summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java4
-rw-r--r--core/java/android/accounts/AbstractAccountAuthenticator.java8
-rw-r--r--core/java/android/accounts/AccountManager.java257
-rw-r--r--core/java/android/accounts/AccountManagerService.java34
-rw-r--r--core/java/android/app/ActivityThread.java153
-rw-r--r--core/java/android/app/AlarmManager.java21
-rw-r--r--core/java/android/app/BackupAgent.java24
-rw-r--r--core/java/android/app/ContextImpl.java155
-rw-r--r--core/java/android/app/DeviceAdminInfo.java31
-rw-r--r--core/java/android/app/DeviceAdminReceiver.java (renamed from core/java/android/app/DeviceAdmin.java)19
-rw-r--r--core/java/android/app/DevicePolicyManager.java34
-rw-r--r--core/java/android/app/Dialog.java5
-rw-r--r--core/java/android/app/FullBackupAgent.java3
-rwxr-xr-xcore/java/android/app/IAlarmManager.aidl1
-rw-r--r--core/java/android/app/IUiModeManager.aidl49
-rw-r--r--core/java/android/app/IntentService.java2
-rw-r--r--core/java/android/app/SearchDialog.java592
-rw-r--r--core/java/android/app/SearchManager.java266
-rw-r--r--core/java/android/app/SearchableInfo.java20
-rw-r--r--core/java/android/app/Service.java60
-rw-r--r--core/java/android/app/SuggestionsAdapter.java154
-rw-r--r--core/java/android/backup/AbsoluteFileBackupHelper.java12
-rw-r--r--core/java/android/backup/BackupDataInput.java46
-rw-r--r--core/java/android/backup/BackupDataInputStream.java6
-rw-r--r--core/java/android/backup/BackupDataOutput.java23
-rw-r--r--core/java/android/backup/BackupHelper.java14
-rw-r--r--core/java/android/backup/BackupHelperAgent.java28
-rw-r--r--core/java/android/backup/BackupHelperDispatcher.java2
-rw-r--r--core/java/android/backup/BackupManager.java15
-rw-r--r--core/java/android/backup/FileBackupHelper.java14
-rw-r--r--core/java/android/backup/FileBackupHelperBase.java11
-rw-r--r--core/java/android/backup/RestoreObserver.java1
-rw-r--r--core/java/android/backup/RestoreSession.java10
-rw-r--r--core/java/android/backup/SharedPreferencesBackupHelper.java18
-rw-r--r--core/java/android/content/ComponentName.java6
-rw-r--r--core/java/android/content/ContentResolver.java232
-rw-r--r--core/java/android/content/ContentService.java38
-rw-r--r--core/java/android/content/Context.java138
-rw-r--r--core/java/android/content/ContextWrapper.java10
-rw-r--r--core/java/android/content/EventLogTags.logtags6
-rw-r--r--core/java/android/content/IContentService.aidl42
-rw-r--r--core/java/android/content/Intent.java49
-rw-r--r--core/java/android/content/PeriodicSync.aidl (renamed from core/java/com/google/android/net/ParentalControlState.aidl)11
-rw-r--r--core/java/android/content/PeriodicSync.java84
-rw-r--r--core/java/android/content/SyncManager.java528
-rw-r--r--core/java/android/content/SyncOperation.java3
-rw-r--r--core/java/android/content/SyncQueue.java88
-rw-r--r--core/java/android/content/SyncStatusInfo.java70
-rw-r--r--core/java/android/content/SyncStorageEngine.java399
-rw-r--r--core/java/android/content/pm/ActivityInfo.java7
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java26
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageInfo.java30
-rw-r--r--core/java/android/content/pm/PackageManager.java62
-rw-r--r--core/java/android/content/pm/PackageParser.java141
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java4
-rw-r--r--core/java/android/content/res/AssetManager.java2
-rw-r--r--core/java/android/content/res/Configuration.java97
-rw-r--r--core/java/android/content/res/Resources.java6
-rw-r--r--core/java/android/database/sqlite/SQLiteClosable.java26
-rw-r--r--core/java/android/database/sqlite/SQLiteCompiledSql.java15
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java232
-rw-r--r--core/java/android/database/sqlite/SQLiteDebug.java72
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java101
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java37
-rwxr-xr-xcore/java/android/gesture/Learner.java3
-rw-r--r--core/java/android/hardware/Camera.java106
-rw-r--r--[-rwxr-xr-x]core/java/android/inputmethodservice/KeyboardView.java21
-rw-r--r--core/java/android/net/ConnectivityManager.java45
-rw-r--r--core/java/android/net/Downloads.java645
-rw-r--r--core/java/android/net/IConnectivityManager.aidl6
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java303
-rw-r--r--core/java/android/net/SSLSessionCache.java68
-rw-r--r--core/java/android/net/WebAddress.java7
-rw-r--r--core/java/android/os/Base64Utils.java31
-rw-r--r--core/java/android/os/Environment.java184
-rw-r--r--core/java/android/os/FileObserver.java130
-rw-r--r--core/java/android/os/HandlerThread.java2
-rw-r--r--core/java/android/os/ICheckinService.aidl41
-rw-r--r--core/java/android/os/IMountServiceListener.aidl66
-rw-r--r--core/java/android/os/INetworkManagementService.aidl14
-rw-r--r--core/java/android/os/IParentalControlCallback.aidl27
-rw-r--r--core/java/android/os/Message.java76
-rw-r--r--core/java/android/os/MountServiceListener.java69
-rw-r--r--core/java/android/os/MountServiceResultCode.java34
-rw-r--r--core/java/android/os/Power.java2
-rw-r--r--core/java/android/os/Process.java3
-rw-r--r--core/java/android/os/storage/IMountService.aidl (renamed from core/java/android/os/IMountService.aidl)48
-rw-r--r--core/java/android/os/storage/IMountServiceListener.aidl43
-rw-r--r--core/java/android/os/storage/MountServiceListener.java44
-rw-r--r--core/java/android/os/storage/StorageEventListener.java38
-rw-r--r--core/java/android/os/storage/StorageManager.java297
-rw-r--r--core/java/android/os/storage/StorageResultCode.java73
-rw-r--r--core/java/android/pim/RecurrenceSet.java12
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java60
-rw-r--r--core/java/android/pim/vcard/VCardEntry.java25
-rw-r--r--core/java/android/provider/Browser.java49
-rw-r--r--core/java/android/provider/Calendar.java28
-rw-r--r--core/java/android/provider/Checkin.java269
-rw-r--r--core/java/android/provider/ContactsContract.java40
-rw-r--r--core/java/android/provider/MediaStore.java8
-rw-r--r--core/java/android/provider/Settings.java117
-rw-r--r--core/java/android/server/BluetoothA2dpService.java37
-rw-r--r--core/java/android/server/BluetoothService.java8
-rw-r--r--core/java/android/server/search/SearchManagerService.java8
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java3
-rw-r--r--core/java/android/speech/RecognitionListener.java3
-rw-r--r--core/java/android/speech/RecognitionManager.java80
-rw-r--r--core/java/android/speech/RecognitionService.java18
-rw-r--r--core/java/android/speech/RecognizerIntent.java104
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java25
-rw-r--r--core/java/android/storage/StorageEventListener.java57
-rw-r--r--core/java/android/storage/StorageManager.java300
-rw-r--r--core/java/android/text/AndroidCharacter.java44
-rw-r--r--core/java/android/text/Layout.java217
-rw-r--r--core/java/android/text/StaticLayout.java4
-rw-r--r--core/java/android/text/Styled.java255
-rw-r--r--core/java/android/text/style/LeadingMarginSpan.java49
-rw-r--r--core/java/android/text/style/TabStopSpan.java17
-rw-r--r--core/java/android/text/util/Rfc822Tokenizer.java4
-rw-r--r--core/java/android/util/base64/Base64.java741
-rw-r--r--core/java/android/util/base64/Base64InputStream.java153
-rw-r--r--core/java/android/util/base64/Base64OutputStream.java155
-rw-r--r--core/java/android/view/GestureDetector.java8
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java10
-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/MotionEvent.java100
-rw-r--r--core/java/android/view/SurfaceView.java3
-rw-r--r--core/java/android/view/VelocityTracker.java131
-rw-r--r--core/java/android/view/View.java187
-rw-r--r--core/java/android/view/ViewGroup.java16
-rw-r--r--core/java/android/view/ViewRoot.java51
-rw-r--r--core/java/android/view/WindowManagerPolicy.java5
-rw-r--r--core/java/android/view/animation/Animation.java35
-rw-r--r--core/java/android/view/animation/AnimationSet.java4
-rw-r--r--core/java/android/webkit/DateSorter.java4
-rw-r--r--core/java/android/webkit/EventLogTags.logtags11
-rwxr-xr-xcore/java/android/webkit/GeolocationPermissions.java91
-rw-r--r--core/java/android/webkit/GoogleLocationSettingManager.java209
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java5
-rw-r--r--core/java/android/webkit/MimeTypeMap.java1
-rw-r--r--core/java/android/webkit/WebSettings.java37
-rw-r--r--core/java/android/webkit/WebStorage.java4
-rw-r--r--core/java/android/webkit/WebTextView.java47
-rw-r--r--core/java/android/webkit/WebView.java295
-rw-r--r--core/java/android/webkit/WebViewCore.java37
-rw-r--r--core/java/android/widget/AbsListView.java576
-rw-r--r--core/java/android/widget/DatePicker.java12
-rw-r--r--core/java/android/widget/EditText.java8
-rw-r--r--core/java/android/widget/ExpandableListView.java12
-rw-r--r--core/java/android/widget/GridView.java14
-rw-r--r--core/java/android/widget/HorizontalScrollView.java72
-rw-r--r--core/java/android/widget/ImageView.java25
-rw-r--r--core/java/android/widget/LinearLayout.java61
-rw-r--r--core/java/android/widget/ListView.java62
-rw-r--r--core/java/android/widget/NumberPicker.java1
-rw-r--r--core/java/android/widget/ScrollView.java70
-rw-r--r--core/java/android/widget/TabHost.java6
-rw-r--r--core/java/android/widget/TableLayout.java10
-rw-r--r--core/java/android/widget/TableRow.java2
-rw-r--r--core/java/android/widget/ViewFlipper.java2
-rw-r--r--core/java/com/android/internal/app/DisableCarModeActivity.java42
-rw-r--r--core/java/com/android/internal/app/ExternalMediaFormatActivity.java2
-rwxr-xr-xcore/java/com/android/internal/app/IMediaContainerService.aidl1
-rwxr-xr-xcore/java/com/android/internal/app/NetInitiatedActivity.java1
-rw-r--r--core/java/com/android/internal/app/ShutdownThread.java2
-rw-r--r--core/java/com/android/internal/app/TetherActivity.java63
-rw-r--r--core/java/com/android/internal/app/UsbStorageActivity.java191
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java182
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java1
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java7
-rw-r--r--core/java/com/android/internal/util/HierarchicalStateMachine.java2
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java3
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboard.java268
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java229
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardView.java39
-rw-r--r--core/java/com/android/internal/widget/WeightedLinearLayout.java8
-rw-r--r--core/java/com/google/android/net/ParentalControl.java73
-rw-r--r--core/java/com/google/android/net/ParentalControlState.java56
181 files changed, 8836 insertions, 4547 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..8bc7428 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -296,8 +296,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 +367,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 +376,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/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 414d963..43a0f30 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -27,6 +27,7 @@ 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 java.io.IOException;
@@ -66,6 +67,8 @@ import com.google.android.collect.Maps;
* 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.
+ * getResult() will throw an {@link IllegalStateException} if you call it from the main thread
+ * before the request has completed, i.e. before the callback has been invoked.
* <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
@@ -148,6 +151,8 @@ public class AccountManager {
* Get the password that is associated with the account. Returns null if the account does
* not exist.
* <p>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -165,6 +170,8 @@ 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>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -184,6 +191,8 @@ public class AccountManager {
* @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>
+ * It is safe to call this method from the main thread.
+ * <p>
* No permission is required to make this call.
*/
public AuthenticatorDescription[] getAuthenticatorTypes() {
@@ -200,6 +209,8 @@ public class AccountManager {
* @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>
+ * It is safe to call this method from the main thread.
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
*/
public Account[] getAccounts() {
@@ -218,6 +229,8 @@ public class AccountManager {
* @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>
+ * It is safe to call this method from the main thread.
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
*/
public Account[] getAccountsByType(String type) {
@@ -242,6 +255,22 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Boolean result = hasFeatures(account, features, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * hasFeatures(account, features, new AccountManagerCallback<Boolean>() {
+ * public void run(AccountManagerFuture<Boolean> future) {
+ * Boolean result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
*
* @param account The {@link Account} to test
@@ -271,7 +300,9 @@ public class AccountManager {
}
/**
- * Add an account to the AccountManager's set of known accounts.
+ * Add an account to the AccountManager's set of known accounts.
+ * <p>
+ * It is safe to call this method from the main thread.
* <p>
* Requires that the caller has permission
* {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
@@ -303,6 +334,22 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Boolean result = removeAccount(account, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * removeAccount(account, new AccountManagerCallback<Boolean>() {
+ * public void run(AccountManagerFuture<Boolean> future) {
+ * Boolean result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
*
* @param account The {@link Account} to remove
@@ -333,6 +380,8 @@ 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>
+ * It is safe to call this method from the main thread.
+ * <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
@@ -352,6 +401,8 @@ public class AccountManager {
* asking the authenticaticor to generate one. If the account or the
* authtoken do not exist then null is returned.
* <p>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -380,6 +431,8 @@ 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>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -403,6 +456,8 @@ public class AccountManager {
* Sets the password for account to null. If the account does not exist then this call
* has no effect.
* <p>
+ * It is safe to call this method from the main thread.
+ * <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.
*/
@@ -423,6 +478,8 @@ 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>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -451,6 +508,8 @@ public class AccountManager {
* Sets the authtoken named by "authTokenType" to the value specified by authToken.
* If the account does not exist then this call has no effect.
* <p>
+ * It is safe to call this method from the main thread.
+ * <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.
@@ -472,6 +531,8 @@ public class AccountManager {
* {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
* then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
* <p>
+ * It is not safe to call this method from the main thread. See {@link #getAuthToken}.
+ * <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
@@ -504,9 +565,8 @@ public class AccountManager {
* 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.
+ * an activity that will do the prompting. The supplied activity will be used to launch the
+ * intent and the result will come from the launched activity.
* <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
@@ -517,6 +577,23 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = getAuthToken(
+ * account, authTokenType, options, activity, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * getAuthToken(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
*
* @param account The account whose credentials are to be updated.
@@ -524,8 +601,9 @@ public class AccountManager {
* 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.
+ * the intent will be started with this activity. If you do not with to have the intent
+ * started automatically then use the other form,
+ * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, android.os.Handler)}
* @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
@@ -560,9 +638,13 @@ public class AccountManager {
* 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
+ * If the authenticator needs to prompt the user for credentials, rather than returning the
+ * authtoken it will instead 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.
+ * is true then a notification will be created that launches this intent. This intent can be
+ * invoked by the caller directly to start the activity that prompts the user for the
+ * updated credentials. Otherwise this activity will not be run until the user activates
+ * the notification.
* <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
@@ -573,6 +655,23 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = getAuthToken(
+ * account, authTokenType, notifyAuthFailure, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * getAuthToken(account, authTokenType, notifyAuthFailure, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
*
* @param account The account whose credentials are to be updated.
@@ -620,6 +719,23 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = addAccount(
+ * account, authTokenType, features, options, activity, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * addAccount(account, authTokenType, features, options, activity, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <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.
@@ -641,7 +757,6 @@ public class AccountManager {
* <ul>
* <li> {@link #KEY_INTENT}, or
* <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
- * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
* </ul>
*/
public AccountManagerFuture<Bundle> addAccount(final String accountType,
@@ -653,7 +768,7 @@ public class AccountManager {
if (accountType == null) {
Log.e(TAG, "the account must not be null");
// to unblock caller waiting on Future.get()
- set(new Bundle());
+ set(new Bundle());
return;
}
mService.addAcount(mResponse, accountType, authTokenType,
@@ -662,6 +777,51 @@ public class AccountManager {
}.start();
}
+ /**
+ * Queries for accounts that match the given account type and feature set.
+ * <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>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Account[] result =
+ * getAccountsByTypeAndFeatures(accountType, features, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * getAccountsByTypeAndFeatures(accountType, features, new AccountManagerCallback<Account[]>() {
+ * public void run(AccountManagerFuture<Account[]> future) {
+ * Account[] result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
+ * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param type The type of {@link Account} to return. If null is passed in then an empty
+ * array will be returned.
+ * @param features the features with which to filter the accounts list. Each returned account
+ * will have all specified features. This may be null, which will mean the account list will
+ * not be filtered by features, making this functionally identical to
+ * {@link #getAccountsByType(String)}.
+ * @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 an {@link Account} array that contains accounts of the specified
+ * type that match all the requested features.
+ */
public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
final String type, final String[] features,
AccountManagerCallback<Account[]> callback, Handler handler) {
@@ -704,6 +864,23 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = confirmCredentials(
+ * account, options, activity, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * confirmCredentials(account, options, activity, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
*
* @param account The account whose credentials are to be checked
@@ -752,6 +929,23 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = updateCredentials(
+ * account, authTokenType, options, activity, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * updateCredentials(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <p>
* Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
*
* @param account The account whose credentials are to be updated.
@@ -770,7 +964,7 @@ public class AccountManager {
* <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.
+ * credentials.
* </ul>
* If the user presses "back" then the request will be canceled.
*/
@@ -802,6 +996,22 @@ public class AccountManager {
* {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
* which will then block until the request completes.
* <p>
+ * Do not block the main thread waiting this method's result.
+ * <p>
+ * Not allowed from main thread (but allowed from other threads):
+ * <pre>
+ * Bundle result = editProperties(accountType, activity, callback, handler).getResult();
+ * </pre>
+ * Allowed from main thread:
+ * <pre>
+ * editProperties(accountType, activity, new AccountManagerCallback<Bundle>() {
+ * public void run(AccountManagerFuture<Bundle> future) {
+ * Bundle result = future.getResult();
+ * // use result
+ * }
+ * }, handler);
+ * </pre>
+ * <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.
@@ -833,14 +1043,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;
+ }
}
}
@@ -905,7 +1114,9 @@ public class AccountManager {
private Bundle internalGetResult(Long timeout, TimeUnit unit)
throws OperationCanceledException, IOException, AuthenticatorException {
- ensureNotOnMainThread();
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
try {
if (timeout == null) {
return get();
@@ -1071,7 +1282,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();
@@ -1372,7 +1585,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);
}
}
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index e3ccd00..770554e 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -466,7 +466,8 @@ public class AccountManagerService
public TestFeaturesSession(IAccountManagerResponse response,
Account account, String[] features) {
- super(response, account.type, false /* expectActivityLaunch */);
+ super(response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mFeatures = features;
mAccount = account;
}
@@ -520,7 +521,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;
}
@@ -794,7 +796,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"
@@ -945,7 +948,8 @@ public class AccountManagerService
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);
@@ -970,7 +974,8 @@ public class AccountManagerService
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);
}
@@ -990,7 +995,8 @@ public class AccountManagerService
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);
}
@@ -1012,7 +1018,8 @@ public class AccountManagerService
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);
}
@@ -1034,7 +1041,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;
}
@@ -1176,11 +1184,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;
@@ -1319,6 +1330,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) {
@@ -1788,7 +1802,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;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 56e44c8..13cc3ba 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1392,7 +1392,7 @@ public final class ActivityThread {
r.startsNotResumed = notResumed;
r.createdConfig = config;
- synchronized (mRelaunchingActivities) {
+ synchronized (mPackages) {
mRelaunchingActivities.add(r);
}
@@ -1523,8 +1523,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);
}
@@ -2060,6 +2063,7 @@ public final class ActivityThread {
= new HashMap<IBinder, Service>();
AppBindData mBoundApplication;
Configuration mConfiguration;
+ Configuration mResConfiguration;
Application mInitialApplication;
final ArrayList<Application> mAllApplications
= new ArrayList<Application>();
@@ -2073,14 +2077,6 @@ public final class ActivityThread {
boolean mSystemThread = false;
boolean mJitEnabled = 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;
-
// 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.
@@ -2092,6 +2088,9 @@ public final class ActivityThread {
DisplayMetrics mDisplayMetrics = null;
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
@@ -3555,7 +3554,7 @@ public final class ActivityThread {
// 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;
@@ -3585,8 +3584,12 @@ 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;
+ }
}
}
@@ -3761,62 +3764,81 @@ public final class ActivityThread {
}
}
+ final void applyConfigurationToResourcesLocked(Configuration config) {
+ if (mResConfiguration == null) {
+ mResConfiguration = new Configuration();
+ }
+ if (!mResConfiguration.isOtherSeqNewer(config)) {
+ return;
+ }
+ 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();
+ //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();
+ }
+ }
+ }
+
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) Log.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);
-
- ContextImpl.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);
+ }
}
}
@@ -3856,7 +3878,7 @@ public final class ActivityThread {
ArrayList<ComponentCallbacks> callbacks
= new ArrayList<ComponentCallbacks>();
- synchronized(mPackages) {
+ synchronized (mPackages) {
callbacks = collectComponentCallbacksLocked(true, null);
}
@@ -4348,6 +4370,25 @@ public final class ActivityThread {
"Unable to instantiate Application():" + e.toString(), e);
}
}
+
+ ViewRoot.addConfigCallback(new ComponentCallbacks() {
+ public void onConfigurationChanged(Configuration newConfig) {
+ synchronized (mPackages) {
+ if (mPendingConfiguration == null ||
+ mPendingConfiguration.isOtherSeqNewer(newConfig)) {
+ mPendingConfiguration = newConfig;
+
+ // We need to apply this change to the resources
+ // immediately, because upon returning the view
+ // hierarchy will be informed about it.
+ applyConfigurationToResourcesLocked(newConfig);
+ }
+ }
+ queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
+ }
+ public void onLowMemory() {
+ }
+ });
}
private final void detach()
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/BackupAgent.java b/core/java/android/app/BackupAgent.java
index 35b6fed..a2bfc76 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -33,8 +33,8 @@ import java.io.IOException;
/**
* This is the central interface between an application and Android's
* settings backup mechanism.
- *
- * @hide pending API solidification
+ *
+ * <p>STOPSHIP write more documentation about the backup process here.
*/
public abstract class BackupAgent extends ContextWrapper {
private static final String TAG = "BackupAgent";
@@ -62,9 +62,9 @@ public abstract class BackupAgent extends ContextWrapper {
* 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 data A structured wrapper around 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.
@@ -77,10 +77,18 @@ public abstract class BackupAgent extends ContextWrapper {
* 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,
+ * of the final state to the newStateFd 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 An open, read-only ParcelFileDescriptor pointing to a full snapshot
- * of the application's data.
+ * @param data A structured wrapper around an open, read-only ParcelFileDescriptor
+ * pointing to a full snapshot of the application's data. Typically the
+ * application will use helper classes to read this 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
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5f89496..db6a4bf 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -52,6 +52,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;
@@ -84,7 +85,8 @@ import android.os.ServiceManager;
import android.os.StatFs;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
-import android.storage.StorageManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.AndroidRuntimeException;
@@ -197,9 +199,9 @@ class ContextImpl extends Context {
private File mDatabasesDir;
private File mPreferencesDir;
private File mFilesDir;
-
-
private File mCacheDir;
+ private File mExternalFilesDir;
+ private File mExternalCacheDir;
private static long sInstanceCount = 0;
@@ -438,6 +440,38 @@ class ContextImpl 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) {
@@ -457,7 +491,28 @@ class ContextImpl 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);
@@ -1560,6 +1615,24 @@ class ContextImpl 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
@@ -2166,7 +2239,7 @@ class ContextImpl extends Context {
filter, null, null, null);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverInternal(sPackageRemovedReceiver,
sdFilter, null, null, null);
}
@@ -2189,7 +2262,7 @@ class ContextImpl extends Context {
String pkgList[] = null;
String action = intent.getAction();
boolean immediateGc = false;
- if (Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
immediateGc = true;
} else {
@@ -2586,76 +2659,6 @@ class ContextImpl extends Context {
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
- // Constants related to app heuristics
- // No-installation limit for internal flash: 10% or less space available
- private static final double LOW_NAND_FLASH_TRESHOLD = 0.1;
-
- // SD-to-internal app size threshold: currently set to 1 MB
- private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
-
- @Override
- public int recommendAppInstallLocation(ApplicationInfo appInfo, Uri packageURI) {
- // Initial implementation:
- // Package size = code size + cache size + data size
- // If code size > 1 MB, install on SD card.
- // Else install on internal NAND flash, unless space on NAND is less than 10%
-
- if ((packageURI == null) || (appInfo == null)) {
- return INSTALL_PARSE_FAILED_NOT_APK;
- }
-
- StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath());
- StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
-
- long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() *
- (long)internalFlashStats.getBlockSize();
- long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() *
- (long)internalFlashStats.getBlockSize();
- long availSDSize = (long)sdcardStats.getAvailableBlocks() *
- (long)sdcardStats.getBlockSize();
-
- double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize;
-
- final String archiveFilePath = packageURI.getPath();
- File apkFile = new File(archiveFilePath);
- long pkgLen = apkFile.length();
-
- // Consider application flags preferences as well...
- boolean installOnlyOnSD = ((appInfo.flags & PackageManager.INSTALL_ON_SDCARD) != 0);
-
- // These are not very precise measures, but I guess it is hard to estimate sizes
- // before installing the package.
- // As a shortcut, I am assuming that the package fits on NAND flash if the available
- // space is three times that of the APK size. For SD, we only worry about the APK size.
- // Since packages are downloaded into SD, this might not even be necessary.
- boolean fitsOnSD = (pkgLen < availSDSize) && ((2 * pkgLen) < availInternalFlashSize);
- boolean fitsOnInternalFlash = ((pkgLen * 3) < availInternalFlashSize);
-
- // Does not fit, recommend no installation.
- if (!fitsOnSD && !fitsOnInternalFlash) {
- return INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
-
- if (pkgLen < (INSTALL_ON_SD_THRESHOLD) && fitsOnInternalFlash && !(installOnlyOnSD)) {
- // recommend internal NAND likely
- if (pctNandFree < LOW_NAND_FLASH_TRESHOLD) {
- // Low space on NAND (<10%) - install on SD
- return INSTALL_ON_SDCARD;
- }
- return INSTALL_ON_INTERNAL_FLASH;
- } else {
- if (fitsOnSD) {
- // Recommend SD card
- return INSTALL_ON_SDCARD;
- } else if (fitsOnInternalFlash && (pctNandFree >= LOW_NAND_FLASH_TRESHOLD) &&
- !(installOnlyOnSD)) {
- return INSTALL_ON_INTERNAL_FLASH;
- } else {
- return INSTALL_FAILED_INSUFFICIENT_STORAGE;
- }
- }
- }
-
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java
index ab9c44f..bedf4b4 100644
--- a/core/java/android/app/DeviceAdminInfo.java
+++ b/core/java/android/app/DeviceAdminInfo.java
@@ -60,8 +60,8 @@ public final class DeviceAdminInfo implements Parcelable {
/**
* A type of policy that this device admin can use: able to watch login
- * attempts from the user, via {@link DeviceAdmin#ACTION_PASSWORD_FAILED},
- * {@link DeviceAdmin#ACTION_PASSWORD_SUCCEEDED}, and
+ * 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"
@@ -80,23 +80,15 @@ public final class DeviceAdminInfo implements Parcelable {
public static final int USES_POLICY_RESET_PASSWORD = 2;
/**
- * A type of policy that this device admin can use: able to limit the
+ * 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 "limit-unlock"
- * tag in the "uses-policies" section of its meta-data.
- */
- public static final int USES_POLICY_LIMIT_UNLOCK = 3;
-
- /**
- * A type of policy that this device admin can use: able to force the device
- * to lock via{@link DevicePolicyManager#lockNow}.
- *
* <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 = 4;
+ public static final int USES_POLICY_FORCE_LOCK = 3;
/**
* A type of policy that this device admin can use: able to factory
@@ -106,7 +98,7 @@ public final class DeviceAdminInfo implements Parcelable {
* <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 = 5;
+ public static final int USES_POLICY_WIPE_DATA = 4;
/** @hide */
public static class PolicyInfo {
@@ -140,9 +132,6 @@ public final class DeviceAdminInfo implements Parcelable {
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_LIMIT_UNLOCK, "limit-unlock",
- com.android.internal.R.string.policylab_limitUnlock,
- com.android.internal.R.string.policydesc_limitUnlock));
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_FORCE_LOCK, "force-lock",
com.android.internal.R.string.policylab_forceLock,
com.android.internal.R.string.policydesc_forceLock));
@@ -180,10 +169,10 @@ public final class DeviceAdminInfo implements Parcelable {
XmlResourceParser parser = null;
try {
- parser = ai.loadXmlMetaData(pm, DeviceAdmin.DEVICE_ADMIN_META_DATA);
+ parser = ai.loadXmlMetaData(pm, DeviceAdminReceiver.DEVICE_ADMIN_META_DATA);
if (parser == null) {
throw new XmlPullParserException("No "
- + DeviceAdmin.DEVICE_ADMIN_META_DATA + " meta-data");
+ + DeviceAdminReceiver.DEVICE_ADMIN_META_DATA + " meta-data");
}
AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -314,8 +303,8 @@ public final class DeviceAdminInfo implements Parcelable {
* 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_LIMIT_UNLOCK},
- * {@link #USES_POLICY_FORCE_LOCK}, {@link #USES_POLICY_WIPE_DATA}.
+ * {@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;
diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdminReceiver.java
index ecbad01..453e0bf 100644
--- a/core/java/android/app/DeviceAdmin.java
+++ b/core/java/android/app/DeviceAdminReceiver.java
@@ -29,6 +29,13 @@ import android.os.Bundle;
* 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
@@ -40,9 +47,9 @@ import android.os.Bundle;
* to the device administrator, as parsed by the {@link DeviceAdminInfo} class.
* A typical file would be:</p>
*
- * {@sample development/samples/ApiDemos/res/xml/sample_device_admin.xml meta_data}
+ * {@sample development/samples/ApiDemos/res/xml/device_admin_sample.xml meta_data}
*/
-public class DeviceAdmin extends BroadcastReceiver {
+public class DeviceAdminReceiver extends BroadcastReceiver {
private static String TAG = "DevicePolicy";
private static boolean DEBUG = false;
private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
@@ -51,7 +58,7 @@ public class DeviceAdmin extends BroadcastReceiver {
* 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 DeviceAdmin#onEnabled(Context, Intent)}. To be
+ * 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.
@@ -85,7 +92,7 @@ public class DeviceAdmin extends BroadcastReceiver {
* 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 DeviceAdmin#onDisabled(Context, Intent)}. Note
+ * 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.
@@ -100,7 +107,7 @@ public class DeviceAdmin extends BroadcastReceiver {
* of the new password with {@link DevicePolicyManager#isActivePasswordSufficient()
* DevicePolicyManager.isActivePasswordSufficient()}.
* You will generally
- * handle this in {@link DeviceAdmin#onPasswordChanged}.
+ * handle this in {@link DeviceAdminReceiver#onPasswordChanged}.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
@@ -116,7 +123,7 @@ public class DeviceAdmin extends BroadcastReceiver {
* number of failed password attempts there have been with
* {@link DevicePolicyManager#getCurrentFailedPasswordAttempts
* DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally
- * handle this in {@link DeviceAdmin#onPasswordFailed}.
+ * handle this in {@link DeviceAdminReceiver#onPasswordFailed}.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
index 08cdd05..d611807 100644
--- a/core/java/android/app/DevicePolicyManager.java
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -36,7 +36,7 @@ import java.util.List;
/**
* Public interface for managing policies enforced on a device. Most clients
- * of this class must have published a {@link DeviceAdmin} that the user
+ * of this class must have published a {@link DeviceAdminReceiver} that the user
* has currently enabled.
*/
public class DevicePolicyManager {
@@ -195,7 +195,7 @@ public class DevicePolicyManager {
* {@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 DeviceAdmin} this request is associated with.
+ * @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}, or {@link #PASSWORD_QUALITY_ALPHANUMERIC}.
@@ -243,7 +243,7 @@ public class DevicePolicyManager {
* {@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 DeviceAdmin} this request is associated with.
+ * @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.
*/
@@ -328,15 +328,21 @@ public class DevicePolicyManager {
}
/**
- * Set the maximum number of failed password attempts that are allowed
- * before the device wipes its data. This is convenience for implementing
- * the corresponding functionality with a combination of watching failed
- * password attempts and calling {@link #wipeData} upon reaching a certain
- * count, and as such requires that you request both
- * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
+ * 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}}.
*
- * @param admin Which {@link DeviceAdmin} this request is associated with.
+ * <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.
*/
@@ -369,7 +375,9 @@ public class DevicePolicyManager {
}
/**
- * Force a new password on the user. This takes effect immediately.
+ * 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
@@ -404,10 +412,10 @@ public class DevicePolicyManager {
* the length that the user can set. It takes effect immediately.
*
* <p>The calling device admin must have requested
- * {@link DeviceAdminInfo#USES_POLICY_LIMIT_UNLOCK} to be able to call
+ * {@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 DeviceAdmin} this request is associated with.
+ * @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.
*/
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index ed38240..4598bb5 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -822,11 +822,6 @@ 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()) {
- return false;
- }
-
// associate search with owner activity if possible (otherwise it will default to
// global search).
final ComponentName appName = getAssociatedActivity();
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
index d89db96..db198ad 100644
--- a/core/java/android/app/FullBackupAgent.java
+++ b/core/java/android/app/FullBackupAgent.java
@@ -48,7 +48,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/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/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
new file mode 100644
index 0000000..6ac8a2a
--- /dev/null
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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();
+
+ /**
+ * Disables the car mode.
+ */
+ void disableCarMode();
+
+ /**
+ * 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..3fd36a3 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -42,7 +42,7 @@ public abstract class IntentService extends Service {
* {@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
+ * is killed 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.
*/
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7fa5b08..581b436 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -16,6 +16,9 @@
package android.app;
+import com.android.common.Patterns;
+import com.android.common.speech.Recognition;
+
import static android.app.SuggestionsAdapter.getColumnString;
import android.content.ActivityNotFoundException;
@@ -67,8 +70,6 @@ import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
-import com.android.common.Patterns;
-
import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
@@ -88,15 +89,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,19 +112,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private SearchableInfo mSearchable;
private ComponentName mLaunchComponent;
private Bundle mAppSearchData;
- private boolean mGlobalSearchMode;
private Context mActivityContext;
private SearchManager mSearchManager;
-
- // 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;
// For voice searching
private final Intent mVoiceWebSearchIntent;
@@ -160,7 +144,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @param context Application Context we can use for system acess
*/
public SearchDialog(Context context, SearchManager searchManager) {
- super(context, com.android.internal.R.style.Theme_GlobalSearchBar);
+ super(context, com.android.internal.R.style.Theme_SearchBar);
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
@@ -243,14 +227,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.
@@ -259,67 +237,16 @@ 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 = "";
-
- cancel();
- mSearchManager.startGlobalSearch(currentSearchText, false, mStoredAppSearchData);
- return 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;
}
@@ -337,38 +264,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, false);
- // 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().
@@ -377,20 +290,6 @@ 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();
@@ -416,7 +315,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchable = null;
mActivityContext = null;
mUserQuery = null;
- mPreviousComponents = null;
}
/**
@@ -430,7 +328,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mWorkingSpinner.setVisible(working, false);
mWorkingSpinner.invalidateSelf();
}
-
+
/**
* Closes and gets rid of the suggestions adapter.
*/
@@ -444,7 +342,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
mSuggestionsAdapter = null;
}
-
+
/**
* Save the minimal set of data necessary to recreate the search
*
@@ -460,10 +358,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;
@@ -483,22 +377,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;
}
@@ -508,7 +390,7 @@ 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();
@@ -573,19 +455,13 @@ 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);
}
}
@@ -613,7 +489,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// 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,
@@ -707,15 +583,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
/**
* 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.
+ * to the left of the search field.
*/
private boolean isBrowserSearch() {
return mLaunchComponent.flattenToShortString().startsWith("com.android.browser/");
}
- /**
+ /*
* Listeners of various types
*/
@@ -761,12 +635,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);
@@ -777,25 +645,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
*/
@@ -825,12 +675,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 (Patterns.WEB_URL.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);
@@ -907,7 +753,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (searchable.getVoiceSearchLaunchWebSearch()) {
getContext().startActivity(mVoiceWebSearchIntent);
} else if (searchable.getVoiceSearchLaunchRecognizer()) {
- Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
+ Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
+ searchable);
getContext().startActivity(appSearchIntent);
}
} catch (ActivityNotFoundException e) {
@@ -925,8 +772,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @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,
@@ -956,23 +803,23 @@ 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,
+ voiceIntent.putExtra(Recognition.EXTRA_CALLING_PACKAGE,
searchActivity == null ? null : searchActivity.toShortString());
// Add the values that configure forwarding the results
@@ -1166,14 +1013,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);
}
@@ -1186,7 +1028,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.
*
@@ -1203,20 +1045,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;
@@ -1225,163 +1054,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.
*/
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
@@ -1398,106 +1092,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) {
@@ -1505,61 +1099,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
@@ -1608,10 +1147,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
@@ -1642,16 +1180,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);
}
@@ -1662,9 +1197,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);
}
@@ -1672,16 +1204,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).
@@ -1838,12 +1364,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 3046a2c..ce5f1bf 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -350,7 +350,7 @@ import java.util.List;
* <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
+ * 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.
*
@@ -360,7 +360,8 @@ import java.util.List;
*
* <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>
+ * 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>
@@ -379,7 +380,7 @@ import java.util.List;
* <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
+ * 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
@@ -546,7 +547,8 @@ import java.util.List;
* 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>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>
@@ -570,7 +572,7 @@ import java.util.List;
*
* <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
+ * 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.
@@ -1279,16 +1281,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.
@@ -1299,15 +1291,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
@@ -1318,14 +1301,6 @@ 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.
*/
@@ -1339,58 +1314,6 @@ public class SearchManager
public final static String EXTRA_SELECT_QUERY = "select_query";
/**
- * 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
- */
- public static class DialogCursorProtocol {
-
- /**
- * 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";
- }
-
- /**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
* to obtain the action message that was defined for a particular search action key and/or
@@ -1431,40 +1354,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>
*/
@@ -1541,10 +1430,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
*/
@@ -1604,27 +1490,6 @@ 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 starting the global search activity.
* The global search provider should handle this intent.
*
@@ -1638,8 +1503,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";
@@ -1675,7 +1538,7 @@ public class SearchManager
* @hide
*/
public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
-
+
/**
* Reference to the shared system search service.
*/
@@ -1684,13 +1547,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;
@@ -1708,21 +1564,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.
@@ -1769,15 +1610,14 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
- ensureSearchDialog();
-
if (globalSearch) {
startGlobalSearch(initialQuery, selectInitialQuery, appSearchData);
return;
}
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
+ ensureSearchDialog();
+
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData);
}
private void ensureSearchDialog() {
@@ -1850,6 +1690,25 @@ public class SearchManager
}
/**
+ * 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() {
+ ComponentName globalSearch = getGlobalSearchActivity();
+ if (globalSearch == null) {
+ return null;
+ }
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.setPackage(globalSearch.getPackageName());
+ return intent.resolveActivity(mContext.getPackageManager());
+ }
+
+ /**
* Similar to {@link #startSearch} but actually fires off the search query after invoking
* the search dialog. Made available for testing purposes.
*
@@ -2006,17 +1865,6 @@ public class SearchManager
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.
@@ -2103,56 +1951,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() + ")");
- }
}
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 9897742..4496354 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -73,7 +73,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;
@@ -155,11 +155,11 @@ public final class SearchableInfo implements Parcelable {
}
/**
- * 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.
*/
- public String getSettingsDescription() {
- return mSettingsDescription;
+ public int getSettingsDescriptionId() {
+ return mSettingsDescriptionId;
}
/**
@@ -311,8 +311,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(
@@ -495,7 +495,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");
@@ -746,7 +746,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();
@@ -784,7 +784,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 8ec5bd4..6767332 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -56,6 +56,8 @@ import java.io.PrintWriter;
* <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="ServiceLifecycle"></a>
@@ -166,6 +168,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";
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 173c3e1..57795d1 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -16,7 +16,6 @@
package android.app;
-import android.app.SearchManager.DialogCursorProtocol;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,12 +29,10 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
-import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
@@ -65,7 +62,6 @@ 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;
// Cached column indexes, updated when the cursor changes.
@@ -76,29 +72,8 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
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;
-
private final Runnable mStartSpinnerRunnable;
private final Runnable mStopSpinnerRunnable;
@@ -110,8 +85,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
@@ -126,7 +100,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mOutsideDrawablesCache = outsideDrawablesCache;
mBackgroundsCache = new SparseArray<Drawable.ConstantState>();
- mGlobalSearchMode = globalSearchMode;
mStartSpinnerRunnable = new Runnable() {
public void run() {
@@ -140,10 +113,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
};
- // 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() {
@@ -177,27 +146,22 @@ 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.
+ */
+ mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
try {
final Cursor 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();
+ 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);
- }
+ mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
}
}
@@ -221,21 +185,8 @@ 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);
@@ -249,79 +200,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
}
- @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.
*/
@@ -353,20 +231,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) {
diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/backup/AbsoluteFileBackupHelper.java
index 1dbccc9..6bf848f 100644
--- a/core/java/android/backup/AbsoluteFileBackupHelper.java
+++ b/core/java/android/backup/AbsoluteFileBackupHelper.java
@@ -27,7 +27,7 @@ import java.io.FileDescriptor;
* Like FileBackupHelper, but takes absolute paths for the files instead of
* subpaths of getFilesDir()
*
- * @hide
+ * STOPSHIP: document!
*/
public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "AbsoluteFileBackupHelper";
@@ -36,6 +36,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 +61,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/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
index e67b0be..295dc66 100644
--- a/core/java/android/backup/BackupDataInput.java
+++ b/core/java/android/backup/BackupDataInput.java
@@ -21,7 +21,9 @@ import android.content.Context;
import java.io.FileDescriptor;
import java.io.IOException;
-/** @hide */
+/**
+ * STOPSHIP: document!
+ */
public class BackupDataInput {
int mBackupReader;
@@ -33,6 +35,7 @@ public class BackupDataInput {
int dataSize;
}
+ /** @hide */
public BackupDataInput(FileDescriptor fd) {
if (fd == null) throw new NullPointerException();
mBackupReader = ctor(fd);
@@ -41,6 +44,7 @@ public class BackupDataInput {
}
}
+ /** @hide */
protected void finalize() throws Throwable {
try {
dtor(mBackupReader);
@@ -49,6 +53,13 @@ public class BackupDataInput {
}
}
+ /**
+ * Consumes the next header from the restore stream.
+ *
+ * @return true when there is an entity ready for consumption from the restore stream,
+ * false 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) {
@@ -66,6 +77,11 @@ public class BackupDataInput {
}
}
+ /**
+ * Report the key associated with the current record in the restore stream
+ * @return the current record's key string
+ * @throws IllegalStateException if the next record header has not yet been read
+ */
public String getKey() {
if (mHeaderReady) {
return mHeader.key;
@@ -74,6 +90,13 @@ public class BackupDataInput {
}
}
+ /**
+ * Report the size in bytes of the data associated with the current record 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;
@@ -82,6 +105,19 @@ public class BackupDataInput {
}
}
+ /**
+ * 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.
+ *
+ * @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
+ * @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);
@@ -95,6 +131,14 @@ public class BackupDataInput {
}
}
+ /**
+ * Consume the current record's data without actually reading it into a buffer
+ * for further processing. This allows a {@link android.app.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);
diff --git a/core/java/android/backup/BackupDataInputStream.java b/core/java/android/backup/BackupDataInputStream.java
index b705c4c..503b3c1 100644
--- a/core/java/android/backup/BackupDataInputStream.java
+++ b/core/java/android/backup/BackupDataInputStream.java
@@ -16,12 +16,11 @@
package android.backup;
-import android.util.Log;
-
import java.io.InputStream;
import java.io.IOException;
-/** @hide */
+/**
+ * STOPSHIP: document */
public class BackupDataInputStream extends InputStream {
String key;
@@ -30,6 +29,7 @@ public class BackupDataInputStream extends InputStream {
BackupDataInput mData;
byte[] mOneByte;
+ /** @hide */
BackupDataInputStream(BackupDataInput data) {
mData = data;
}
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
index d29c5ba..672d01f 100644
--- a/core/java/android/backup/BackupDataOutput.java
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -21,13 +21,16 @@ import android.content.Context;
import java.io.FileDescriptor;
import java.io.IOException;
-/** @hide */
+/**
+ * STOPSHIP: document
+ */
public class BackupDataOutput {
int mBackupWriter;
public static final int OP_UPDATE = 1;
public static final int OP_DELETE = 2;
+ /** @hide */
public BackupDataOutput(FileDescriptor fd) {
if (fd == null) throw new NullPointerException();
mBackupWriter = ctor(fd);
@@ -36,7 +39,15 @@ public class BackupDataOutput {
}
}
- // A dataSize of -1 indicates that the record under this key should be deleted
+ /**
+ * Mark the beginning of one record in the backup data stream.
+ *
+ * @param key
+ * @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) {
@@ -46,6 +57,13 @@ public class BackupDataOutput {
}
}
+ /**
+ * 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) {
@@ -59,6 +77,7 @@ public class BackupDataOutput {
setKeyPrefix_native(mBackupWriter, keyPrefix);
}
+ /** @hide */
protected void finalize() throws Throwable {
try {
dtor(mBackupWriter);
diff --git a/core/java/android/backup/BackupHelper.java b/core/java/android/backup/BackupHelper.java
index 3983e28..fc48cf2 100644
--- a/core/java/android/backup/BackupHelper.java
+++ b/core/java/android/backup/BackupHelper.java
@@ -20,7 +20,9 @@ import android.os.ParcelFileDescriptor;
import java.io.InputStream;
-/** @hide */
+/**
+ * STOPSHIP: document!
+ */
public interface BackupHelper {
/**
* Based on oldState, determine which of the files from the application's data directory
@@ -31,16 +33,18 @@ public interface BackupHelper {
ParcelFileDescriptor newState);
/**
- * Called by BackupHelperDispatcher to dispatch one entity of data.
+ * Called by BackupHelperAgent to restore one entity from the restore dataset.
* <p class=note>
* Do not close the <code>data</code> stream. Do not read more than
- * <code>dataSize</code> bytes from <code>data</code>.
+ * <code>data.size()</code> bytes from <code>data</code>.
*/
public void restoreEntity(BackupDataInputStream data);
/**
- *
+ * Called by BackupHelperAgent to write the new backup state file corresponding to
+ * the current state of the app's data at the time the backup operation was
+ * performed.
*/
- public void writeRestoreSnapshot(ParcelFileDescriptor fd);
+ public void writeNewStateDescription(ParcelFileDescriptor fd);
}
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
index 5d0c4a2..dc17154f 100644
--- a/core/java/android/backup/BackupHelperAgent.java
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -26,28 +26,54 @@ import android.util.Log;
import java.io.IOException;
-/** @hide */
+/**
+ * A convenient BackupAgent wrapper class that automatically manages heterogeneous
+ * data sets within the backup data, each identified by a unique key prefix. An
+ * application will typically extend this class in their own backup agent. Then,
+ * within the agent's onBackup() and onRestore() methods, it will call
+ * {@link #addHelper(String, BackupHelper)} one or more times to specify the data
+ * sets, then invoke super.onBackup() or super.onRestore() to have the BackupHelperAgent
+ * implementation process the data.
+ *
+ * STOPSHIP: document!
+ */
public class BackupHelperAgent extends BackupAgent {
static final String TAG = "BackupHelperAgent";
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/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
index 6ccb83e..bf2c44d 100644
--- a/core/java/android/backup/BackupHelperDispatcher.java
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -138,7 +138,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/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 0b27117..4bf59eb 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -38,7 +38,11 @@ import android.util.Log;
* documentation for {@link android.app.BackupAgent} for a detailed description
* of how the backup then proceeds.
*
- * @hide pending API solidification
+ * <p>STOPSHIP more documentation here! Include the attributes:
+ * android:backupAgent
+ * android:allowBackup
+ * android:restoreNeedsApplication
+ * android:killAfterRestore
*/
public class BackupManager {
private static final String TAG = "BackupManager";
@@ -110,11 +114,8 @@ public class BackupManager {
}
/**
- * 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}
+ * Begin the process of restoring data from backup. See the
+ * {@link android.backup.RestoreSession} class for documentation on that process.
*/
public RestoreSession beginRestoreSession() {
if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
@@ -128,7 +129,7 @@ public class BackupManager {
IRestoreSession binder = sService.beginRestoreSession(transport);
session = new RestoreSession(mContext, binder);
} catch (RemoteException e) {
- Log.d(TAG, "beginRestoreSession() couldn't connect");
+ Log.w(TAG, "beginRestoreSession() couldn't connect");
}
}
return session;
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
index dacfc8f..68b4d42 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -23,7 +23,9 @@ import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
-/** @hide */
+/**
+ * STOPSHIP: document! [manages backup of a set of files; restore is totally opaque]
+ */
public class FileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "FileBackupHelper";
private static final boolean DEBUG = false;
@@ -32,6 +34,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);
@@ -60,6 +69,9 @@ 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.
+ */
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/backup/FileBackupHelperBase.java
index 03ae476..a0ff38b 100644
--- a/core/java/android/backup/FileBackupHelperBase.java
+++ b/core/java/android/backup/FileBackupHelperBase.java
@@ -20,7 +20,6 @@ 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;
@@ -80,18 +79,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 +98,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/RestoreObserver.java b/core/java/android/backup/RestoreObserver.java
index 3be8c08..e4182750 100644
--- a/core/java/android/backup/RestoreObserver.java
+++ b/core/java/android/backup/RestoreObserver.java
@@ -19,7 +19,6 @@ package android.backup;
/**
* 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 RestoreObserver {
/**
diff --git a/core/java/android/backup/RestoreSession.java b/core/java/android/backup/RestoreSession.java
index d10831e..bc410c4 100644
--- a/core/java/android/backup/RestoreSession.java
+++ b/core/java/android/backup/RestoreSession.java
@@ -27,7 +27,6 @@ import android.util.Log;
/**
* Interface for applications to use when managing a restore session.
- * @hide
*/
public class RestoreSession {
static final String TAG = "RestoreSession";
@@ -44,6 +43,8 @@ public class RestoreSession {
* 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.
* On error, returns null.
+ *
+ * {@hide}
*/
public RestoreSet[] getAvailableRestoreSets() {
try {
@@ -62,10 +63,12 @@ public class RestoreSession {
*
* @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
+ * @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.
+ *
+ * {@hide}
*/
public int restoreAll(long token, RestoreObserver observer) {
int err = -1;
@@ -117,8 +120,7 @@ public class 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 #getAvailableRestoreSets()} or
- * {@link #restorePackage(long, String, RestoreObserver)} failed.
+ * even if {@link #restorePackage(String, RestoreObserver)} failed.
*/
public void endRestoreSession() {
try {
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index 6a0bc96..f9c97a3 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -23,7 +23,9 @@ import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
-/** @hide */
+/**
+ * STOPSHIP: document!
+ */
public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "SharedPreferencesBackupHelper";
private static final boolean DEBUG = false;
@@ -31,6 +33,13 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
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
+ * @param prefGroups
+ */
public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
super(context);
@@ -38,6 +47,9 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
mPrefGroups = prefGroups;
}
+ /**
+ * Backs up the configured SharedPreferences groups
+ */
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
Context context = mContext;
@@ -54,6 +66,10 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
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;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 0455202..c4ba05d 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.
*/
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index eb2d7b1..45f361e 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";
@@ -139,6 +167,11 @@ public abstract class ContentResolver {
/** @hide */
public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
+ // Always log queries which take 100ms+; shorter queries are
+ // sampled accordingly.
+ private static final int SLOW_THRESHOLD_MILLIS = 100;
+ private final Random mRandom = new Random(); // guarded by itself
+
public ContentResolver(Context context) {
mContext = context;
}
@@ -211,12 +244,15 @@ 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
+ 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);
@@ -548,7 +584,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 {
@@ -603,7 +643,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 {
@@ -628,7 +672,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 {
@@ -656,7 +704,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 {
@@ -966,6 +1018,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.
*/
@@ -1099,6 +1245,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_OPERATION,
+ 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_OPERATION,
+ uri.toString(),
+ operation,
+ selection != null ? selection : "",
+ durationMillis,
+ blockingPackage != null ? blockingPackage : "",
+ samplePercent);
+ }
private final class CursorWrapperInner extends CursorWrapper {
private IContentProvider mContentProvider;
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 974a667..e0dfab5 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -32,6 +32,8 @@ import android.Manifest;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
* {@hide}
@@ -273,6 +275,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");
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5aefe4c..672e5f7 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -25,6 +25,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.media.MediaScannerConnection.ScanResultListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -137,7 +138,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();
@@ -393,11 +415,84 @@ 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 install 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[],
+ * ScanResultListener) 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 its 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
+ */
+ 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
@@ -407,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.
*
@@ -1110,7 +1236,7 @@ public abstract class Context {
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
- * @see android.storage.StorageManager
+ * @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
@@ -1243,11 +1369,11 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
- * android.storage.StorageManager} for accesssing system storage
+ * android.os.storage.StorageManager} for accesssing system storage
* functions.
*
* @see #getSystemService
- * @see android.storage.StorageManager
+ * @see android.os.storage.StorageManager
*/
public static final String STORAGE_SERVICE = "storage";
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/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags
new file mode 100644
index 0000000..a815b95
--- /dev/null
+++ b/core/java/android/content/EventLogTags.logtags
@@ -0,0 +1,6 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.content;
+
+52002 content_query_operation (uri|3),(projection|3),(selection|3),(sortorder|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
+52003 content_update_operation (uri|3),(operation|3),(selection|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index b0f14c1..2d906ed 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -21,6 +21,7 @@ import android.content.ActiveSyncInfo;
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();
-
+
/**
* 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/Intent.java b/core/java/android/content/Intent.java
index e957e20..1b0437c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1381,8 +1381,8 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_MEDIA_RESOURCES_AVAILABLE =
- "android.intent.action.MEDIA_RESOURCES_AVAILABILE";
+ public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
/**
* Broadcast Action: Resources for a set of packages are currently
@@ -1406,8 +1406,8 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_MEDIA_RESOURCES_UNAVAILABLE =
- "android.intent.action.MEDIA_RESOURCES_UNAVAILABILE";
+ public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABILE";
/**
* Broadcast Action: The current system wallpaper has changed. See
@@ -1815,9 +1815,18 @@ public class Intent implements Parcelable, Cloneable {
/**
* 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.
+ * or undocked.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>{@link #EXTRA_DOCK_STATE}</em> - the current dock
+ * state, which depends on the state of the car mode.</li>
+ * <li><em>{@link #EXTRA_PHYSICAL_DOCK_STATE}</em> - the physical dock
+ * state.</li>
+ * <li><em>{@link #EXTRA_CAR_MODE_ENABLED}</em> - a boolean indicating the
+ * state of the car mode.</li>
+ * </ul>
+ * <p>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.
*/
@@ -1838,7 +1847,7 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
public static final String ACTION_REMOTE_INTENT =
- "android.intent.action.REMOTE_INTENT";
+ "com.google.android.pushmessaging.intent.RECEIVE";
/**
* Broadcast Action: hook for permforming cleanup after a system update.
@@ -2152,6 +2161,22 @@ public class Intent implements Parcelable, Cloneable {
public static final int EXTRA_DOCK_STATE_CAR = 2;
/**
+ * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
+ * intents to request the physical dock state. Possible values are
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED},
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or
+ * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}.
+ */
+ public static final String EXTRA_PHYSICAL_DOCK_STATE =
+ "android.intent.extra.PHYSICAL_DOCK_STATE";
+
+ /**
+ * Used as an boolean extra field in {@link android.content.Intent#ACTION_DOCK_EVENT}
+ * intents to indicate that the car mode is enabled or not.
+ */
+ public static final String EXTRA_CAR_MODE_ENABLED = "android.intent.extra.CAR_MODE_ENABLED";
+
+ /**
* Boolean that can be supplied as meta-data with a dock activity, to
* indicate that the dock should take over the home key when it is active.
*/
@@ -2198,8 +2223,8 @@ public class Intent implements Parcelable, Cloneable {
/**
* This field is part of
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_AVAILABLE},
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_UNAVAILABLE}
+ * {@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.
* @hide
*/
@@ -2208,8 +2233,8 @@ public class Intent implements Parcelable, Cloneable {
/**
* This field is part of
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_AVAILABLE},
- * {@link android.content.Intent#ACTION_MEDIA_RESOURCES_UNAVAILABLE}
+ * {@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.
* @hide
diff --git a/core/java/com/google/android/net/ParentalControlState.aidl b/core/java/android/content/PeriodicSync.aidl
index ed1326a..4530591 100644
--- a/core/java/com/google/android/net/ParentalControlState.aidl
+++ b/core/java/android/content/PeriodicSync.aidl
@@ -1,11 +1,11 @@
-/**
- * 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.
* 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,
@@ -14,5 +14,6 @@
* limitations under the License.
*/
-package com.google.android.net;
-parcelable ParentalControlState;
+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/SyncManager.java b/core/java/android/content/SyncManager.java
index 699b61d..317e5a9 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -52,14 +52,7 @@ 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.HashSet;
@@ -74,12 +67,6 @@ import java.util.concurrent.CountDownLatch;
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;
@@ -128,6 +115,11 @@ public 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
@@ -137,7 +129,7 @@ public class SyncManager implements OnAccountsUpdateListener {
private Context mContext;
- private volatile Account[] mAccounts = null;
+ private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mSyncWakeLock;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
@@ -157,9 +149,7 @@ public 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;
@@ -201,9 +191,11 @@ public 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,
@@ -276,7 +268,6 @@ public class SyncManager implements OnAccountsUpdateListener {
// ignore the rest of the states -- leave our boolean alone.
}
if (mDataConnectionIsConnected) {
- initializeSyncPoll();
sendCheckAlarmsMessage();
}
}
@@ -291,14 +282,8 @@ public 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 volatile boolean mBootCompleted = false;
private ConnectivityManager getConnectivityManager() {
@@ -338,9 +323,6 @@ public 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);
@@ -396,49 +378,6 @@ public 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.
@@ -453,68 +392,6 @@ public 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 */);
- }
-
- 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 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);
- }
- }
- }
- return 0;
- }
-
public ActiveSyncContext getActiveSyncContext() {
return mActiveSyncContext;
}
@@ -575,17 +452,6 @@ public class SyncManager implements OnAccountsUpdateListener {
}
/**
- * 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"));
- }
-
- /**
* Initiate a sync. This can start a sync for all providers
* (pass null to url, set onlyTicklable to false), only those
* providers that are marked as ticklable (pass null to url,
@@ -616,18 +482,6 @@ public 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");
- }
- return;
- }
-
final boolean backgroundDataUsageAllowed = !mBootCompleted ||
getConnectivityManager().getBackgroundDataSetting();
@@ -645,13 +499,6 @@ public 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");
@@ -662,6 +509,12 @@ public class SyncManager implements OnAccountsUpdateListener {
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) {
@@ -720,7 +573,7 @@ public class SyncManager implements OnAccountsUpdateListener {
final boolean syncAutomatically = masterSyncAutomatically
&& mSyncStorageEngine.getSyncAutomatically(account, authority);
boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+ ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically);
if (!syncAllowed) {
if (isLoggable) {
Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
@@ -799,12 +652,6 @@ public 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);
@@ -923,7 +770,7 @@ public class SyncManager implements OnAccountsUpdateListener {
mSyncStorageEngine.setBackoff(account, authority,
SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
- mSyncQueue.clear(account, authority);
+ mSyncQueue.remove(account, authority);
}
}
@@ -933,21 +780,29 @@ public class SyncManager implements OnAccountsUpdateListener {
Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
}
+ operation = new SyncOperation(operation);
+
+ // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+ // request. Retries of the request will always honor the backoff, so clear the
+ // flag in case we retry this request.
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ }
+
// If this sync aborted because the internal sync loop retried too many times then
// don't reschedule. Otherwise we risk getting into a retry loop.
// If the operation succeeded to some extent then retry immediately.
// If this was a two-way sync then retry soft errors with an exponential backoff.
// If this was an upward sync then schedule a two-way sync immediately.
// Otherwise do not reschedule.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
- Log.d(TAG, "not retrying sync operation because it is a manual sync: "
+ 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)) {
- final SyncOperation newSyncOperation = new SyncOperation(operation);
- newSyncOperation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ 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(newSyncOperation);
+ scheduleSyncOperation(operation);
} else if (syncResult.tooManyRetries) {
Log.d(TAG, "not retrying sync operation because it retried too many times: "
+ operation);
@@ -956,13 +811,21 @@ public class SyncManager implements OnAccountsUpdateListener {
Log.d(TAG, "retrying sync operation because even though it had an error "
+ "it achieved some success");
}
- scheduleSyncOperation(new SyncOperation(operation));
+ scheduleSyncOperation(operation);
+ } else if (syncResult.syncAlreadyInProgress) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation that failed because there was already a "
+ + "sync in progress: " + operation);
+ }
+ scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
+ operation.authority, operation.extras,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS));
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ operation);
}
- scheduleSyncOperation(new SyncOperation(operation));
+ scheduleSyncOperation(operation);
} else {
Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ operation);
@@ -1054,9 +917,7 @@ public 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:");
@@ -1072,19 +933,19 @@ public 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: ");
@@ -1102,7 +963,9 @@ public 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);
@@ -1125,6 +988,11 @@ public 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 {
@@ -1152,80 +1020,76 @@ public class SyncManager implements OnAccountsUpdateListener {
}
}
- 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(":");
- final String syncable = authority.syncable > 0
- ? "syncable"
- : (authority.syncable == 0 ? "not syncable" : "not initialized");
- final String enabled = authority.enabled ? "enabled" : "disabled";
- final String delayUntil = authority.delayUntil > now
- ? "delay for " + ((authority.delayUntil - now) / 1000) + " sec"
- : "no delay required";
- final String backoff = authority.backoffTime > now
- ? "backoff for " + ((authority.backoffTime - now) / 1000)
- + " sec"
- : "no backoff required";
- final String backoffDelay = authority.backoffDelay > 0
- ? ("the backoff increment is " + authority.backoffDelay / 1000
- + " sec")
- : "no backoff increment";
- pw.println(String.format(
- " settings: %s, %s, %s, %s, %s",
- enabled, syncable, backoff, backoffDelay, delayUntil));
- 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);
}
}
}
@@ -1580,6 +1444,31 @@ public class SyncManager implements OnAccountsUpdateListener {
}
}
+ private boolean isSyncAllowed(Account account, String authority, boolean ignoreSettings,
+ boolean backgroundDataUsageAllowed) {
+ Account[] accounts = mAccounts;
+
+ // skip the sync if the account of this operation no longer exists
+ if (!ArrayUtils.contains(accounts, account)) {
+ return false;
+ }
+
+ // skip the sync if it isn't manual and auto sync is disabled
+ final boolean syncAutomatically =
+ mSyncStorageEngine.getSyncAutomatically(account, authority)
+ && mSyncStorageEngine.getMasterSyncAutomatically();
+ if (!(ignoreSettings || (backgroundDataUsageAllowed && syncAutomatically))) {
+ return false;
+ }
+
+ if (mSyncStorageEngine.getIsSyncable(account, authority) <= 0) {
+ // if not syncable or if the syncable is unknown (< 0), don't allow
+ return false;
+ }
+
+ return true;
+ }
+
private void runStateSyncing() {
// if the sync timeout has been reached then cancel it
@@ -1589,7 +1478,7 @@ public class SyncManager implements OnAccountsUpdateListener {
if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
SyncOperation nextSyncOperation;
synchronized (mSyncQueue) {
- nextSyncOperation = mSyncQueue.nextReadyToRun(now);
+ nextSyncOperation = getNextReadyToRunSyncOperation(now);
}
if (nextSyncOperation != null) {
Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
@@ -1627,7 +1516,7 @@ public class SyncManager implements OnAccountsUpdateListener {
// 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");
}
@@ -1643,7 +1532,7 @@ public class SyncManager implements OnAccountsUpdateListener {
synchronized (mSyncQueue) {
final long now = SystemClock.elapsedRealtime();
while (true) {
- op = mSyncQueue.nextReadyToRun(now);
+ op = getNextReadyToRunSyncOperation(now);
if (op == null) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: no more sync operations, returning");
@@ -1655,42 +1544,10 @@ public class SyncManager implements OnAccountsUpdateListener {
// from the queue now
mSyncQueue.remove(op);
- // Sync is disabled, drop this operation.
- if (!isSyncEnabled()) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
- }
- continue;
- }
-
- // skip the sync if the account of this operation no longer exists
- if (!ArrayUtils.contains(accounts, op.account)) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: account not present, dropping " + op);
- }
- 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();
- if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
- + "dropping " + op);
- }
- continue;
- }
-
- if (mSyncStorageEngine.getIsSyncable(op.account, op.authority) <= 0) {
- // if not syncable or if the syncable is unknown (< 0), don't allow
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
- + "dropping " + op);
- }
+ final boolean ignoreSettings = op.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+ if (!isSyncAllowed(op.account, op.authority, ignoreSettings,
+ backgroundDataUsageAllowed)) {
continue;
}
@@ -1736,6 +1593,74 @@ public class SyncManager implements OnAccountsUpdateListener {
// MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
}
+ private SyncOperation getNextPeriodicSyncOperation() {
+ final boolean backgroundDataUsageAllowed =
+ getConnectivityManager().getBackgroundDataSetting();
+ SyncStorageEngine.AuthorityInfo best = null;
+ long bestPollTimeAbsolute = Long.MAX_VALUE;
+ Bundle bestExtras = null;
+ ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+ for (SyncStorageEngine.AuthorityInfo info : infos) {
+ if (!isSyncAllowed(info.account, info.authority, false /* ignoreSettings */,
+ backgroundDataUsageAllowed)) {
+ continue;
+ }
+ SyncStatusInfo status = mSyncStorageEngine.getStatusByAccountAndAuthority(
+ info.account, info.authority);
+ int i = 0;
+ for (Pair<Bundle, Long> periodicSync : info.periodicSyncs) {
+ long lastPollTimeAbsolute = status != null ? status.getPeriodicSyncTime(i) : 0;
+ final Bundle extras = periodicSync.first;
+ final Long periodInSeconds = periodicSync.second;
+ long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
+ if (nextPollTimeAbsolute < bestPollTimeAbsolute) {
+ best = info;
+ bestPollTimeAbsolute = nextPollTimeAbsolute;
+ bestExtras = extras;
+ }
+ i++;
+ }
+ }
+
+ if (best == null) {
+ return null;
+ }
+
+ final long nowAbsolute = System.currentTimeMillis();
+ final SyncOperation syncOperation = new SyncOperation(best.account,
+ SyncStorageEngine.SOURCE_PERIODIC,
+ best.authority, bestExtras, 0 /* delay */);
+ syncOperation.earliestRunTime = SystemClock.elapsedRealtime()
+ + (bestPollTimeAbsolute - nowAbsolute);
+ if (syncOperation.earliestRunTime < 0) {
+ syncOperation.earliestRunTime = 0;
+ }
+ return syncOperation;
+ }
+
+ public Pair<SyncOperation, Long> bestSyncOperationCandidate() {
+ Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
+ SyncOperation nextOp = nextOpAndRunTime != null ? nextOpAndRunTime.first : null;
+ Long nextRunTime = nextOpAndRunTime != null ? nextOpAndRunTime.second : null;
+ SyncOperation pollOp = getNextPeriodicSyncOperation();
+ if (nextOp != null
+ && (pollOp == null || nextOp.expedited
+ || nextRunTime <= pollOp.earliestRunTime)) {
+ return nextOpAndRunTime;
+ } else if (pollOp != null) {
+ return Pair.create(pollOp, pollOp.earliestRunTime);
+ } else {
+ return null;
+ }
+ }
+
+ private SyncOperation getNextReadyToRunSyncOperation(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = bestSyncOperationCandidate();
+ return nextOpAndRunTime != null && nextOpAndRunTime.second <= now
+ ? nextOpAndRunTime.first
+ : null;
+ }
+
private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
mActiveSyncContext.mSyncAdapter = syncAdapter;
final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
@@ -1948,7 +1873,6 @@ public class SyncManager implements OnAccountsUpdateListener {
// 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();
@@ -1961,7 +1885,8 @@ public class SyncManager implements OnAccountsUpdateListener {
ActiveSyncContext activeSyncContext = mActiveSyncContext;
if (activeSyncContext == null) {
synchronized (mSyncQueue) {
- alarmTime = mSyncQueue.nextRunTime(now);
+ Pair<SyncOperation, Long> candidate = bestSyncOperationCandidate();
+ alarmTime = candidate != null ? candidate.second : null;
}
} else {
final long notificationTime =
@@ -2102,9 +2027,8 @@ public class SyncManager implements OnAccountsUpdateListener {
SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
syncOperation.account.name.hashCode());
- mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
- downstreamActivity, upstreamActivity);
+ mSyncStorageEngine.stopSyncEvent(rowId, syncOperation.extras, elapsedTime,
+ resultMessage, downstreamActivity, upstreamActivity);
}
}
-
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index 2d6e833..4599165 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -26,6 +26,9 @@ public class SyncOperation implements Comparable {
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);
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index a9f15d9..bb21488 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -2,8 +2,6 @@ package android.content;
import com.google.android.collect.Maps;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;
@@ -32,10 +30,9 @@ public class SyncQueue {
final int N = ops.size();
for (int i=0; i<N; i++) {
SyncStorageEngine.PendingOperation op = ops.get(i);
- // -1 is a special value that means expedited
- final int delay = op.expedited ? -1 : 0;
SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, delay);
+ op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+ syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
}
@@ -90,8 +87,15 @@ public class SyncQueue {
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));
@@ -102,54 +106,34 @@ public class SyncQueue {
* Find the operation that should run next. Operations are sorted by their earliestRunTime,
* prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
* backoff and delayUntil times, if any.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}
* @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.
*/
- private Pair<SyncOperation, Long> nextOperation(long now) {
- SyncOperation lowestOp = null;
- long lowestOpRunTime = 0;
+ public Pair<SyncOperation, Long> nextOperation() {
+ SyncOperation best = null;
+ long bestRunTime = 0;
for (SyncOperation op : mOperationsMap.values()) {
- // effectiveRunTime:
- // - backoffTime > currentTime : backoffTime
- // - backoffTime <= currentTime : op.runTime
- Pair<Long, Long> backoff = null;
- long delayUntilTime = 0;
- final boolean isManualSync =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (!isManualSync) {
- backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
- delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
+ 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);
}
- long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime);
- long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime;
- if (lowestOp == null
- || (lowestOp.expedited == op.expedited
- ? opRunTime < lowestOpRunTime
- : op.expedited)) {
- lowestOp = op;
- lowestOpRunTime = opRunTime;
+ // if the expedited state of both ops are the same then compare their runtime.
+ // Otherwise the candidate is only better than the current best if the candidate
+ // is expedited.
+ if (best == null
+ || (best.expedited == op.expedited ? opRunTime < bestRunTime : op.expedited)) {
+ best = op;
+ bestRunTime = opRunTime;
}
}
- if (lowestOp == null) {
+ if (best == null) {
return null;
}
- return Pair.create(lowestOp, lowestOpRunTime);
- }
-
- /**
- * Return when the next SyncOperation will be ready to run or null if there are
- * none.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
- * decide if the sync operation is ready to run
- * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
- */
- public Long nextRunTime(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
- if (nextOpAndRunTime == null) {
- return null;
- }
- return nextOpAndRunTime.second;
+ return Pair.create(best, bestRunTime);
}
/**
@@ -158,21 +142,25 @@ public class SyncQueue {
* decide if the sync operation is ready to run
* @return the SyncOperation that should be run next and is ready to run.
*/
- public SyncOperation nextReadyToRun(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
+ public Pair<SyncOperation, Long> nextReadyToRun(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
return null;
}
- return nextOpAndRunTime.first;
+ return nextOpAndRunTime;
}
- public void clear(Account account, String authority) {
+ 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;
+ 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;
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 db70096..fcb910d 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,7 +36,6 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -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
@@ -62,6 +62,8 @@ public class SyncStorageEngine extends Handler {
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 +91,9 @@ 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 =
@@ -99,7 +104,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";
@@ -164,6 +170,7 @@ public class SyncStorageEngine extends Handler {
long backoffTime;
long backoffDelay;
long delayUntil;
+ final ArrayList<Pair<Bundle, Long>> periodicSyncs;
AuthorityInfo(Account account, String authority, int ident) {
this.account = account;
@@ -173,6 +180,8 @@ public class SyncStorageEngine extends Handler {
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));
}
}
@@ -228,6 +237,7 @@ public class SyncStorageEngine extends Handler {
private int mYearInDays;
private final Context mContext;
+
private static volatile SyncStorageEngine sSyncStorageEngine = null;
/**
@@ -262,17 +272,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"));
- // This call will return the correct directory whether Encrypted File Systems is
- // enabled or not.
- File dataDir = Environment.getSecureDataDirectory();
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"));
@@ -286,14 +294,17 @@ public class SyncStorageEngine extends Handler {
}
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) {
return;
}
- sSyncStorageEngine = new SyncStorageEngine(context);
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
+ sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
public static SyncStorageEngine getSingleton() {
@@ -475,7 +486,7 @@ public class SyncStorageEngine extends Handler {
}
} else {
AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, providerName, -1, false);
+ getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
return;
}
@@ -483,9 +494,6 @@ public class SyncStorageEngine extends Handler {
authority.backoffDelay = nextDelay;
changed = true;
}
- if (changed) {
- writeAccountInfoLocked();
- }
}
if (changed) {
@@ -499,12 +507,12 @@ public class SyncStorageEngine extends Handler {
+ " -> delayUntil " + delayUntil);
}
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ account, providerName, -1 /* ident */, true);
if (authority.delayUntil == delayUntil) {
return;
}
authority.delayUntil = delayUntil;
- writeAccountInfoLocked();
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -520,6 +528,90 @@ public class SyncStorageEngine extends Handler {
}
}
+ 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) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (add) {
+ 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 (!alreadyPresent) {
+ authority.periodicSyncs.add(Pair.create(extras, period));
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+ }
+ } else {
+ 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 (status != null) {
+ status.removePeriodicSyncTime(i);
+ }
+ } else {
+ i++;
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ }
+ 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) {
@@ -540,9 +632,11 @@ 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 */);
}
}
@@ -817,7 +911,25 @@ public class SyncStorageEngine extends Handler {
return id;
}
- public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+ 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, Bundle extras, long elapsedTime, String resultMessage,
long downstreamActivity, long upstreamActivity) {
synchronized (mAuthorities) {
if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
@@ -860,6 +972,17 @@ public class SyncStorageEngine extends Handler {
case SOURCE_SERVER:
status.numSourceServer++;
break;
+ case SOURCE_PERIODIC:
+ status.numSourcePeriodic++;
+ AuthorityInfo authority = mAuthorities.get(item.authorityId);
+ for (int periodicSyncIndex = 0;
+ periodicSyncIndex < authority.periodicSyncs.size();
+ periodicSyncIndex++) {
+ if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) {
+ status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime);
+ }
+ }
+ break;
}
boolean writeStatisticsNow = false;
@@ -948,11 +1071,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) {
@@ -1130,6 +1269,12 @@ public class SyncStorageEngine extends Handler {
return authority;
}
+ public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateSyncStatusLocked(authority.ident);
+ }
+ }
+
private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
SyncStatusInfo status = mSyncStatus.get(authorityId);
if (status == null) {
@@ -1155,6 +1300,25 @@ 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();
+ readLegacyAccountInfoLocked();
+ }
+ }
+
+ /**
* Read all account information back in to the initial engine state.
*/
private void readAccountInfoLocked() {
@@ -1175,59 +1339,23 @@ public class SyncStorageEngine extends Handler {
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 (parser.getDepth() == 2) {
+ if ("authority".equals(tagName)) {
+ authority = parseAuthority(parser);
+ periodicSync = null;
+ }
+ } else if (parser.getDepth() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
}
- 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);
- }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
}
}
}
@@ -1249,6 +1377,105 @@ public class SyncStorageEngine extends Handler {
}
}
+ private AuthorityInfo parseAuthority(XmlPullParser parser) {
+ 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 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");
+ 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);
+ // clear this since we will read these later on
+ 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(enabled)) ? 1 : 0;
+ }
+ } else {
+ Log.w(TAG, "Failure adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ }
+ }
+
+ return authority;
+ }
+
+ private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle();
+ String periodValue = parser.getAttributeValue(null, "period");
+ final long period;
+ try {
+ period = Long.parseLong(periodValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the period of a periodic sync", e);
+ return null;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the period of a periodic sync is null", e);
+ return null;
+ }
+ final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ authority.periodicSyncs.add(periodicSync);
+ return periodicSync;
+ }
+
+ private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+ final Bundle extras = periodicSync.first;
+ String name = parser.getAttributeValue(null, "name");
+ String type = parser.getAttributeValue(null, "type");
+ String value1 = parser.getAttributeValue(null, "value1");
+ String value2 = parser.getAttributeValue(null, "value2");
+
+ try {
+ if ("long".equals(type)) {
+ extras.putLong(name, Long.parseLong(value1));
+ } else if ("integer".equals(type)) {
+ extras.putInt(name, Integer.parseInt(value1));
+ } else if ("double".equals(type)) {
+ extras.putDouble(name, Double.parseDouble(value1));
+ } else if ("float".equals(type)) {
+ extras.putFloat(name, Float.parseFloat(value1));
+ } else if ("boolean".equals(type)) {
+ extras.putBoolean(name, Boolean.parseBoolean(value1));
+ } else if ("string".equals(type)) {
+ extras.putString(name, value1);
+ } else if ("account".equals(type)) {
+ extras.putParcelable(name, new Account(value1, value2));
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ }
+ }
+
/**
* Write all account information to the account file.
*/
@@ -1284,6 +1511,41 @@ public class SyncStorageEngine extends Handler {
} else if (authority.syncable == 0) {
out.attribute(null, "syncable", "false");
}
+ 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");
}
@@ -1389,6 +1651,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");
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index b94bb51..a13f7f9 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -251,6 +251,13 @@ 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.
+ * @hide (UIMODE) Pending API council approval
+ */
+ 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 808c839..123d9b7 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -178,12 +178,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13;
/**
+ * 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_safeMode
+ * android:safeMode} of the &lt;application&gt; tag.
+ */
+ public static final int FLAG_VM_SAFE_MODE = 1<<14;
+
+ /**
* Value for {@link #flags}: this is false if the application has set
* its android:allowBackup to false, true otherwise.
*
* {@hide}
*/
- 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
@@ -194,7 +202,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- 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
@@ -205,7 +213,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public static final int FLAG_RESTORE_NEEDS_APPLICATION = 1<<16;
+ public static final int FLAG_RESTORE_NEEDS_APPLICATION = 1<<17;
/**
* Value for {@link #flags}: this is true if the application has set
@@ -215,7 +223,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public static final int FLAG_NEVER_ENCRYPT = 1<<17;
+ public static final int FLAG_NEVER_ENCRYPT = 1<<18;
/**
* Value for {@link #flags}: Set to true if the application has been
@@ -223,7 +231,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public static final int FLAG_FORWARD_LOCK = 1<<18;
+ public static final int FLAG_FORWARD_LOCK = 1<<19;
/**
* Value for {@link #flags}: Set to true if the application is
@@ -231,7 +239,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public static final int FLAG_ON_SDCARD = 1<<19;
+ public static final int FLAG_ON_SDCARD = 1<<20;
/**
* Value for {@link #flags}: Set to true if the application is
@@ -239,7 +247,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*
* {@hide}
*/
- public static final int FLAG_NATIVE_DEBUGGABLE = 1<<20;
+ public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21;
/**
* Flags associated with the application. Any combination of
@@ -250,7 +258,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;
@@ -270,6 +278,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* 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;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 54db5e0..47789a5 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -47,6 +47,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);
@@ -305,4 +308,5 @@ interface IPackageManager {
*/
void updateExternalMediaStatus(boolean mounted);
+ String nextPackageToClean(String lastPackage);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index a8ce889..c003355 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -131,6 +131,34 @@ 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_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 launch mode style 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 +196,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 +231,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/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 745628a..8576de2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -258,14 +258,7 @@ public abstract class PackageManager {
* package has to be installed on the sdcard.
* @hide
*/
- public static final int INSTALL_ON_SDCARD = 0x00000008;
-
- /**
- * Convenience flag parameter to indicate that this package has to be installed
- * on internal flash.
- * @hide
- */
- public static final int INSTALL_ON_INTERNAL_FLASH = 0x00000000;
+ public static final int INSTALL_EXTERNAL = 0x00000008;
/**
* Flag parameter for
@@ -437,6 +430,15 @@ public abstract class PackageManager {
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 parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the parser was given a path that is not a file, or does not end with the expected
@@ -520,6 +522,14 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
/**
+ * 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 INSTALL_FAILED_INTERNAL_ERROR = -110;
+
+ /**
* 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
@@ -613,20 +623,11 @@ public abstract class PackageManager {
public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
/**
- * Determines best place to install an application: either SD or internal FLASH.
- * Tweak the algorithm for best results.
- * @param appInfo ApplicationInfo object of the package to install.
- * Call utility method to obtain.
- * @param packageURI URI identifying the package's APK file.
- * @return {@link INSTALL_ON_INTERNAL_FLASH} if it is best to install package on internal
- * storage, {@link INSTALL_ON_SDCARD} if it is best to install package on SD card,
- * and {@link INSTALL_FAILED_INSUFFICIENT_STORAGE} if insufficient space to safely install
- * the application. {@link INSTALL_PARSE_FAILED_NOT_APK} Is returned if any input
- * parameter is <code>null</code>.
- * This recommendation does take into account the package's own flags.
+ * Action to external storage service to clean out removed apps.
* @hide
*/
- public abstract int recommendAppInstallLocation(ApplicationInfo appInfo, Uri packageURI);
+ public static final String ACTION_CLEAN_EXTERNAL_STORAGE
+ = "android.content.pm.CLEAN_EXTERNAL_STORAGE";
/**
* Retrieve overall information about an application package that is
@@ -674,6 +675,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
@@ -1135,7 +1153,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
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bac55cc..5823560 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -177,6 +177,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;
}
@@ -709,6 +710,9 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
}
sa.recycle();
+ pkg.installLocation = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_installLocation,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
// Resource boolean are -1, so 1 means we don't know the value.
int supportsSmallScreens = 1;
@@ -957,15 +961,15 @@ public class PackageParser {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestOriginalPackage);
- String name = sa.getNonResourceString(
+ String orig =sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestOriginalPackage_name);
+ if (!pkg.packageName.equals(orig)) {
+ pkg.mOriginalPackage = orig;
+ pkg.mRealPackage = pkg.packageName;
+ }
sa.recycle();
- if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
- pkg.mOriginalPackage = name;
- }
-
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("adopt-permissions")) {
@@ -977,7 +981,7 @@ public class PackageParser {
sa.recycle();
- if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {
+ if (name != null) {
if (pkg.mAdoptPermissions == null) {
pkg.mAdoptPermissions = new ArrayList<String>();
}
@@ -1425,6 +1429,12 @@ public class PackageParser {
}
if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_safeMode,
+ 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;
@@ -2138,7 +2148,7 @@ public class PackageParser {
havePerm = true;
}
if (writePermission != null) {
- writePermission = readPermission.intern();
+ writePermission = writePermission.intern();
havePerm = true;
}
@@ -2540,7 +2550,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();
@@ -2562,6 +2572,7 @@ public class PackageParser {
public String[] usesLibraryFiles = null;
public String mOriginalPackage = null;
+ public String mRealPackage = null;
public ArrayList<String> mAdoptPermissions = null;
// We store the application meta-data independently to avoid multiple unwanted references
@@ -2610,12 +2621,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))
@@ -2626,15 +2665,16 @@ 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) {
@@ -2642,8 +2682,7 @@ public class PackageParser {
intents = new ArrayList<II>(0);
String name = args.sa.getNonResourceString(args.nameRes);
if (name == null) {
- component = null;
- componentShortName = null;
+ className = null;
args.outError[0] = args.tag + " does not specify android:name";
return;
}
@@ -2651,15 +2690,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) {
@@ -2697,9 +2733,36 @@ public class PackageParser {
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;
}
}
@@ -2717,6 +2780,11 @@ public class PackageParser {
super(_owner);
info = _info;
}
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
public String toString() {
return "Permission{"
@@ -2738,6 +2806,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))
@@ -2813,10 +2886,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() + "}";
}
}
@@ -2842,10 +2920,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() + "}";
}
}
@@ -2878,6 +2961,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))
@@ -2911,10 +2999,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/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7362394..a885820 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -117,8 +117,8 @@ public abstract class RegisteredServicesCache<V> {
mContext.registerReceiver(receiver, intentFilter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(receiver, sdFilter);
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 23a408b..7f9a5c6 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -619,7 +619,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.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 1fe34b5..6490b65 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -161,6 +161,41 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* or {@link #ORIENTATION_SQUARE}.
*/
public int orientation;
+
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_TYPE_MASK = 0x0f;
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_TYPE_NORMAL = 0x00;
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_TYPE_CAR = 0x01;
+
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_NIGHT_MASK = 0x30;
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
+ /** @hide (UIMODE) Pending API council approval */
+ public static final int UI_MODE_NIGHT_NO = 0x10;
+ /** @hide (UIMODE) Pending API council approval */
+ 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_NORMAL} 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_NO} or {@link #UI_MODE_NIGHT_YES}.
+ *
+ * @hide (UIMODE) Pending API council approval
+ */
+ public int uiMode;
+
+ /**
+ * @hide Internal book-keeping.
+ */
+ public int seq;
/**
* Construct an invalid Configuration. You must call {@link #setToDefaults}
@@ -189,6 +224,8 @@ 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() {
@@ -217,6 +254,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 +270,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 +280,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_SIZE_UNDEFINED;
+ uiMode = UI_MODE_TYPE_NORMAL;
+ seq = 0;
}
/** {@hide} */
@@ -317,6 +362,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
screenLayout = delta.screenLayout;
}
+ if (delta.uiMode != UI_MODE_TYPE_NORMAL
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ uiMode = delta.uiMode;
+ }
+
+ if (delta.seq != 0) {
+ seq = delta.seq;
+ }
return changed;
}
@@ -393,6 +447,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& screenLayout != delta.screenLayout) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
+ if (delta.uiMode != UI_MODE_TYPE_NORMAL
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ }
return changed;
}
@@ -413,6 +471,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,6 +531,8 @@ 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
@@ -477,6 +566,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigationHidden = source.readInt();
orientation = source.readInt();
screenLayout = source.readInt();
+ uiMode = source.readInt();
+ seq = source.readInt();
}
public int compareTo(Configuration that) {
@@ -510,6 +601,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;
}
@@ -533,6 +626,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
+ this.locale.hashCode() + 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 e4fc259..a5e39d4 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/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index f64261c..e589f34 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.database.CursorWindow;
+
/**
* An object create from a SQLiteDatabase that can be closed.
*/
@@ -29,9 +31,9 @@ public abstract class SQLiteClosable {
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
- "attempt to acquire a reference on a close SQLiteClosable");
+ "attempt to acquire a reference on an already-closed " + getObjInfo());
}
- mReferenceCount++;
+ mReferenceCount++;
}
}
@@ -52,4 +54,24 @@ public abstract class SQLiteClosable {
}
}
}
+
+ private String getObjInfo() {
+ StringBuilder buff = new StringBuilder();
+ buff.append(this.getClass().getName());
+ buff.append(" Obj");
+ 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
index 79527b4..eb85822 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -44,6 +44,9 @@ import android.util.Log;
*/
/* package */ int nStatement = 0;
+ /** when in cache and is in use, this member is set */
+ private boolean mInUse = false;
+
/* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
mDatabase = db;
this.nHandle = db.mNativeHandle;
@@ -92,6 +95,18 @@ import android.util.Log;
}
}
+ /* package */ synchronized boolean isInUse() {
+ return mInUse;
+ }
+
+ /* package */ synchronized void acquire() {
+ mInUse = true;
+ }
+
+ /* package */ synchronized void release() {
+ mInUse = false;
+ }
+
/**
* Make sure that the native resource is cleaned up.
*/
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e4b0191..9ac8a4d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -66,64 +66,60 @@ public class SQLiteDatabase extends SQLiteClosable {
* Algorithms used in ON CONFLICT clause
* http://www.sqlite.org/lang_conflict.html
*/
- public static final class 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.
- */
- public static final int 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.
- */
- public static final int ABORT = 2;
+ /**
+ * 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, 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 FAIL = 3;
+ /**
+ * 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 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 IGNORE = 4;
+ /**
+ * 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 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 REPLACE = 5;
+ /**
+ * 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;
- /**
- * use the following when no conflict action is specified.
- */
- public static final int NONE = 0;
- private static final String[] VALUES = new String[]
- {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR 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 ConflictAlgorithm() {} // disable instantiation of this class
- }
+ /**
+ * 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
@@ -205,10 +201,17 @@ public class SQLiteDatabase extends SQLiteClosable {
private long mLastLockMessageTime = 0L;
- // always log queries which take 100ms+; shorter queries are sampled accordingly
- private static final int QUERY_LOG_TIME_IN_NANOS = 100 * 1000000;
+ // Things related to query logging/sampling for debugging
+ // slow/frequent queries during development. Always log queries
+ // which take 100ms+; shorter queries are sampled accordingly.
+ // Commit statements, which are typically slow, are logged
+ // together with the most recently executed SQL statement, for
+ // disambiguation.
+ private static final int QUERY_LOG_TIME_IN_MILLIS = 100;
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;
/** Used by native code, do not rename */
/* package */ int mNativeHandle = 0;
@@ -290,10 +293,6 @@ public class SQLiteDatabase extends SQLiteClosable {
@Override
protected void onAllReferencesReleased() {
if (isOpen()) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " +
- getDatabaseName(mPath) + ";");
- }
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeClosed = getTime();
}
@@ -548,7 +547,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
if (mTransactionIsSuccessful) {
- execSQL("COMMIT;");
+ execSQL(COMMIT_SQL);
} else {
try {
execSQL("ROLLBACK;");
@@ -782,7 +781,14 @@ public class SQLiteDatabase extends SQLiteClosable {
SQLiteDatabase db = null;
try {
// Open the database.
- return new SQLiteDatabase(path, factory, flags);
+ SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ sqliteDatabase.enableSqlTracing(path);
+ }
+ if (SQLiteDebug.DEBUG_SQL_TIME) {
+ sqliteDatabase.enableSqlProfiling(path);
+ }
+ return sqliteDatabase;
} catch (SQLiteDatabaseCorruptException e) {
// Try to recover from this, if we can.
// TODO: should we do this for other open failures?
@@ -1338,7 +1344,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
- return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
@@ -1360,7 +1366,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
- return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}
/**
@@ -1377,7 +1383,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;
@@ -1399,7 +1405,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);
}
/**
@@ -1412,10 +1418,10 @@ 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 conflictAlgorithm {@link ConflictAlgorithm} for insert conflict resolver
+ * @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 ConflictAlgorithm#IGNORE}
+ * {@link #CONFLICT_IGNORE}
* OR -1 if any error
*/
public long insertWithOnConflict(String table, String nullColumnHack,
@@ -1427,7 +1433,7 @@ public class SQLiteDatabase extends SQLiteClosable {
// Measurements show most sql lengths <= 152
StringBuilder sql = new StringBuilder(152);
sql.append("INSERT");
- sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]);
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
// Measurements show most values lengths < 40
@@ -1551,7 +1557,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, ConflictAlgorithm.NONE);
+ return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
/**
@@ -1562,7 +1568,7 @@ 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 conflictAlgorithm {@link ConflictAlgorithm} for update conflict resolver
+ * @param conflictAlgorithm for update conflict resolver
* @return the number of rows affected
*/
public int updateWithOnConflict(String table, ContentValues values,
@@ -1577,7 +1583,7 @@ public class SQLiteDatabase extends SQLiteClosable {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
- sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]);
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
@@ -1651,10 +1657,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql) throws SQLException {
- long timeStart = Debug.threadCpuTimeNanos();
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, null));
- }
+ long timeStart = SystemClock.uptimeMillis();
lock();
try {
native_execSQL(sql);
@@ -1664,7 +1667,15 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
unlock();
}
- logTimeStat(sql, timeStart);
+
+ // 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(sql + mLastSqlStatement, timeStart);
+ } else {
+ logTimeStat(sql, timeStart);
+ }
}
/**
@@ -1680,10 +1691,7 @@ public class SQLiteDatabase extends SQLiteClosable {
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, bindArgs));
- }
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
lock();
SQLiteStatement statement = null;
try {
@@ -1741,10 +1749,6 @@ public class SQLiteDatabase extends SQLiteClosable {
mLeakedException = new IllegalStateException(path +
" SQLiteDatabase created and never closed");
mFactory = factory;
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|ATTACH DATABASE '" + mPath +
- "' as " + getDatabaseName(mPath) + ";");
- }
dbopen(mPath, mFlags);
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeOpened = getTime();
@@ -1754,10 +1758,6 @@ public class SQLiteDatabase extends SQLiteClosable {
setLocale(Locale.getDefault());
} catch (RuntimeException e) {
Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " +
- getDatabaseName(mPath) + ";");
- }
dbclose();
if (SQLiteDebug.DEBUG_SQL_CACHE) {
mTimeClosed = getTime();
@@ -1770,20 +1770,6 @@ public class SQLiteDatabase extends SQLiteClosable {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
}
- private String getDatabaseName(String path) {
- if (path == null || path.trim().length() == 0) {
- return "db not specified?";
- }
-
- if (path.equalsIgnoreCase(":memory:")) {
- return "memorydb";
- }
- String[] tokens = path.split("/");
- String[] lastNodeTokens = tokens[tokens.length - 1].split("\\.", 2);
- return (lastNodeTokens.length == 1) ? lastNodeTokens[0]
- : lastNodeTokens[0] + lastNodeTokens[1];
- }
-
/**
* return whether the DB is opened as read only.
* @return true if DB is opened as read only
@@ -1814,17 +1800,22 @@ public class SQLiteDatabase extends SQLiteClosable {
- /* package */ void logTimeStat(String sql, long beginNanos) {
+ /* package */ void logTimeStat(String sql, long beginMillis) {
+ // 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 nanos = Debug.threadCpuTimeNanos() - beginNanos;
- if (nanos >= QUERY_LOG_TIME_IN_NANOS) {
+ long durationMillis = SystemClock.uptimeMillis() - beginMillis;
+ if (durationMillis >= QUERY_LOG_TIME_IN_MILLIS) {
samplePercent = 100;
} else {
- samplePercent = (int) (100 * nanos / QUERY_LOG_TIME_IN_NANOS) + 1;
+ samplePercent = (int) (100 * durationMillis / QUERY_LOG_TIME_IN_MILLIS) + 1;
if (mRandom.nextInt(100) >= samplePercent) return;
}
@@ -1841,8 +1832,8 @@ public class SQLiteDatabase extends SQLiteClosable {
String blockingPackage = ActivityThread.currentPackageName();
if (blockingPackage == null) blockingPackage = "";
- int millis = (int) (nanos / 1000000);
- EventLog.writeEvent(EVENT_DB_OPERATION, mPath, sql, millis, blockingPackage, samplePercent);
+ EventLog.writeEvent(
+ EVENT_DB_OPERATION, mPath, sql, durationMillis, blockingPackage, samplePercent);
}
/**
@@ -1909,8 +1900,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
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 + ". Make sure your sql " +
- "statements are using prepared-sql-statement syntax with '?' for " +
+ sql + ". Please change your sql statements to use '?' for " +
"bindargs, instead of using actual values");
/* increment the number of times this warnings has been printed.
@@ -1922,8 +1912,9 @@ public class SQLiteDatabase extends SQLiteClosable {
mCacheFullWarnings = 0;
// clear the cache
mCompiledQueries.clear();
- Log.w(TAG, "compiled-sql statement cache cleared for the database " +
- getPath());
+ Log.w(TAG, "Compiled-sql statement cache for database: " +
+ getPath() + " hit MAX size-limit too many times. " +
+ "Removing all compiled-sql statements from the cache.");
} else {
// clear just a single entry from cache
Set<String> keySet = mCompiledQueries.keySet();
@@ -2061,6 +2052,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.
*
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index b12034a..4ea680e 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -31,17 +31,17 @@ public final class SQLiteDebug {
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
- * Controls the printing of compiled-sql-statement cache stats.
+ * Controls the printing of wall-clock time taken to execute SQL statements
+ * as they are executed.
*/
- public static final boolean DEBUG_SQL_CACHE =
- Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
+ public static final boolean DEBUG_SQL_TIME =
+ Log.isLoggable("SQLiteTime", Log.VERBOSE);
/**
- * Controls the capturing and printing of complete sql statement including the bind args and
- * the database name.
+ * Controls the printing of compiled-sql-statement cache stats.
*/
- public static final boolean DEBUG_CAPTURE_SQL =
- Log.isLoggable("SQLiteCaptureSql", Log.VERBOSE);
+ public static final boolean DEBUG_SQL_CACHE =
+ Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
/**
* Controls the stack trace reporting of active cursors being
@@ -121,62 +121,4 @@ public final class SQLiteDebug {
static synchronized void notifyActiveCursorFinalized() {
sNumActiveCursorsFinalized++;
}
-
- /**
- * returns a message containing the given database name (path) and the string built by
- * replacing "?" characters in the given sql string with the corresponding
- * positional values from the given param bindArgs.
- *
- * @param path the database name
- * @param sql sql string with possibly "?" for bindargs
- * @param bindArgs args for "?"s in the above string
- * @return the String to be logged
- */
- /* package */ static String captureSql(String path, String sql, Object[] bindArgs) {
- // how many bindargs in sql
- sql = sql.trim();
- String args[] = sql.split("\\?");
- // how many "?"s in the given sql string?
- int varArgsInSql = (sql.endsWith("?")) ? args.length : args.length - 1;
-
- // how many bind args do we have in the given input param bindArgs
- int bindArgsLen = (bindArgs == null) ? 0 : bindArgs.length;
- if (varArgsInSql < bindArgsLen) {
- return "too many bindArgs provided. " +
- "# of bindArgs = " + bindArgsLen + ", # of varargs = " + varArgsInSql +
- "; sql = " + sql;
- }
-
- // if there are no bindArgs, we are done. log the sql as is.
- if (bindArgsLen == 0 && varArgsInSql == 0) {
- return logSql(path, sql);
- }
-
- StringBuilder buf = new StringBuilder();
-
- // take the supplied bindArgs and plug them into sql
- for (int i = 0; i < bindArgsLen; i++) {
- buf.append(args[i]);
- buf.append(bindArgs[i]);
- }
-
- // does given sql have more varArgs than the supplied bindArgs
- // if so, assign nulls to the extra varArgs in sql
- for (int i = bindArgsLen; i < varArgsInSql; i ++) {
- buf.append(args[i]);
- buf.append("null");
- }
-
- // if there are any characters left in the given sql string AFTER the last "?"
- // log them also. for example, if the given sql = "select * from test where a=? and b=1
- // then the following code appends " and b=1" string to buf.
- if (varArgsInSql < args.length) {
- buf.append(args[varArgsInSql]);
- }
- return logSql(path, buf.toString());
- }
-
- private static String logSql(String path, String sql) {
- return "captured_sql|" + path + "|" + sql + ";";
- }
}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 1159c1d..2bb2f5d 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,19 +16,10 @@
package android.database.sqlite;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-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. */
protected SQLiteDatabase mDatabase;
@@ -53,16 +44,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
protected int nStatement = 0;
- /**
- * stores all bindargs for debugging purposes
- */
- private Map<Integer, String> mBindArgs = null;
-
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.d(TAG, "processing sql: " + sql);
- }
-
mDatabase = db;
mSql = sql;
db.acquireReference();
@@ -76,32 +58,51 @@ public abstract class SQLiteProgram extends SQLiteClosable {
// add it to the cache of compiled-sqls
db.addToCompiledQueries(sql, mCompiledSql);
+ mCompiledSql.acquire();
+ } else {
+ // it is already in compiled-sql cache.
+ if (mCompiledSql.isInUse()) {
+ // but the CompiledSql 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);
+ } else {
+ // the CompiledSql in cache is NOT in use by any other SQLiteProgram object.
+ // it is safe to give it to this SQLIteProgram Object.
+ mCompiledSql.acquire();
+ }
}
nStatement = mCompiledSql.nStatement;
}
@Override
protected void onAllReferencesReleased() {
- releaseCompiledSqlIfInCache();
+ releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
@Override
protected void onAllReferencesReleasedFromContainer() {
- releaseCompiledSqlIfInCache();
+ releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
}
- private void releaseCompiledSqlIfInCache() {
+ 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
+ // release this statement is on me.
mCompiledSql.releaseSqlStatement();
mCompiledSql = null; // so that GC doesn't call finalize() on it
nStatement = 0;
+ } else {
+ // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
+ mCompiledSql.release();
}
}
}
@@ -120,7 +121,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
}
/**
- * @deprecated use this.compiledStatement.compile instead
+ * @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
@@ -138,9 +139,6 @@ 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 (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "null");
- }
acquireReference();
try {
native_bind_null(index);
@@ -157,9 +155,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindLong(int index, long value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, value + "");
- }
acquireReference();
try {
native_bind_long(index, value);
@@ -176,9 +171,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, value + "");
- }
acquireReference();
try {
native_bind_double(index, value);
@@ -195,9 +187,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindString(int index, String value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "'" + value + "'");
- }
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
@@ -217,9 +206,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindBlob(int index, byte[] value) {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- addToBindArgs(index, "blob");
- }
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
@@ -235,9 +221,6 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- mBindArgs = null;
- }
acquireReference();
try {
native_clear_bindings();
@@ -259,39 +242,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
}
/**
- * this method is called under the debug flag {@link SQLiteDebug.DEBUG_CAPTURE_SQL} only.
- * it collects the bindargs as they are called by the callers the bind... methods in this
- * class.
- */
- private void addToBindArgs(int index, String obj) {
- if (mBindArgs == null) {
- mBindArgs = new HashMap<Integer, String>();
- }
- mBindArgs.put(index, obj);
- }
-
- /**
- * constructs all the bindargs in sequence and returns a String Array of the values.
- * it uses the HashMap built up by the above method.
- *
- * @return the string array of bindArgs with the args arranged in sequence
- */
- /* package */ String[] getBindArgs() {
- if (mBindArgs == null) {
- return null;
- }
- Set<Integer> indexSet = mBindArgs.keySet();
- ArrayList<Integer> indexList = new ArrayList<Integer>(indexSet);
- Collections.sort(indexList);
- int len = indexList.size();
- String[] bindObjs = new String[len];
- for (int i = 0; i < len; i++) {
- bindObjs[i] = mBindArgs.get(indexList.get(i));
- }
- return bindObjs;
- }
-
- /**
+ * @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.
@@ -299,6 +250,10 @@ public abstract class SQLiteProgram extends SQLiteClosable {
*/
@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();
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index c34661d..7cd9561 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -17,7 +17,6 @@
package android.database.sqlite;
import android.database.CursorWindow;
-import android.os.Debug;
import android.os.SystemClock;
import android.util.Log;
@@ -58,7 +57,7 @@ public class SQLiteQuery extends SQLiteProgram {
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
try {
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 0cee3c5..f29b69f 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,8 +16,7 @@
package android.database.sqlite;
-import android.os.Debug;
-import android.util.Log;
+import android.os.SystemClock;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
@@ -27,8 +26,6 @@ import android.util.Log;
*/
public class SQLiteStatement extends SQLiteProgram
{
- private static final String TAG = "SQLiteStatement";
-
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -47,17 +44,11 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public void execute() {
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "execute() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
} finally {
@@ -77,17 +68,11 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public long executeInsert() {
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "executeInsert() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
return mDatabase.lastInsertRow();
@@ -106,17 +91,11 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
long retValue = native_1x1_long();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
@@ -135,17 +114,11 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
- long timeStart = Debug.threadCpuTimeNanos();
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
- }
- if (SQLiteDebug.DEBUG_CAPTURE_SQL) {
- Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs()));
- }
String retValue = native_1x1_string();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
diff --git a/core/java/android/gesture/Learner.java b/core/java/android/gesture/Learner.java
index 60997e0..a105652 100755
--- a/core/java/android/gesture/Learner.java
+++ b/core/java/android/gesture/Learner.java
@@ -72,7 +72,8 @@ 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);
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 47c2cac..6dba94d 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;
@@ -192,7 +192,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
@@ -276,7 +276,7 @@ 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)}
+ * {@link android.graphics.ImageFormat#getBitsPerPixel(int)}
*
* Alternatively, a buffer from a previous callback may be passed in or used
* to determine the size of new preview frame buffers.
@@ -723,6 +723,7 @@ public class Camera {
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";
// Parameter key suffix for supported values.
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
@@ -1051,7 +1052,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)
*/
@@ -1060,8 +1062,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)
*/
@@ -1083,15 +1086,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);
@@ -1107,7 +1110,7 @@ public class Camera {
* Returns the image format for preview pictures got from
* {@link PreviewCallback}.
*
- * @return the {@link android.graphics.PixelFormat} int representing
+ * @return the {@link android.graphics.ImageFormat} int representing
* the preview picture format.
*/
public int getPreviewFormat() {
@@ -1125,7 +1128,7 @@ public class Camera {
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;
@@ -1168,10 +1171,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);
@@ -1186,7 +1189,7 @@ public class Camera {
/**
* Returns the image format for pictures.
*
- * @return the PixelFormat int representing the picture format
+ * @return the ImageFormat int representing the picture format
*/
public int getPictureFormat() {
return pixelFormatForCameraFormat(get(KEY_PICTURE_FORMAT));
@@ -1195,7 +1198,7 @@ public class Camera {
/**
* Gets the supported picture formats.
*
- * @return a List of Integer objects (values are PixelFormat.XXX). This
+ * @return a List of Integer objects (values are ImageFormat.XXX). This
* method will always return a list with at least one element.
*/
public List<Integer> getSupportedPictureFormats() {
@@ -1203,7 +1206,7 @@ public class Camera {
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;
@@ -1211,35 +1214,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;
}
/**
@@ -1540,6 +1543,41 @@ public class Camera {
}
/**
+ * Gets the current exposure compensation setting.
+ *
+ * @return the current exposure compensation value multiplied by 100.
+ * null if exposure compensation is not supported. Ex: -100
+ * means -1 EV. 130 means +1.3 EV.
+ * @hide
+ */
+ public int getExposureCompensation() {
+ return getInt(KEY_EXPOSURE_COMPENSATION);
+ }
+
+ /**
+ * Sets the exposure compensation.
+ *
+ * @param value exposure compensation multiplied by 100. Ex: -100 means
+ * -1 EV. 130 means +1.3 EV.
+ * @hide
+ */
+ public void setExposureCompensation(int value) {
+ set(KEY_EXPOSURE_COMPENSATION, value);
+ }
+
+ /**
+ * Gets the supported exposure compensation.
+ *
+ * @return a List of Integer constants. null if exposure compensation is
+ * not supported. The list is sorted from small to large. Ex:
+ * -100, -66, -33, 0, 33, 66, 100.
+ * @hide
+ */
+ public List<Integer> getSupportedExposureCompensation() {
+ return splitInt(get(KEY_EXPOSURE_COMPENSATION + SUPPORTED_VALUES_SUFFIX));
+ }
+
+ /**
* Gets current zoom value. This also works when smooth zoom is in
* progress.
*
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index b0c3909..9201e3b 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;
@@ -620,7 +621,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 +743,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 +775,6 @@ public class KeyboardView extends View implements View.OnClickListener {
}
}
}
-
- if (isInside) {
- primaryIndex = nearestKeyIndices[i];
- }
}
if (primaryIndex == NOT_A_KEY) {
primaryIndex = closestKey;
@@ -1119,7 +1123,8 @@ public class KeyboardView extends View implements View.OnClickListener {
mSwipeTracker.addMovement(me);
// Ignore all motion events until a DOWN.
- if (mAbortKey && action != MotionEvent.ACTION_DOWN) {
+ if (mAbortKey
+ && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
return true;
}
@@ -1203,6 +1208,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
}
showPreview(mCurrentKey);
+ mLastMoveTime = eventTime;
break;
case MotionEvent.ACTION_UP:
@@ -1216,7 +1222,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;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d435df5..badb767 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -125,13 +125,21 @@ public class ConnectivityManager
/**
* @hide
+ * gives a String[]
*/
- public static final String EXTRA_AVAILABLE_TETHER_COUNT = "availableCount";
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
* @hide
+ * gives a String[]
*/
- public static final String EXTRA_ACTIVE_TETHER_COUNT = "activeCount";
+ 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
@@ -400,4 +408,37 @@ public class ConnectivityManager
return false;
}
}
+
+ /**
+ * {@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];
+ }
+ }
}
diff --git a/core/java/android/net/Downloads.java b/core/java/android/net/Downloads.java
new file mode 100644
index 0000000..72106c8
--- /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
+ *
+ *
+ */
+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
+ */
+ private 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 caa3f2b..508e9c3 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -55,7 +55,13 @@ interface IConnectivityManager
boolean untether(String iface);
+ boolean isTetheringSupported();
+
String[] getTetherableIfaces();
String[] getTetheredIfaces();
+
+ String[] getTetherableUsbRegexs();
+
+ String[] getTetherableWifiRegexs();
}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index e40f1b8..f959fee 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -40,224 +40,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 mHandshakeTimeoutMillis;
+ private final SSLClientSessionCache mSessionCache;
+ private final boolean mSecure;
- private final int mSocketReadTimeoutForSslHandshake;
+ /** @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 || ("0".equals(SystemProperties.get("ro.secure")) &&
+ "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");
- }
- 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!");
+ mInsecureFactory = makeSocketFactory(INSECURE_TRUST_MANAGER);
}
- throw new IOException("Certificate untrusted");
- }
-
- X509Certificate lastChainCert = (X509Certificate) certs[0];
-
- if (!DomainNameValidator.match(lastChainCert, destHost)) {
- if (Config.LOGD) {
- Log.d(LOG_TAG,"validateSocket(): domain name check failed");
+ return mInsecureFactory;
+ } else {
+ if (mSecureFactory == null) {
+ mSecureFactory = makeSocketFactory(null);
}
- 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/WebAddress.java b/core/java/android/net/WebAddress.java
index f4ae66a..fa13894 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -16,6 +16,8 @@
package android.net;
+import static com.android.common.Patterns.GOOD_IRI_CHAR;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,9 +56,10 @@ public class WebAddress {
static Pattern sAddressPattern = Pattern.compile(
/* scheme */ "(?:(http|HTTP|https|HTTPS|file|FILE)\\:\\/\\/)?" +
/* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
- /* host */ "([-A-Za-z0-9%_]+(?:\\.[-A-Za-z0-9%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
+ /* host */ "([-" + GOOD_IRI_CHAR + "%_]+(?:\\.[-" + GOOD_IRI_CHAR + "%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
/* port */ "(?:\\:([0-9]+))?" +
- /* path */ "(\\/?.*)?");
+ /* path */ "(\\/?[^#]*)?" +
+ /* anchor */ ".*");
/** parses given uriString. */
public WebAddress(String address) throws ParseException {
diff --git a/core/java/android/os/Base64Utils.java b/core/java/android/os/Base64Utils.java
deleted file mode 100644
index 684a469..0000000
--- a/core/java/android/os/Base64Utils.java
+++ /dev/null
@@ -1,31 +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;
-
-/**
- * {@hide}
- */
-public class Base64Utils
-{
- // TODO add encode api here if possible
-
- public static byte [] decodeBase64(String data) {
- return decodeBase64Native(data);
- }
- private static native byte[] decodeBase64Native(String data);
-}
-
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 9491bd4..a9831aa 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,7 +18,7 @@ package android.os;
import java.io.File;
-import android.os.IMountService;
+import android.os.storage.IMountService;
/**
* Provides access to environment variables.
@@ -91,6 +91,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");
@@ -102,13 +110,183 @@ 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>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.
+ */
+ public static String DIRECTORY_DOWNLOADS = "Downloads";
+
+ /**
+ * 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() {
@@ -173,6 +351,8 @@ 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() {
try {
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index 3457815..7e99f38 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -52,73 +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.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, 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
@@ -127,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/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/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
deleted file mode 100644
index e5609b0..0000000
--- a/core/java/android/os/ICheckinService.aidl
+++ /dev/null
@@ -1,41 +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 {
- /** Reboot into the recovery system and wipe all user data. */
- void masterClear();
-
- /** Reboot into the recovery system, wipe all user data and enable Encrypted File Systems. */
- void masterClearAndToggleEFS(boolean efsEnabled);
-
- /**
- * 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/IMountServiceListener.aidl b/core/java/android/os/IMountServiceListener.aidl
deleted file mode 100644
index 3df64b2..0000000
--- a/core/java/android/os/IMountServiceListener.aidl
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * Callback class for receiving events from MountService.
- *
- * @hide
- */
-interface IMountServiceListener {
- /**
- * A sharing method has changed availability state.
- *
- * @param method The share method which has changed.
- * @param available The share availability state.
- */
- void onShareAvailabilityChanged(String method, boolean available);
-
- /**
- * Media has been inserted
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- */
- void onMediaInserted(String label, String path, int major, int minor);
-
- /**
- * Media has been removed
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- * @param clean Indicates if the removal was clean (unmounted first).
- */
- void onMediaRemoved(String label, String path, int major, int minor, boolean clean);
-
- /**
- * Volume state has changed.
- *
- * @param label The volume label.
- * @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 onVolumeStateChanged(String label, String path, String oldState, String newState);
-
-}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f48f45f..92041d8 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -148,4 +148,18 @@ interface INetworkManagementService
*/
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();
}
diff --git a/core/java/android/os/IParentalControlCallback.aidl b/core/java/android/os/IParentalControlCallback.aidl
deleted file mode 100644
index 2f1a563..0000000
--- a/core/java/android/os/IParentalControlCallback.aidl
+++ /dev/null
@@ -1,27 +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.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);
-}
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/MountServiceListener.java b/core/java/android/os/MountServiceListener.java
deleted file mode 100644
index a68f464..0000000
--- a/core/java/android/os/MountServiceListener.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * 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 {
- /**
- * A sharing method has changed availability state.
- *
- * @param method The share method which has changed.
- * @param available The share availability state.
- */
- void shareAvailabilityChange(String method, boolean available) {
- }
-
- /**
- * Media has been inserted
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- */
- void mediaInserted(String label, String path, int major, int minor) {
- }
-
- /**
- * Media has been removed
- *
- * @param label The volume label.
- * @param path The volume mount path.
- * @param major The backing device major number.
- * @param minor The backing device minor number.
- * @param clean Indicates if the removal was clean (unmounted first).
- */
- void mediaRemoved(String label, String path, int major, int minor, boolean clean) {
- }
-
- /**
- * Volume state has changed.
- *
- * @param label The volume label.
- * @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 volumeStateChange(String label, String path, String oldState, String newState) {
- }
-}
diff --git a/core/java/android/os/MountServiceResultCode.java b/core/java/android/os/MountServiceResultCode.java
deleted file mode 100644
index e71dbf4..0000000
--- a/core/java/android/os/MountServiceResultCode.java
+++ /dev/null
@@ -1,34 +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 java.io.IOException;
-
-/**
- * Class that provides access to constants returned from MountService APIs
- *
- * {@hide}
- */
-public class MountServiceResultCode
-{
- public static final int OperationSucceeded = 0;
- public static final int OperationFailedInternalError = -1;
- public static final int OperationFailedNoMedia = -2;
- public static final int OperationFailedMediaBlank = -3;
- public static final int OperationFailedMediaCorrupt = -4;
- public static final int OperationFailedVolumeNotMounted = -5;
-}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index bc76180..b3df522 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -18,7 +18,7 @@ package android.os;
import java.io.IOException;
import android.os.ServiceManager;
-import android.os.IMountService;
+import android.os.storage.IMountService;
/**
* Class that provides access to some of the power management functions.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 699ddb2..4887783 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");
}
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
index a5828f6..79a6cfe 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -15,14 +15,15 @@
** limitations under the License.
*/
-package android.os;
+package android.os.storage;
-import android.os.IMountServiceListener;
+import android.os.storage.IMountServiceListener;
/** 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
+ * @hide - Applications should use android.os.storage.StorageManager to access
+ * storage functions.
*/
interface IMountService
{
@@ -38,31 +39,19 @@ interface IMountService
void unregisterListener(IMountServiceListener listener);
/**
- * Gets an Array of supported share methods
+ * Returns true if a USB mass storage host is connected
*/
- String[] getShareMethodList();
+ boolean isUsbMassStorageConnected();
/**
- * Returns true if the share method is available
+ * Enables / disables USB mass storage.
*/
- boolean getShareMethodAvailable(String method);
+ int setUsbMassStorageEnabled(boolean enable);
/**
- * Shares a volume via the specified method
- * Returns an int consistent with MountServiceResultCode
- */
- int shareVolume(String path, String method);
-
- /**
- * Unshares a volume via the specified method
- * Returns an int consistent with MountServiceResultCode
- */
- int unshareVolume(String path, String method);
-
- /**
- * Returns true if the volume is shared via the specified method.
+ * Returns true if a USB mass storage host is enabled (media is shared)
*/
- boolean getVolumeShared(String path, String method);
+ boolean isUsbMassStorageEnabled();
/**
* Mount external storage at given mount point.
@@ -74,7 +63,7 @@ interface IMountService
* Safely unmount external storage at given mount point.
* Returns an int consistent with MountServiceResultCode
*/
- int unmountVolume(String mountPoint);
+ int unmountVolume(String mountPoint, boolean force);
/**
* Format external storage given a mount point.
@@ -83,6 +72,12 @@ interface IMountService
int formatVolume(String mountPoint);
/**
+ * Returns an array of pids with open files on
+ * the specified path.
+ */
+ int[] getStorageUsers(String path);
+
+ /**
* Gets the state of an volume via it's mountpoint.
*/
String getVolumeState(String mountPoint);
@@ -105,7 +100,7 @@ interface IMountService
* NOTE: Ensure all references are released prior to deleting.
* Returns an int consistent with MountServiceResultCode
*/
- int destroySecureContainer(String id);
+ int destroySecureContainer(String id, boolean force);
/*
* Mount a secure container with the specified key and owner UID.
@@ -117,7 +112,12 @@ interface IMountService
* Unount a secure container.
* Returns an int consistent with MountServiceResultCode
*/
- int unmountSecureContainer(String id);
+ int unmountSecureContainer(String id, boolean force);
+
+ /*
+ * Returns true if the specified container is mounted
+ */
+ boolean isSecureContainerMounted(String id);
/*
* Rename an unmounted secure container.
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/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..d3d39d6
--- /dev/null
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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
+ */
+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..e421ea5
--- /dev/null
+++ b/core/java/android/os/storage/StorageManager.java
@@ -0,0 +1,297 @@
+/*
+ * 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}.
+ *
+ */
+
+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.
+ * @return an integer value representing the outcome of the operation.
+ * @see android.os.storage.StorageResultCode
+ */
+ public int enableUsbMassStorage() {
+ try {
+ return mMountService.setUsbMassStorageEnabled(true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to enable UMS", ex);
+ }
+ return StorageResultCode.OperationFailedInternalError;
+ }
+
+ /**
+ * Disables USB Mass Storage (UMS) on the device.
+ * @return an integer value representing the outcome of the operation.
+ * @see android.os.storage.StorageResultCode
+ */
+ public int disableUsbMassStorage() {
+ try {
+ return mMountService.setUsbMassStorageEnabled(false);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to disable UMS", ex);
+ }
+ return StorageResultCode.OperationFailedInternalError;
+ }
+
+ /**
+ * 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..07d95df
--- /dev/null
+++ b/core/java/android/os/storage/StorageResultCode.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.os.storage;
+
+/**
+ * Class that provides access to constants returned from StorageManager
+ * and lower level MountService APIs.
+ */
+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/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index bd7924a..5d09fb5 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -48,7 +48,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 +66,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 +80,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)) {
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 389c9f4..2eb25954 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -25,11 +25,13 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.RemoteException;
+import android.pim.vcard.exception.VCardException;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
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;
@@ -54,6 +56,7 @@ 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;
@@ -199,6 +202,10 @@ public class VCardComposer {
try {
// Create one empty entry.
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: "
@@ -455,6 +462,9 @@ public class VCardComposer {
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.
@@ -486,36 +496,42 @@ public class VCardComposer {
}
private String createOneEntryInternal(final String contactId,
- Method getEntityIteratorMethod) {
+ Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
- // 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 {
-
+ 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 {
- final Uri uri = RawContacts.CONTENT_URI.buildUpon()
- .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
- .build();
- final String selection = Data.CONTACT_ID + "=?";
- final String[] selectionArgs = new String[] {contactId};
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, null);
- } catch (Exception e) {
- e.printStackTrace();
+ } 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 {
- final Uri uri = RawContacts.CONTENT_URI.buildUpon()
- .appendEncodedPath(contactId)
- .appendEncodedPath(RawContacts.Entity.CONTENT_DIRECTORY)
- .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
- .build();
entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
- uri, null, null, null, null));
+ uri, null, selection, selectionArgs, null));
}
if (entityIterator == null) {
@@ -523,7 +539,11 @@ public class VCardComposer {
return "";
}
- dataExists = entityIterator.hasNext();
+ 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()) {
@@ -550,10 +570,6 @@ public class VCardComposer {
}
}
- if (!dataExists) {
- return "";
- }
-
final VCardBuilder builder = new VCardBuilder(mVCardType);
builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 20eee84..1cf3144 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -281,6 +281,29 @@ public class VCardEntry {
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(
@@ -1008,6 +1031,8 @@ public class VCardEntry {
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) {
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 36255d0..f7c3148 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -120,6 +120,15 @@ public class Browser {
private static final int MAX_HISTORY_COUNT = 250;
/**
+ * URI for writing geolocation permissions. This requires the
+ * {@link android.Manifest.permission#WRITE_GEOLOCATION_PERMISSIONS}.
+ */
+ public static final Uri GEOLOCATION_URI =
+ Uri.parse("content://browser/geolocation");
+
+ private static final String GEOLOCATION_WHERE_CLAUSE = GeolocationColumns.ORIGIN + " = ?";
+
+ /**
* Open the AddBookmark activity to save a bookmark. Launch with
* and/or url, which can be edited by the user before saving.
* @param c Context used to launch the AddBookmark activity.
@@ -553,6 +562,42 @@ public class Browser {
}
}
+ /**
+ * Allows geolocation for the specified origin.
+ * This requires the {@link android.Manifest.permission#WRITE_GEOLOCATION_PERMISSIONS}
+ * permission.
+ *
+ * @param origin The origin to allow geolocation for, e.g. "http://www.google.com". The string
+ * should not include a trailing slash.
+ */
+ public static void allowGeolocation(ContentResolver cr, String origin) {
+ try {
+ ContentValues map = new ContentValues();
+ map.put(GeolocationColumns.ORIGIN, origin);
+ cr.insert(GEOLOCATION_URI, map);
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "allowGeolocation", e);
+ return;
+ }
+ }
+
+ /**
+ * Clears the geolocation permission state for the specified origin.
+ * This requires the {@link android.Manifest.permission#WRITE_GEOLOCATION_PERMISSIONS}
+ * permission.
+ *
+ * @param origin The origin to allow geolocation for, e.g. "http://www.google.com". The string
+ * should not include a trailing slash.
+ */
+ public static void clearGeolocation(ContentResolver cr, String origin) {
+ try {
+ String[] whereArgs = { origin };
+ cr.delete(GEOLOCATION_URI, GEOLOCATION_WHERE_CLAUSE, whereArgs);
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "clearGeolocation", e);
+ }
+ }
+
public static class BookmarkColumns implements BaseColumns {
public static final String URL = "url";
public static final String VISITS = "visits";
@@ -580,4 +625,8 @@ public class Browser {
public static final String SEARCH = "search";
public static final String DATE = "date";
}
+
+ public static class GeolocationColumns {
+ public static final String ORIGIN = "origin";
+ }
}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f89ba91..cb42d73 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -184,6 +184,20 @@ public final class Calendar {
* <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";
}
/**
@@ -579,20 +593,6 @@ public final class Calendar {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
"/event_entities");
- /**
- * 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 = "_sync_account";
-
- /**
- * 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 = "_sync_account_type";
-
public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
return new EntityIteratorImpl(cursor, resolver);
}
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
deleted file mode 100644
index 75936a1..0000000
--- a/core/java/android/provider/Checkin.java
+++ /dev/null
@@ -1,269 +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.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,
- 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;
-}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 544e399..acb8473 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -608,6 +608,45 @@ public final class ContactsContract {
}
/**
+ * 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>
@@ -5695,5 +5734,4 @@ public final class ContactsContract {
public static final String IM_ISPRIMARY = "im_isprimary";
}
}
-
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 211bc0a..74a03da 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -383,10 +383,10 @@ public final class MediaStore {
if (isVideo) {
bitmap = ThumbnailUtils.createVideoThumbnail(filePath);
if (kind == MICRO_KIND && bitmap != null) {
- bitmap = ThumbnailUtils.extractMiniThumb(bitmap,
- ThumbnailUtils.MINI_THUMB_TARGET_SIZE,
- ThumbnailUtils.MINI_THUMB_TARGET_SIZE,
- ThumbnailUtils.RECYCLE_INPUT);
+ bitmap = ThumbnailUtils.extractThumbnail(bitmap,
+ ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL,
+ ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL,
+ ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
}
} else {
bitmap = ThumbnailUtils.createImageThumbnail(cr, filePath, uri, origId,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bacaf43..14e27eb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -22,6 +22,7 @@ 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;
@@ -1473,6 +1474,89 @@ public final class Settings {
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
/**
+ * 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";
+
+ /**
+ * 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
@@ -2126,14 +2210,23 @@ public final class Settings {
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";
@@ -2799,22 +2892,6 @@ public final class Settings {
"sms_outgoing_check_max_count";
/**
- * 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
- * @hide
- */
- public static final String SSL_SESSION_CACHE = "ssl_session_cache";
-
- /**
- * How many bytes long a message has to be, in order to be gzipped.
- * @hide
- */
- public static final String SYNC_MIN_GZIP_BYTES =
- "sync_min_gzip_bytes";
-
- /**
* The number of promoted sources in GlobalSearch.
* @hide
*/
@@ -2956,6 +3033,14 @@ public final class Settings {
* @hide
*/
public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
+
+ /**
+ * 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";
/**
* @hide
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 22bb43c..096ad39 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>();
@@ -551,4 +586,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/BluetoothService.java b/core/java/android/server/BluetoothService.java
index dfb775f..aa20ac4 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -136,6 +136,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 {
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 324fbaa..fbc4a81 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -75,8 +75,8 @@ public class SearchManagerService extends ISearchManager.Stub {
mContext.registerReceiver(mPackageChangedReceiver, packageFilter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mPackageChangedReceiver, sdFilter);
}
@@ -96,8 +96,8 @@ public class SearchManagerService extends ISearchManager.Stub {
if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
- Intent.ACTION_MEDIA_RESOURCES_AVAILABLE.equals(action) ||
- Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE.equals(action)) {
+ Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) ||
+ Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
if (DBG) Log.d(TAG, "Got " + action);
// Update list of searchable activities
getSearchables().buildSearchableList();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index eb48a0c..52de64c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -28,6 +28,7 @@ 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;
@@ -226,7 +227,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);
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index 5434887..2f5bcc3 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -79,7 +79,8 @@ public interface RecognitionListener {
* 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 RecognitionManager#startListening(Intent)}, depending on the speech recognition
- * service implementation.
+ * 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
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
index 0d25b2f..16b1f89 100644
--- a/core/java/android/speech/RecognitionManager.java
+++ b/core/java/android/speech/RecognitionManager.java
@@ -27,6 +27,8 @@ 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;
@@ -96,6 +98,9 @@ public class RecognitionManager {
/** 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() {
@@ -131,8 +136,9 @@ public class RecognitionManager {
* The right way to create a {@code RecognitionManager} is by using
* {@link #createRecognitionManager} static factory method
*/
- private RecognitionManager(final Context context) {
+ private RecognitionManager(final Context context, final ComponentName serviceComponent) {
mContext = context;
+ mServiceComponent = serviceComponent;
}
/**
@@ -169,7 +175,7 @@ public class RecognitionManager {
*/
public static boolean isRecognitionAvailable(final Context context) {
final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
- new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
+ new Intent(RecognitionService.SERVICE_INTERFACE), 0);
return list != null && list.size() != 0;
}
@@ -182,11 +188,31 @@ public class RecognitionManager {
* @return a new {@code RecognitionManager}
*/
public static RecognitionManager createRecognitionManager(final Context context) {
+ return createRecognitionManager(context, null);
+ }
+
+ /**
+ * Factory method to create a new {@code RecognitionManager}, please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called before dispatching any
+ * command to the created {@code RecognitionManager}.
+ *
+ * Use this version of the method to specify a specific service to direct this
+ * {@link RecognitionManager} to. Normally you would not use this; use
+ * {@link #createRecognitionManager(Context)} instead to use the system default
+ * recognition service.
+ *
+ * @param context in which to create {@code RecognitionManager}
+ * @param serviceComponent the {@link ComponentName} of a specific service to direct this
+ * {@code RecognitionManager} to
+ * @return a new {@code RecognitionManager}
+ */
+ public static RecognitionManager createRecognitionManager(final Context context,
+ final ComponentName serviceComponent) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null)");
}
checkIsCalledFromMainThread();
- return new RecognitionManager(context);
+ return new RecognitionManager(context, serviceComponent);
}
/**
@@ -216,11 +242,27 @@ public class RecognitionManager {
throw new IllegalArgumentException("intent must not be null");
}
checkIsCalledFromMainThread();
- checkIsCommandAllowed();
if (mConnection == null) { // first time connection
mConnection = new Connection();
- if (!mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
- mConnection, Context.BIND_AUTO_CREATE)) {
+
+ 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;
@@ -243,7 +285,6 @@ public class RecognitionManager {
*/
public void stopListening() {
checkIsCalledFromMainThread();
- checkIsCommandAllowed();
putMessage(Message.obtain(mHandler, MSG_STOP));
}
@@ -254,7 +295,6 @@ public class RecognitionManager {
*/
public void cancel() {
checkIsCalledFromMainThread();
- checkIsCommandAllowed();
putMessage(Message.obtain(mHandler, MSG_CANCEL));
}
@@ -265,12 +305,6 @@ public class RecognitionManager {
}
}
- private void checkIsCommandAllowed() {
- if (mService == null && mPendingTasks.isEmpty()) { // setListener message must be there
- throw new IllegalStateException("Listener must be set before any command is called");
- }
- }
-
private void putMessage(Message msg) {
if (mService == null) {
mPendingTasks.offer(msg);
@@ -281,6 +315,9 @@ public class RecognitionManager {
/** 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");
@@ -292,6 +329,9 @@ public class RecognitionManager {
/** 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");
@@ -303,6 +343,9 @@ public class RecognitionManager {
/** 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");
@@ -311,6 +354,15 @@ public class RecognitionManager {
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) {
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 0d74960..941b70c 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -16,6 +16,8 @@
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;
@@ -28,10 +30,22 @@ 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 not that the
- * implementation of this service is state-less.
+ * 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";
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 334b049..7c15cec 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,9 +16,17 @@
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}
@@ -77,6 +85,7 @@ 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 (returned in the result, not to be specified in the request):
@@ -166,6 +175,13 @@ public class RecognizerIntent {
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
* extra to supply a PendingIntent, the results will be added to its bundle and the
@@ -202,8 +218,90 @@ public class RecognizerIntent {
public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
/**
- * Triggers the voice search settings activity.
+ * 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";
+
+ /**
+ * 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 ACTION_VOICE_SEARCH_SETTINGS =
- "android.speech.action.VOICE_SEARCH_SETTINGS";
+ public static final String EXTRA_SUPPORTED_LANGUAGES =
+ "android.speech.extra.SUPPORTED_LANGUAGES";
}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index bbbeb3f..0db2198 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -256,6 +256,31 @@ 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").
+ * {@hide}
+ */
+ 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").
+ * {@hide}
+ */
+ 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").
+ * {@hide}
+ */
+ public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
// extras for a TTS engine's data installation
/**
diff --git a/core/java/android/storage/StorageEventListener.java b/core/java/android/storage/StorageEventListener.java
deleted file mode 100644
index cd71090..0000000
--- a/core/java/android/storage/StorageEventListener.java
+++ /dev/null
@@ -1,57 +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.storage;
-
-/**
- * Used for receiving notifications from the StorageManager
- */
-public interface StorageEventListener {
- /**
- * Called when the ability to share a volume has changed.
- * @param method the share-method which has changed.
- * @param available true if the share is available.
- */
- public void onShareAvailabilityChanged(String method, boolean available);
-
- /**
- * Called when media has been inserted
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @param major the major number of the device.
- * @param minor the minor number of the device.
- */
- public void onMediaInserted(String label, String path, int major, int minor);
-
- /**
- * Called when media has been removed
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @param major the major number of the device.
- * @param minor the minor number of the device.
- * @param clean the media was removed cleanly.
- */
- public void onMediaRemoved(String label, String path, int major, int minor, boolean clean);
-
- /**
- * Called when a volume has changed state
- * @param label the system defined label for the volume.
- * @param path the filesystem path for the volume.
- * @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 onVolumeStateChanged(String label, String path, String oldState, String newState);
-}
diff --git a/core/java/android/storage/StorageManager.java b/core/java/android/storage/StorageManager.java
deleted file mode 100644
index dd8bd63..0000000
--- a/core/java/android/storage/StorageManager.java
+++ /dev/null
@@ -1,300 +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.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.IMountService;
-import android.os.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;
-
-/**
- * Class that lets you access the device's storage management functions. Get an instance of this
- * class by calling {@link android.content.Context#getSystemService(java.lang.String)
- * Context.getSystemService()} with an argument of {@link android.content.Context#STORAGE_SERVICE}.
- */
-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;
-
- /*
- * *static* list of our listeners
- */
- static final ArrayList<ListenerDelegate> sListeners = new ArrayList<ListenerDelegate>();
-
- private class MountServiceBinderListener extends IMountServiceListener.Stub {
- public void onShareAvailabilityChanged(String method, boolean available) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendShareAvailabilityChanged(method, available);
- }
- }
-
- public void onMediaInserted(String label, String path, int major, int minor) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendMediaInserted(label, path, major, minor);
- }
- }
-
- public void onMediaRemoved(String label, String path, int major, int minor, boolean clean) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendMediaRemoved(label, path, major, minor, clean);
- }
- }
-
- public void onVolumeStateChanged(String label, String path, String oldState, String newState) {
- final int size = sListeners.size();
- for (int i = 0; i < size; i++) {
- sListeners.get(i).sendVolumeStateChanged(label, 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_SHARE_AVAILABILITY_CHANGED = 1;
- public static final int EVENT_MEDIA_INSERTED = 2;
- public static final int EVENT_MEDIA_REMOVED = 3;
- public static final int EVENT_VOLUME_STATE_CHANGED = 4;
-
- 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 share availability change.
- */
- private class ShareAvailabilityChangedStorageEvent extends StorageEvent {
- public String method;
- public boolean available;
-
- public ShareAvailabilityChangedStorageEvent(String m, boolean a) {
- super(EVENT_SHARE_AVAILABILITY_CHANGED);
- method = m;
- available = a;
- }
- }
-
- /**
- * Message sent on media insertion
- */
- private class MediaInsertedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public int major;
- public int minor;
-
- public MediaInsertedStorageEvent(String l, String p, int maj, int min) {
- super(EVENT_MEDIA_INSERTED);
- label = l;
- path = p;
- major = maj;
- minor = min;
- }
- }
-
- /**
- * Message sent on media removal
- */
- private class MediaRemovedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public int major;
- public int minor;
- public boolean clean;
-
- public MediaRemovedStorageEvent(String l, String p, int maj, int min, boolean c) {
- super(EVENT_MEDIA_REMOVED);
- label = l;
- path = p;
- major = maj;
- minor = min;
- clean = c;
- }
- }
-
- /**
- * Message sent on volume state change
- */
- private class VolumeStateChangedStorageEvent extends StorageEvent {
- public String label;
- public String path;
- public String oldState;
- public String newState;
-
- public VolumeStateChangedStorageEvent(String l, String p, String oldS, String newS) {
- super(EVENT_VOLUME_STATE_CHANGED);
- label = l;
- 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_SHARE_AVAILABILITY_CHANGED) {
- ShareAvailabilityChangedStorageEvent ev = (ShareAvailabilityChangedStorageEvent) e;
- mStorageEventListener.onShareAvailabilityChanged(ev.method, ev.available);
- } else if (msg.what == StorageEvent.EVENT_MEDIA_INSERTED) {
- MediaInsertedStorageEvent ev = (MediaInsertedStorageEvent) e;
- mStorageEventListener.onMediaInserted(ev.label, ev.path, ev.major, ev.minor);
- } else if (msg.what == StorageEvent.EVENT_MEDIA_REMOVED) {
- MediaRemovedStorageEvent ev = (MediaRemovedStorageEvent) e;
- mStorageEventListener.onMediaRemoved(ev.label, ev.path, ev.major, ev.minor, ev.clean);
- } else if (msg.what == StorageEvent.EVENT_VOLUME_STATE_CHANGED) {
- VolumeStateChangedStorageEvent ev = (VolumeStateChangedStorageEvent) e;
- mStorageEventListener.onVolumeStateChanged(ev.label, ev.path, ev.oldState, ev.newState);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
- }
- };
- }
-
- StorageEventListener getListener() {
- return mStorageEventListener;
- }
-
- void sendShareAvailabilityChanged(String method, boolean available) {
- ShareAvailabilityChangedStorageEvent e = new ShareAvailabilityChangedStorageEvent(method, available);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendMediaInserted(String label, String path, int major, int minor) {
- MediaInsertedStorageEvent e = new MediaInsertedStorageEvent(label, path, major, minor);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendMediaRemoved(String label, String path, int major, int minor, boolean clean) {
- MediaRemovedStorageEvent e = new MediaRemovedStorageEvent(label, path, major, minor, clean);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendVolumeStateChanged(String label, String path, String oldState, String newState) {
- VolumeStateChangedStorageEvent e = new VolumeStateChangedStorageEvent(label, path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
- }
- }
-
- /**
- * {@hide}
- */
- public StorageManager(Looper tgtLooper) throws RemoteException {
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
- mTgtLooper = tgtLooper;
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- }
-
-
- /**
- * Registers a {@link android.storage.StorageEventListener StorageEventListener}.
- *
- * @param listener A {@link android.storage.StorageEventListener StorageEventListener} object.
- *
- */
- public void registerListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (sListeners) {
- sListeners.add(new ListenerDelegate(listener));
- }
- }
-
- /**
- * Unregisters a {@link android.storage.StorageEventListener StorageEventListener}.
- *
- * @param listener A {@link android.storage.StorageEventListener StorageEventListener} object.
- *
- */
- public void unregisterListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- sListeners.remove(i);
- break;
- }
- }
- }
- }
-}
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/Layout.java b/core/java/android/text/Layout.java
index afc6864..1023036 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,15 +308,15 @@ 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);
- boolean useMargin = par;
+ boolean useMargin = isFirstParaLine;
if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
useMargin = count > i;
@@ -305,6 +327,8 @@ public abstract class Layout {
}
}
+ // 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) {
@@ -340,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,
@@ -382,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) {
@@ -397,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());
}
/**
@@ -439,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);
@@ -477,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);
@@ -1565,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,
@@ -1580,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) {
@@ -1623,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;
@@ -1747,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;
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index fbf1261..6c89f92 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1012,6 +1012,10 @@ extends Layout
int extra;
if (needMultiply) {
+ // XXX: this looks like it is using the +0.5 and the cast to int
+ // to do rounding, but this I expect this isn't doing the intended
+ // thing when spacingmult < 1. An intended extra of, say, -1.2
+ // will get 'rounded' to -.7 and then truncated to 0.
extra = (int) ((below - above) * (spacingmult - 1)
+ spacingadd + 0.5);
} else {
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/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index cb55329..f320701 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -23,10 +23,44 @@ 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,
@@ -38,14 +72,29 @@ extends ParagraphStyle
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/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
index 9d8bfd9..69d745d 100644
--- a/core/java/android/text/util/Rfc822Tokenizer.java
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -84,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 {
@@ -110,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/util/base64/Base64.java b/core/java/android/util/base64/Base64.java
new file mode 100644
index 0000000..f6d3905
--- /dev/null
+++ b/core/java/android/util/base64/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.base64;
+
+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/base64/Base64InputStream.java b/core/java/android/util/base64/Base64InputStream.java
new file mode 100644
index 0000000..935939e
--- /dev/null
+++ b/core/java/android/util/base64/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.base64;
+
+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/base64/Base64OutputStream.java b/core/java/android/util/base64/Base64OutputStream.java
new file mode 100644
index 0000000..35e9a2b
--- /dev/null
+++ b/core/java/android/util/base64/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.base64;
+
+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/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 3c79200..58f998e 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -465,10 +465,10 @@ public class GestureDetector {
case MotionEvent.ACTION_POINTER_UP:
// Ending a multitouch gesture and going back to 1 finger
if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
- int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
- >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
- mLastMotionX = ev.getX(id);
- mLastMotionY = ev.getY(id);
+ 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();
}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index e1f2823..d31c8dc 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -34,8 +34,18 @@ public class HapticFeedbackConstants {
* The user has pressed on a virtual on-screen key.
*/
public static final int VIRTUAL_KEY = 1;
+
+ /**
+ * The user has hit the barrier point while scrolling a view.
+ */
+ public static final int SCROLL_BARRIER = 2;
/**
+ * 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/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/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ca5e1de..d7f2539 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;
@@ -504,7 +505,7 @@ 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(
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 9581080..91fd6f1 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -61,6 +61,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
+ int mLastTouch;
private VelocityTracker mNext;
@@ -105,8 +106,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- for (int i = 0; i < MotionEvent.BASE_AVAIL_POINTERS; i++) {
- mPastTime[i][0] = 0;
+ 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;
+ }
}
}
@@ -120,55 +124,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] = 0;
}
- addPoint(p, ev.getX(p), ev.getY(p), time);
- }
- }
-
- private void addPoint(int pos, float x, float y, long time) {
- int drop = -1;
- int i;
- if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time);
- final long[] pastTime = mPastTime[pos];
- for (i=0; i<NUM_PAST; i++) {
- if (pastTime[i] == 0) {
- break;
- } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
- if (localLOGV) Log.v(TAG, "Dropping past too old at "
- + i + " time=" + pastTime[i]);
- drop = i;
+ 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);
}
+
+ touchIndex = (touchIndex + 1) % NUM_PAST;
}
- if (localLOGV) Log.v(TAG, "Add index: " + i);
- if (i == NUM_PAST && drop < 0) {
- drop = 0;
- }
- if (drop == i) drop--;
- final float[] pastX = mPastX[pos];
- final float[] pastY = mPastY[pos];
- if (drop >= 0) {
- if (localLOGV) Log.v(TAG, "Dropping up to #" + drop);
- final int start = drop+1;
- final int count = NUM_PAST-drop-1;
- System.arraycopy(pastX, start, pastX, 0, count);
- System.arraycopy(pastY, start, pastY, 0, count);
- System.arraycopy(pastTime, start, pastTime, 0, count);
- i -= (drop+1);
+
+ // During calculation any pointer values with a time of 0 are treated
+ // as a break in input. Initialize all to 0 for each new touch index.
+ for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
+ mPastTime[id][touchIndex] = 0;
}
- pastX[i] = x;
- pastY[i] = y;
- pastTime[i] = time;
- i++;
- if (i < NUM_PAST) {
- pastTime[i] = 0;
+ 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;
}
+
+ mLastTouch = touchIndex;
}
/**
@@ -199,36 +185,43 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
final float[] pastX = mPastX[pos];
final float[] pastY = mPastY[pos];
final long[] pastTime = mPastTime[pos];
-
+ final int lastTouch = mLastTouch;
+
+ // find oldest acceptable time
+ int oldestTouch = lastTouch;
+ if (pastTime[lastTouch] > 0) { // cleared ?
+ final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME;
+ int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
+ while (pastTime[nextOldestTouch] >= acceptableTime &&
+ nextOldestTouch != lastTouch) {
+ oldestTouch = nextOldestTouch;
+ nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
+ }
+ }
+
// Kind-of stupid.
- final float oldestX = pastX[0];
- final float oldestY = pastY[0];
- final long oldestTime = pastTime[0];
+ final float oldestX = pastX[oldestTouch];
+ final float oldestY = pastY[oldestTouch];
+ final long oldestTime = pastTime[oldestTouch];
float accumX = 0;
float accumY = 0;
- int N=0;
- while (N < NUM_PAST) {
- if (pastTime[N] == 0) {
- break;
- }
- N++;
- }
+ float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1;
// Skip the last received event, since it is probably pretty noisy.
if (N > 3) N--;
for (int i=1; i < N; i++) {
- final int dur = (int)(pastTime[i] - oldestTime);
+ final int j = (oldestTouch + i) % NUM_PAST;
+ final int dur = (int)(pastTime[j] - oldestTime);
if (dur == 0) continue;
- float dist = pastX[i] - oldestX;
+ float dist = pastX[j] - oldestX;
float vel = (dist/dur) * units; // pixels/frame.
- if (accumX == 0) accumX = vel;
- else accumX = (accumX + vel) * .5f;
-
- dist = pastY[i] - oldestY;
+ accumX = (accumX == 0) ? vel : (accumX + vel) * .5f;
+
+ dist = pastY[j] - oldestY;
vel = (dist/dur) * units; // pixels/frame.
- if (accumY == 0) accumY = vel;
- else accumY = (accumY + vel) * .5f;
+ accumY = (accumY == 0) ? vel : (accumY + vel) * .5f;
}
+
mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
: Math.min(accumX, maxVelocity);
mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
@@ -263,25 +256,25 @@ 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 f5c465e..2eb633f 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;
@@ -1504,6 +1505,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @hide
*/
private static final int PREPRESSED = 0x02000000;
+
+ /**
+ * Always allow a user to overscroll this view, provided it is a
+ * view that can scroll.
+ */
+ private static final int OVERSCROLL_ALWAYS = 0;
+
+ /**
+ * Allow a user to overscroll this view only if the content is large
+ * enough to meaningfully scroll, provided it is a view that can scroll.
+ */
+ private static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1;
+
+ /**
+ * Never allow a user to overscroll this view.
+ */
+ private static final int OVERSCROLL_NEVER = 2;
+
+ /**
+ * Controls the overscroll mode for this view.
+ * See {@link #overscrollBy(int, int, int, int, int, int, int, int)},
+ * {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS},
+ * and {@link #OVERSCROLL_NEVER}.
+ */
+ private int mOverscrollMode = OVERSCROLL_ALWAYS;
/**
* The parent this view is attached to.
@@ -2053,6 +2079,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
});
}
break;
+ case R.styleable.View_overscrollMode:
+ mOverscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS);
+ break;
}
}
@@ -3022,6 +3051,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;
@@ -3904,6 +3934,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.
*/
@@ -8281,6 +8337,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Cancels any animations for this view.
*/
public void clearAnimation() {
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.detach();
+ }
mCurrentAnimation = null;
}
@@ -8544,7 +8603,135 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
+
+ /**
+ * Scroll the view with standard behavior for scrolling beyond the normal
+ * content boundaries. Views that call this method should override
+ * {@link #onOverscrolled(int, int, boolean, boolean)} to respond to the
+ * results of an overscroll operation.
+ *
+ * Views can use this method to handle any touch or fling-based scrolling.
+ *
+ * @param deltaX Change in X in pixels
+ * @param deltaY Change in Y in pixels
+ * @param scrollX Current X scroll value in pixels before applying deltaX
+ * @param scrollY Current Y scroll value in pixels before applying deltaY
+ * @param scrollRangeX Maximum content scroll range along the X axis
+ * @param scrollRangeY Maximum content scroll range along the Y axis
+ * @param maxOverscrollX Number of pixels to overscroll by in either direction
+ * along the X axis.
+ * @param maxOverscrollY Number of pixels to overscroll by in either direction
+ * along the Y axis.
+ * @return true if scrolling was clamped to an overscroll boundary along either
+ * axis, false otherwise.
+ */
+ protected boolean overscrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverscrollX, int maxOverscrollY) {
+ final int overscrollMode = mOverscrollMode;
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+ int newScrollX = scrollX + deltaX;
+ if (overscrollHorizontal) {
+ // Scale the scroll amount if we're in the dropoff zone
+ final int dropoffX = maxOverscrollX / 2;
+ final int dropoffLeft = -dropoffX;
+ final int dropoffRight = dropoffX + scrollRangeX;
+ if ((scrollX < dropoffLeft && deltaX < 0) ||
+ (scrollX > dropoffRight && deltaX > 0)) {
+ newScrollX = scrollX + deltaX / 2;
+ } else {
+ if (newScrollX > dropoffRight && deltaX > 0) {
+ int extra = newScrollX - dropoffRight;
+ newScrollX = dropoffRight + extra / 2;
+ } else if (newScrollX < dropoffLeft && deltaX < 0) {
+ int extra = newScrollX - dropoffLeft;
+ newScrollX = dropoffLeft + extra / 2;
+ }
+ }
+ } else {
+ maxOverscrollX = 0;
+ }
+
+ int newScrollY = scrollY + deltaY;
+ if (overscrollVertical) {
+ final int dropoffY = maxOverscrollY / 2;
+ final int dropoffTop = -dropoffY;
+ final int dropoffBottom = dropoffY + scrollRangeY;
+ if ((scrollY < dropoffTop && deltaY < 0) ||
+ (scrollY > dropoffBottom && deltaY > 0)) {
+ newScrollY = scrollY + deltaY / 2;
+ } else {
+ if (newScrollY > dropoffBottom && deltaY > 0) {
+ int extra = newScrollY - dropoffBottom;
+ newScrollY = dropoffBottom + extra / 2;
+ } else if (newScrollY < dropoffTop && deltaY < 0) {
+ int extra = newScrollY - dropoffTop;
+ newScrollY = dropoffTop + extra / 2;
+ }
+ }
+ } else {
+ maxOverscrollY = 0;
+ }
+
+ // Clamp values if at the limits and record
+ final int left = -maxOverscrollX;
+ final int right = maxOverscrollX + scrollRangeX;
+ final int top = -maxOverscrollY;
+ final int bottom = maxOverscrollY + scrollRangeY;
+
+ boolean clampedX = false;
+ if (newScrollX > right) {
+ newScrollX = right;
+ clampedX = true;
+ } else if (newScrollX < left) {
+ newScrollX = left;
+ clampedX = true;
+ }
+
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ // Bump the device with some haptic feedback if we're at the edge
+ // and didn't start there.
+ if ((overscrollHorizontal && clampedX && scrollX != left && scrollX != right) ||
+ (overscrollVertical && clampedY && scrollY != top && scrollY != bottom)) {
+ performHapticFeedback(HapticFeedbackConstants.SCROLL_BARRIER);
+ }
+ onOverscrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+ return clampedX || clampedY;
+ }
+
+ /**
+ * Called by {@link #overscrollBy(int, int, int, int, int, int, int, int)} to
+ * respond to the results of an overscroll operation.
+ *
+ * @param scrollX New X scroll value in pixels
+ * @param scrollY New Y scroll value in pixels
+ * @param clampedX True if scrollX was clamped to an overscroll boundary
+ * @param clampedY True if scrollY was clamped to an overscroll boundary
+ */
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Intentionally empty.
+ }
+
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index cdf9eb0..0663215 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;
@@ -722,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);
@@ -3398,7 +3412,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Special value for the height or width requested by a View.
- * MATCH_PARENT means that the view wants to be as bigas its parent,
+ * MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any.
*/
public static final int MATCH_PARENT = -1;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 07b2d1c..264b8c9 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;
@@ -101,6 +103,9 @@ public final class ViewRoot extends Handler implements ViewParent,
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;
@@ -171,6 +176,12 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
+ class ResizedInfo {
+ Rect coveredInsets;
+ Rect visibleInsets;
+ Configuration newConfig;
+ }
+
boolean mScrollMayChange;
int mSoftInputMode;
View mLastScrolledFocus;
@@ -265,6 +276,12 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
+ public static void addConfigCallback(ComponentCallbacks callback) {
+ synchronized (sConfigCallbacks) {
+ sConfigCallbacks.add(callback);
+ }
+ }
+
// FIXME for perf testing only
private boolean mProfile = false;
@@ -1782,23 +1799,33 @@ 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)) {
break;
}
// fall through...
case RESIZED_REPORT:
if (mAdded) {
+ Configuration config = ((ResizedInfo)msg.obj).newConfig;
+ if (config != null) {
+ synchronized (sConfigCallbacks) {
+ for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
+ sConfigCallbacks.get(i).onConfigurationChanged(config);
+ }
+ }
+ if (mView != null) {
+ mView.dispatchConfigurationChanged(config);
+ }
+ }
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;
}
@@ -2587,7 +2614,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()
@@ -2601,7 +2628,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);
}
@@ -2802,11 +2833,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/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 7bc5cce..ab3260e 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -851,6 +851,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/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c8396c4..349b7e5 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -256,6 +256,37 @@ public abstract class Animation implements Cloneable {
}
/**
+ * Cancel the animation. Cancelling an animation invokes the animation
+ * listener, if set, to notify the end of the animation.
+ *
+ * If you cancel an animation manually, you must call {@link #reset()}
+ * before starting the animation again.
+ *
+ * @see #reset()
+ * @see #start()
+ * @see #startNow()
+ */
+ public void cancel() {
+ if (mStarted && !mEnded) {
+ if (mListener != null) mListener.onAnimationEnd(this);
+ mEnded = true;
+ }
+ // Make sure we move the animation to the end
+ mStartTime = Long.MIN_VALUE;
+ mMore = mOneMoreTime = false;
+ }
+
+ /**
+ * @hide
+ */
+ public void detach() {
+ if (mStarted && !mEnded) {
+ mEnded = true;
+ if (mListener != null) mListener.onAnimationEnd(this);
+ }
+ }
+
+ /**
* Whether or not the animation has been initialized.
*
* @return Has this animation been initialized.
@@ -745,10 +776,10 @@ public abstract class Animation implements Cloneable {
if (expired) {
if (mRepeatCount == mRepeated) {
if (!mEnded) {
+ mEnded = true;
if (mListener != null) {
mListener.onAnimationEnd(this);
}
- mEnded = true;
}
} else {
if (mRepeatCount > 0) {
@@ -880,7 +911,7 @@ public abstract class Animation implements Cloneable {
region.inset(-1.0f, -1.0f);
if (mFillBefore) {
final Transformation previousTransformation = mPreviousTransformation;
- applyTransformation(0.0f, previousTransformation);
+ applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
}
}
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 98b2594..1546dcd 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -282,7 +282,9 @@ public class AnimationSet extends Animation {
final Animation a = animations.get(i);
temp.clear();
- a.applyTransformation(0.0f, temp);
+ final Interpolator interpolator = a.mInterpolator;
+ a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f)
+ : 0.0f, temp);
previousTransformation.compose(temp);
}
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 16feaa9..bc13504 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
*/
@@ -41,7 +41,7 @@ public class DateSorter {
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
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/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/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index e496d97..9dc7079 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -21,6 +21,8 @@ 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;
@@ -248,4 +250,7 @@ final class JWebCoreJavaBridge extends Handler {
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/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index a9d6ff6..3ed9851 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -368,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");
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 35f1ac6..662be95 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;
@@ -501,8 +501,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();
@@ -916,9 +916,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) {
@@ -928,17 +931,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) {
@@ -948,9 +955,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() {
@@ -1355,8 +1361,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) {
@@ -1373,7 +1377,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 cf71a84..9314d7b 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
@@ -342,7 +342,7 @@ public final class WebStorage {
/**
* 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);
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 6d0be43..d1ad61f 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -116,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;
}
@@ -145,6 +145,12 @@ import java.util.ArrayList;
break;
}
+ 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.
@@ -810,6 +816,9 @@ import java.util.ArrayList;
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
@@ -819,6 +828,7 @@ import java.util.ArrayList;
break;
case 2: // PASSWORD
inPassword = true;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case 3: // SEARCH
imeOptions |= EditorInfo.IME_ACTION_SEARCH;
@@ -826,22 +836,25 @@ import java.util.ArrayList;
case 4: // EMAIL
// TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents EMAIL_ADDRESS
// from working, so exclude it for now.
- inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case 5: // NUMBER
- inputType = EditorInfo.TYPE_CLASS_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;
+ inputType |= EditorInfo.TYPE_CLASS_PHONE;
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case 7: // URL
- // TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents URI
- // from working, so exclude it for now.
- inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_URI;
+ // 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);
@@ -855,22 +868,6 @@ import java.util.ArrayList;
mWebView.requestFormData(name, mNodePointer);
}
}
- if (type != 3 /* SEARCH */) {
- int action = mWebView.nativeTextFieldAction();
- switch (action) {
- // Keep in sync with CachedRoot::ImeAction
- case 0: // NEXT
- imeOptions |= EditorInfo.IME_ACTION_NEXT;
- break;
- case 1: // GO
- imeOptions |= EditorInfo.IME_ACTION_GO;
- break;
- case -1: // FAILURE
- case 2: // DONE
- imeOptions |= EditorInfo.IME_ACTION_DONE;
- break;
- }
- }
}
mSingle = single;
setMaxLength(maxLength);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6627973..adae0cb 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -26,6 +26,7 @@ import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Interpolator;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
@@ -37,7 +38,6 @@ 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;
@@ -86,6 +86,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import junit.framework.Assert;
@@ -213,8 +214,6 @@ public class WebView extends AbsoluteLayout
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
private int mRootLayer; // C++ pointer to the root layer
- private boolean mLayersHaveAnimations;
- private EvaluateLayersAnimations mEvaluateThread;
static final String LOGTAG = "webview";
@@ -622,8 +621,6 @@ public class WebView extends AbsoluteLayout
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;
@@ -1105,7 +1102,7 @@ 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.
@@ -1391,7 +1388,7 @@ public class WebView extends AbsoluteLayout
arg.mUrl = url;
arg.mExtraHeaders = extraHeaders;
mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
- clearTextEntry();
+ clearTextEntry(false);
}
/**
@@ -1420,7 +1417,7 @@ public class WebView extends AbsoluteLayout
arg.mUrl = url;
arg.mPostData = postData;
mWebViewCore.sendMessage(EventHub.POST_URL, arg);
- clearTextEntry();
+ clearTextEntry(false);
} else {
loadUrl(url);
}
@@ -1476,7 +1473,7 @@ public class WebView extends AbsoluteLayout
arg.mEncoding = encoding;
arg.mFailUrl = failUrl;
mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
- clearTextEntry();
+ clearTextEntry(false);
}
/**
@@ -1493,7 +1490,7 @@ public class WebView extends AbsoluteLayout
* Reload the current url.
*/
public void reload() {
- clearTextEntry();
+ clearTextEntry(false);
switchOutDrawHistory();
mWebViewCore.sendMessage(EventHub.RELOAD);
}
@@ -1580,7 +1577,7 @@ public class WebView extends AbsoluteLayout
// 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);
}
@@ -1680,9 +1677,17 @@ public class WebView extends AbsoluteLayout
&& mWebTextView.hasFocus();
}
- 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();
+ }
}
}
@@ -1716,7 +1721,7 @@ public class WebView extends AbsoluteLayout
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return;
}
- clearTextEntry();
+ clearTextEntry(false);
if (getSettings().getBuiltInZoomControls()) {
mZoomButtonsController.setVisible(true);
} else {
@@ -2906,6 +2911,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.
@@ -2972,8 +3016,16 @@ public class WebView extends AbsoluteLayout
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);
@@ -3036,14 +3088,11 @@ public class WebView extends AbsoluteLayout
Rect vBox = contentToViewRect(contentBounds);
Rect visibleRect = new Rect();
calcOurVisibleRect(visibleRect);
- // The IME may have shown, resulting in the textfield being offscreen.
- // If so, the textfield will be scrolled on screen, so treat it as
- // though it is on screen. If it is on screen, place the WebTextView in
- // its new place, accounting for our new scroll/zoom values.
- InputMethodManager imm = InputMethodManager.peekInstance();
- if ((imm != null && imm.isActive(mWebTextView))
- || (allowIntersect ? Rect.intersects(visibleRect, vBox)
- : visibleRect.contains(vBox))) {
+ // 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());
mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -3061,14 +3110,36 @@ public class WebView extends AbsoluteLayout
}
}
+ private static class Metrics {
+ int mScrollX;
+ int mScrollY;
+ int mWidth;
+ int mHeight;
+ float mScale;
+ }
+
+ private Metrics getViewMetrics() {
+ Metrics metrics = new Metrics();
+ metrics.mScrollX = mScrollX;
+ metrics.mScrollY = computeVerticalScrollOffset();
+ metrics.mWidth = getWidth();
+ metrics.mHeight = getHeight() - getVisibleTitleHeight();
+ metrics.mScale = mActualScale;
+ return metrics;
+ }
+
private void drawLayers(Canvas canvas) {
if (mRootLayer != 0) {
- int scrollY = computeVerticalScrollOffset();
- int viewHeight = getHeight() - getVisibleTitleHeight();
+ // Currently for each draw we compute the animation values;
+ // We may in the future decide to do that independently.
+ if (nativeEvaluateLayersAnimations(mRootLayer)) {
+ // If we have unfinished (or unstarted) animations,
+ // we ask for a repaint.
+ invalidate();
+ }
- nativeDrawLayers(mRootLayer, mScrollX, scrollY,
- getWidth(), viewHeight,
- mActualScale, canvas);
+ // We can now draw the layers.
+ nativeDrawLayers(mRootLayer, canvas);
}
}
@@ -3156,7 +3227,15 @@ public class WebView extends AbsoluteLayout
mWebViewCore.drawContentPicture(canvas, color,
(animateZoom || mPreviewZoomOnly), animateScroll);
-
+ boolean cursorIsInLayer = nativeCursorIsInLayer();
+ if (drawCursorRing && !cursorIsInLayer) {
+ 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);
+ }
drawLayers(canvas);
if (mNativeClass == 0) return;
@@ -3179,12 +3258,7 @@ public class WebView extends AbsoluteLayout
LONG_PRESS_TIMEOUT);
}
}
- 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 (cursorIsInLayer) nativeDrawCursorRing(canvas);
}
if (mFocusSizeChanged) {
mFocusSizeChanged = false;
@@ -3370,6 +3444,10 @@ public class WebView extends AbsoluteLayout
text = "";
}
mWebTextView.setTextAndKeepSelection(text);
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imm.isActive(mWebTextView)) {
+ imm.restartInput(mWebTextView);
+ }
}
mWebTextView.requestFocus();
}
@@ -3405,40 +3483,6 @@ public class WebView extends AbsoluteLayout
}
/*
- * This class runs the layers animations in their own thread,
- * so that we do not slow down the UI.
- */
- private class EvaluateLayersAnimations extends Thread {
- boolean mRunning = true;
- // delay corresponds to 40fps, no need to go faster.
- int mDelay = 25; // in ms
- public void run() {
- while (mRunning) {
- if (mLayersHaveAnimations && mRootLayer != 0) {
- // updates is a C++ pointer to a Vector of AnimationValues
- int updates = nativeEvaluateLayersAnimations(mRootLayer);
- if (updates == 0) {
- mRunning = false;
- }
- Message.obtain(mPrivateHandler,
- WebView.IMMEDIATE_REPAINT_MSG_ID,
- updates, 0).sendToTarget();
- } else {
- mRunning = false;
- }
- try {
- Thread.currentThread().sleep(mDelay);
- } catch (InterruptedException e) {
- mRunning = false;
- }
- }
- }
- public void cancel() {
- mRunning = false;
- }
- }
-
- /*
* This class requests an Adapter for the WebTextView which shows past
* entries stored in the database. It is a Runnable so that it can be done
* in its own thread, without slowing down the UI.
@@ -3720,6 +3764,7 @@ public class WebView extends AbsoluteLayout
}
return true;
}
+ clearTextEntry(true);
nativeSetFollowedLink(true);
if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
@@ -3800,7 +3845,7 @@ public class WebView extends AbsoluteLayout
@Override
protected void onDetachedFromWindow() {
- clearTextEntry();
+ clearTextEntry(false);
super.onDetachedFromWindow();
// Clean up the zoom controller
mZoomButtonsController.setVisible(false);
@@ -4066,6 +4111,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;
@@ -4090,6 +4143,7 @@ public class WebView extends AbsoluteLayout
mMinDX = -viewLeft;
mMaxDX = docRight - viewRight;
+ mState = DRAGGING_STATE;
mProxy.onStartDrag(x, y);
// ensure we buildBitmap at least once
@@ -4112,6 +4166,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;
@@ -4126,10 +4186,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() {
@@ -4150,13 +4226,23 @@ public class WebView extends AbsoluteLayout
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);
@@ -4350,6 +4436,7 @@ public class WebView extends AbsoluteLayout
ted.mX = viewToContentX((int) x + mScrollX);
ted.mY = viewToContentY((int) y + mScrollY);
ted.mEventTime = eventTime;
+ ted.mMetaState = ev.getMetaState();
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
mLastSentTouchTime = eventTime;
}
@@ -4399,7 +4486,7 @@ public class WebView extends AbsoluteLayout
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);
}
}
@@ -4600,7 +4687,6 @@ public class WebView extends AbsoluteLayout
case MotionEvent.ACTION_UP: {
if (mDragTrackerHandler != null) {
mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
}
mLastTouchUpTime = eventTime;
switch (mTouchMode) {
@@ -4614,6 +4700,7 @@ public class WebView extends AbsoluteLayout
ted.mX = viewToContentX((int) x + mScrollX);
ted.mY = viewToContentY((int) y + mScrollY);
ted.mEventTime = eventTime;
+ ted.mMetaState = ev.getMetaState();
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
} else if (mFullScreenHolder == null) {
doDoubleTap();
@@ -4712,7 +4799,6 @@ public class WebView extends AbsoluteLayout
private void cancelTouch() {
if (mDragTrackerHandler != null) {
mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
}
// we also use mVelocityTracker == null to tell us that we are
// not "moving around", so we can take the slower/prettier
@@ -5337,11 +5423,8 @@ public class WebView extends AbsoluteLayout
}
private void doMotionUp(int contentX, int contentY) {
- if (nativeMotionUp(contentX, contentY, mNavSlop)) {
- if (mLogEvent) {
- Checkin.updateStats(mContext.getContentResolver(),
- Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
- }
+ if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) {
+ EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER);
}
if (nativeHasCursorNode() && !nativeCursorIsTextInput()) {
playSoundEffect(SoundEffectConstants.CLICK);
@@ -5654,6 +5737,11 @@ public class WebView extends AbsoluteLayout
ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
ted.mEventTime = SystemClock.uptimeMillis();
+ // 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;
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
} else if (mPreventDrag == PREVENT_DRAG_NO) {
mTouchMode = TOUCH_DONE_MODE;
@@ -5751,7 +5839,7 @@ public class WebView extends AbsoluteLayout
// 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();
+ clearTextEntry(false);
// update the zoom buttons as the scale can be changed
if (getSettings().getBuiltInZoomControls()) {
updateZoomButtonsEnabled();
@@ -5834,6 +5922,10 @@ public class WebView extends AbsoluteLayout
}
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();
if (inEditingMode()
&& mWebTextView.isSameTextField(msg.arg1)
&& msg.arg2 == mTextGeneration) {
@@ -5869,7 +5961,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;
@@ -5883,34 +5975,16 @@ public class WebView extends AbsoluteLayout
break;
}
case IMMEDIATE_REPAINT_MSG_ID: {
- int updates = msg.arg1;
- if (updates != 0) {
- // updates is a C++ pointer to a Vector of
- // AnimationValues that we apply to the layers.
- // The Vector is deallocated in nativeUpdateLayers().
- nativeUpdateLayers(updates);
- }
invalidate();
break;
}
case SET_ROOT_LAYER_MSG_ID: {
int oldLayer = mRootLayer;
mRootLayer = msg.arg1;
+ nativeSetRootLayer(mRootLayer);
if (oldLayer > 0) {
nativeDestroyLayer(oldLayer);
}
- if (mRootLayer == 0) {
- mLayersHaveAnimations = false;
- }
- if (mEvaluateThread != null) {
- mEvaluateThread.cancel();
- mEvaluateThread = null;
- }
- if (nativeLayersHaveAnimations(mRootLayer)) {
- mLayersHaveAnimations = true;
- mEvaluateThread = new EvaluateLayersAnimations();
- mEvaluateThread.start();
- }
invalidate();
break;
}
@@ -6124,6 +6198,9 @@ public class WebView extends AbsoluteLayout
// 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;
@@ -6491,8 +6568,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());
@@ -6668,6 +6744,7 @@ public class WebView extends AbsoluteLayout
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
+ private native boolean nativeCursorIsInLayer();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -6680,13 +6757,8 @@ public class WebView extends AbsoluteLayout
private native void nativeDestroy();
private native void nativeDrawCursorRing(Canvas content);
private native void nativeDestroyLayer(int layer);
- private native int nativeEvaluateLayersAnimations(int layer);
- private native boolean nativeLayersHaveAnimations(int layer);
- private native void nativeUpdateLayers(int updates);
- private native void nativeDrawLayers(int layer,
- int scrollX, int scrollY,
- int width, int height,
- float scale, Canvas canvas);
+ private native boolean nativeEvaluateLayersAnimations(int layer);
+ private native void nativeDrawLayers(int layer, Canvas canvas);
private native void nativeDrawMatches(Canvas canvas);
private native void nativeDrawSelectionPointer(Canvas content,
float scale, int x, int y, boolean extendSelection);
@@ -6736,8 +6808,7 @@ public class WebView extends AbsoluteLayout
private native void nativeSetFindIsUp();
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
- // Returns a value corresponding to CachedFrame::ImeAction
- /* package */ native int nativeTextFieldAction();
+ private native void nativeSetRootLayer(int layer);
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 9c91919..361ec56 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -515,7 +515,7 @@ final class WebViewCore {
private native void nativeTouchUp(int touchGeneration,
int framePtr, int nodePtr, int x, int y);
- private native int nativeHandleTouchEvent(int action, int x, int y, long time);
+ private native int nativeHandleTouchEvent(int action, int x, int y, long time, int metaState);
private native void nativeUpdateFrameCache();
@@ -735,6 +735,7 @@ final class WebViewCore {
int mX;
int mY;
long mEventTime;
+ int mMetaState;
}
static class GeolocationPermissionsData {
@@ -892,6 +893,11 @@ final class WebViewCore {
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;
@@ -1193,7 +1199,7 @@ final class WebViewCore {
mWebView.mPrivateHandler,
WebView.PREVENT_TOUCH_ID, ted.mAction,
nativeHandleTouchEvent(ted.mAction, ted.mX,
- ted.mY, ted.mEventTime)).sendToTarget();
+ ted.mY, ted.mEventTime, ted.mMetaState)).sendToTarget();
break;
}
@@ -1363,6 +1369,33 @@ final class WebViewCore {
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;
}
}
};
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 66a7631..a79bbee 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -32,7 +32,6 @@ 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;
@@ -307,6 +306,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
@@ -373,6 +377,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int mResurrectToPosition = INVALID_POSITION;
private ContextMenuInfo mContextMenuInfo = null;
+
+ /**
+ * Maximum distance to overscroll by
+ */
+ private int mOverscrollMax;
+
+ /**
+ * Content height divided by this is the overscroll limit.
+ */
+ private static final int OVERSCROLL_LIMIT_DIVISOR = 3;
/**
* Used to request a layout when we changed touch mode
@@ -1044,7 +1058,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;
@@ -1064,7 +1079,17 @@ 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);
+ if (mScrollY != 0) {
+ // Compensate for overscroll
+ result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
+ }
+ } else {
+ result = mItemCount;
+ }
+ return result;
}
@Override
@@ -1125,6 +1150,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mInLayout = true;
layoutChildren();
mInLayout = false;
+
+ mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
}
/**
@@ -1588,6 +1615,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// 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();
@@ -1935,12 +1966,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} else {
int touchMode = mTouchMode;
if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
- mScrollY = 0;
if (mFlingRunnable != null) {
mFlingRunnable.endFling();
+
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ invalidate();
+ }
}
}
}
+ mLastTouchMode = isInTouchMode ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
}
@Override
@@ -2052,9 +2088,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (motionView != null) {
motionViewPrevTop = motionView.getTop();
}
+
// 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
@@ -2063,11 +2101,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Check if the top of the motion view is where it is
// supposed to be
final int motionViewRealTop = motionView.getTop();
- final int motionViewNewTop = mMotionViewNewTop;
- if (motionViewRealTop != motionViewNewTop) {
+ if (atEdge) {
// Apply overscroll
- mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop);
+ int overscroll = -incrementalDeltaY -
+ (motionViewRealTop - motionViewPrevTop);
+ overscrollBy(0, overscroll, 0, mScrollY, 0, 0,
+ 0, getOverscrollMax());
mTouchMode = TOUCH_MODE_OVERSCROLL;
invalidate();
}
@@ -2111,7 +2151,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mMotionPosition = motionPosition;
}
} else {
- mScrollY -= incrementalDeltaY;
+ overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
+ 0, getOverscrollMax());
invalidate();
}
mLastY = y;
@@ -2296,12 +2337,43 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return true;
}
+
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ mScrollY = scrollY;
+ if (clampedY) {
+ // Velocity is broken by hitting the limit; don't start a fling off of this.
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private int getOverscrollMax() {
+ final int childCount = getChildCount();
+ if (childCount > 0) {
+ return Math.min(mOverscrollMax,
+ getChildAt(childCount - 1).getBottom() / OVERSCROLL_LIMIT_DIVISOR);
+ } else {
+ return mOverscrollMax;
+ }
+ }
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mFastScroller != null) {
- mFastScroller.draw(canvas);
+ final int scrollY = mScrollY;
+ if (scrollY != 0) {
+ // Pin the fast scroll thumb to the top/bottom during overscroll.
+ int restoreCount = canvas.save();
+ canvas.translate(0, (float) scrollY);
+ mFastScroller.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ } else {
+ mFastScroller.draw(canvas);
+ }
}
}
@@ -2440,7 +2512,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
-
+
void startSpringback() {
if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
mTouchMode = TOUCH_MODE_OVERFLING;
@@ -2448,19 +2520,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
post(this);
}
}
-
+
void startOverfling(int initialVelocity) {
mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
mTouchMode = TOUCH_MODE_OVERFLING;
invalidate();
post(this);
}
-
+
+ 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() {
@@ -2503,22 +2589,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
}
- // Do something different on overscroll - offsetChildrenTopAndBottom()
- trackMotionScroll(delta, delta);
-
// Check to see if we have bumped into the scroll limit
View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ int oldTop = 0;
if (motionView != null) {
- // Check if the top of the motion view is where it is
- // supposed to be
- if (motionView.getTop() != mMotionViewNewTop) {
- float vel = scroller.getCurrVelocity();
- if (delta > 0) {
- vel = -vel;
- }
- startOverfling(Math.round(vel));
- break;
+ oldTop = motionView.getTop();
+ }
+ if (trackMotionScroll(delta, delta)) {
+ if (motionView != null) {
+ // Tweak the scroll for how far we overshot
+ int overshoot = -(delta - (motionView.getTop() - oldTop));
+ overscrollBy(0, overshoot, 0, mScrollY, 0, 0, 0, getOverscrollMax());
}
+ float vel = scroller.getCurrVelocity();
+ if (delta > 0) {
+ vel = -vel;
+ }
+ startOverfling(Math.round(vel));
+ break;
}
if (more) {
@@ -2541,9 +2629,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case TOUCH_MODE_OVERFLING: {
final OverScroller scroller = mScroller;
if (scroller.computeScrollOffset()) {
- mScrollY = scroller.getCurrY();
- invalidate();
- post(this);
+ final int scrollY = mScrollY;
+ final int deltaY = scroller.getCurrY() - scrollY;
+ if (overscrollBy(0, deltaY, 0, scrollY, 0, 0, 0, getOverscrollMax())) {
+ startSpringback();
+ } else {
+ invalidate();
+ post(this);
+ }
} else {
endFling();
}
@@ -2553,6 +2646,285 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+
+
+ 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 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;
+ }
+
+ 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;
+
+ smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll,
+ mScrollDuration);
+
+ mLastSeenPos = lastPos;
+ if (lastPos != mTargetPos) {
+ post(this);
+ }
+ break;
+ }
+
+ case MOVE_DOWN_BOUND: {
+ final int nextViewIndex = 1;
+ if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) {
+ return;
+ }
+ final int nextPos = firstPos + nextViewIndex;
+
+ if (nextPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ 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);
+
+ 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;
+ }
+
+ final View firstView = getChildAt(0);
+ if (firstView == null) {
+ return;
+ }
+ final int firstViewTop = firstView.getTop();
+
+ smoothScrollBy(firstViewTop - mExtraScroll, 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 (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() {
if (mScrollingCacheEnabled && !mCachingStarted) {
@@ -2588,11 +2960,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();
@@ -2618,98 +2991,99 @@ 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;
- hideSelector();
+ 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();
- fillGap(down);
- mBlockLayoutRequests = false;
+ invalidate();
- invokeOnItemScrollListener();
- awakenScrollBars();
+ final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+ if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
+ fillGap(down);
}
+
+ mBlockLayoutRequests = false;
+
+ invokeOnItemScrollListener();
+ awakenScrollBars();
+
+ return false;
}
/**
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 299ed8a..9ef5e0b 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -103,6 +103,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/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/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 405461a..a4b20da 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;
@@ -35,6 +33,8 @@ import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
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
@@ -541,6 +541,12 @@ public class ExpandableListView extends ListView {
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
}
+
+ final int groupPos = posMetadata.position.groupPos;
+ final int groupFlatPos = posMetadata.position.flatListPos;
+
+ smoothScrollToPosition(groupFlatPos + mAdapter.getChildrenCount(groupPos),
+ groupFlatPos);
}
returnValue = true;
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 30a38df..b9acf5e 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1856,8 +1856,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;
@@ -1868,7 +1871,12 @@ public class GridView extends AbsListView {
// TODO: Account for vertical spacing too
final int numColumns = mNumColumns;
final int rowCount = (mItemCount + numColumns - 1) / numColumns;
- return Math.max(rowCount * 100, 0);
+ int result = Math.max(rowCount * 100, 0);
+ if (mScrollY != 0) {
+ // Compensate for overscroll
+ result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
+ }
+ return result;
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 0078fec..a7b819a 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -461,7 +461,8 @@ public class HorizontalScrollView extends FrameLayout {
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
- super.scrollTo(mScrollX + deltaX, mScrollY);
+ overscrollBy(deltaX, 0, mScrollX, 0, getScrollRange(), 0,
+ getOverscrollMax(), 0);
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
@@ -472,8 +473,7 @@ public class HorizontalScrollView extends FrameLayout {
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
fling(-initialVelocity);
} else {
- final int right = Math.max(0, getChildAt(0).getHeight() -
- (getHeight() - mPaddingRight - mPaddingLeft));
+ final int right = getScrollRange();
if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
invalidate();
}
@@ -487,6 +487,41 @@ public class HorizontalScrollView extends FrameLayout {
}
return true;
}
+
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ mScrollX = scrollX;
+ mScrollY = scrollY;
+ if (clampedX) {
+ mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
+ }
+ } else {
+ super.scrollTo(scrollX, scrollY);
+ }
+ }
+
+ private int getOverscrollMax() {
+ int childCount = getChildCount();
+ int containerOverscroll = (getWidth() - mPaddingLeft - mPaddingRight) / 3;
+ if (childCount > 0) {
+ return Math.min(containerOverscroll, getChildAt(0).getWidth() / 3);
+ } else {
+ return containerOverscroll;
+ }
+ }
+
+ 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>
@@ -855,10 +890,28 @@ 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;
+ }
+
+ int scrollRange = getChildAt(0).getRight();
+ final int scrollX = mScrollX;
+ final int overscrollRight = Math.max(0, scrollRange - contentWidth);
+ if (scrollX < 0) {
+ scrollRange -= scrollX;
+ } else if (scrollX > overscrollRight) {
+ scrollRange += scrollX - overscrollRight;
+ }
+
+ return scrollRange;
+ }
+
+ @Override
+ protected int computeHorizontalScrollOffset() {
+ return Math.max(0, super.computeHorizontalScrollOffset());
}
-
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
@@ -913,10 +966,9 @@ public class HorizontalScrollView extends FrameLayout {
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
- mScrollX = x;
- mScrollY = y;
-
- if (oldX != mScrollX || oldY != mScrollY) {
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
+ getOverscrollMax(), 0);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 3853359..233ce30 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -32,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;
@@ -69,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;
@@ -138,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(
@@ -877,6 +879,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);
}
@@ -889,22 +903,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 b4e2790..9fcb829 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
@@ -360,19 +360,21 @@ 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;
+ // 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
+ // 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).
@@ -385,8 +387,9 @@ public class LinearLayout extends ViewGroup {
}
final int childHeight = child.getMeasuredHeight();
- mTotalLength += childHeight + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+ lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
@@ -459,8 +462,10 @@ public class LinearLayout extends ViewGroup {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
- mTotalLength += largestChildHeight + lp.topMargin+ lp.bottomMargin +
- getNextLocationOffset(child);
+ // Account for negative margins
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
@@ -536,12 +541,14 @@ public class LinearLayout extends ViewGroup {
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);
@@ -651,7 +658,8 @@ 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;
+ 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).
@@ -666,7 +674,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;
@@ -686,8 +695,9 @@ public class LinearLayout extends ViewGroup {
}
final int childWidth = child.getMeasuredWidth();
- mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
- getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+ lp.rightMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildWidth = Math.max(childWidth, largestChildWidth);
@@ -772,8 +782,9 @@ public class LinearLayout extends ViewGroup {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
- mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
- getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
@@ -843,8 +854,9 @@ public class LinearLayout extends ViewGroup {
}
}
- mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child);
+ 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.MATCH_PARENT;
@@ -875,6 +887,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
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 401e7ff..2feed03 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -16,14 +16,17 @@
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;
@@ -31,16 +34,13 @@ 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;
/*
@@ -2722,7 +2722,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.
*/
@@ -2916,7 +2917,14 @@ public class ListView extends AbsListView {
if (!mStackFromBottom) {
int bottom;
- int listBottom = mBottom - mTop - mListPadding.bottom;
+ int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
+
+ // Draw top divider for overscroll
+ if (count > 0 && mScrollY < 0) {
+ bounds.bottom = 0;
+ bounds.top = -dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
for (int i = 0; i < count; i++) {
if ((headerDividers || first + i >= headerCount) &&
@@ -3084,9 +3092,9 @@ public class ListView extends AbsListView {
previouslyFocusedRect.offset(mScrollX, mScrollY);
final ListAdapter adapter = mAdapter;
- final int firstPosition = mFirstPosition;
- // Don't cache the result of getChildCount here, it could change in layoutChildren.
- if (adapter.getCount() < getChildCount() + firstPosition) {
+ // Don't cache the result of getChildCount or mFirstPosition here,
+ // it could change in layoutChildren.
+ if (adapter.getCount() < getChildCount() + mFirstPosition) {
mLayoutMode = LAYOUT_NORMAL;
layoutChildren();
}
@@ -3096,6 +3104,7 @@ public class ListView extends AbsListView {
Rect otherRect = mTempRect;
int minDistance = Integer.MAX_VALUE;
final int childCount = getChildCount();
+ final int firstPosition = mFirstPosition;
for (int i = 0; i < childCount; i++) {
// only consider selectable views
@@ -3300,9 +3309,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) {
@@ -3385,10 +3394,11 @@ 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_SINGLE}.
+ *
+ * @return A new array which contains the id of each checked item in the
+ * list.
*/
public long[] getCheckItemIds() {
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
@@ -3397,11 +3407,23 @@ public class ListView extends AbsListView {
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];
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2d36bc8..fd18db4 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -39,6 +39,7 @@ import com.android.internal.R;
* A view for selecting a number
*
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ * @hide
*/
@Widget
public class NumberPicker extends LinearLayout {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 4a1d871..52ed11d 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -459,7 +459,8 @@ public class ScrollView extends FrameLayout {
final int deltaY = (int) (mLastMotionY - y);
mLastMotionY = y;
- super.scrollTo(mScrollX, mScrollY + deltaY);
+ overscrollBy(0, deltaY, 0, mScrollY, 0, getScrollRange(),
+ 0, getOverscrollMax());
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
@@ -470,8 +471,7 @@ public class ScrollView extends FrameLayout {
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
fling(-initialVelocity);
} else {
- final int bottom = Math.max(0, getChildAt(0).getHeight() -
- (getHeight() - mPaddingBottom - mPaddingTop));
+ final int bottom = getScrollRange();
if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
invalidate();
}
@@ -485,6 +485,41 @@ public class ScrollView extends FrameLayout {
}
return true;
}
+
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ mScrollX = scrollX;
+ mScrollY = scrollY;
+ if (clampedY) {
+ mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
+ }
+ } else {
+ super.scrollTo(scrollX, scrollY);
+ }
+ }
+
+ private int getOverscrollMax() {
+ int childCount = getChildCount();
+ int containerOverscroll = (getHeight() - mPaddingBottom - mPaddingTop) / 3;
+ if (childCount > 0) {
+ return Math.min(containerOverscroll, getChildAt(0).getHeight() / 3);
+ } else {
+ return containerOverscroll;
+ }
+ }
+
+ 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>
@@ -857,10 +892,28 @@ 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;
+ }
+
+ int scrollRange = getChildAt(0).getBottom();
+ final int scrollY = mScrollY;
+ final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
+ if (scrollY < 0) {
+ scrollRange -= scrollY;
+ } else if (scrollY > overscrollBottom) {
+ scrollRange += scrollY - overscrollBottom;
+ }
+
+ return scrollRange;
}
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return Math.max(0, super.computeVerticalScrollOffset());
+ }
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
@@ -915,10 +968,9 @@ public class ScrollView extends FrameLayout {
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
- mScrollX = x;
- mScrollY = y;
-
- if (oldX != mScrollX || oldY != mScrollY) {
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
+ 0, getOverscrollMax());
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 78e2fee..d4d9063 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
@@ -624,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/TableLayout.java b/core/java/android/widget/TableLayout.java
index 66500a3..73760ac 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -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);
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index abf08bf..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;
/**
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/com/android/internal/app/DisableCarModeActivity.java b/core/java/com/android/internal/app/DisableCarModeActivity.java
new file mode 100644
index 0000000..95dc1f9
--- /dev/null
+++ b/core/java/com/android/internal/app/DisableCarModeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.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();
+ } 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 2b07ae6..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;
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 726e28f..c0e9587 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -25,4 +25,5 @@ interface IMediaContainerService {
String key, String resFileName);
boolean copyResource(in Uri packageURI,
in ParcelFileDescriptor outStream);
+ int getRecommendedInstallLocation(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/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index c110f95..2f48499 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -32,7 +32,7 @@ import android.os.RemoteException;
import android.os.Power;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.IMountService;
+import android.os.storage.IMountService;
import com.android.internal.telephony.ITelephony;
import android.util.Log;
diff --git a/core/java/com/android/internal/app/TetherActivity.java b/core/java/com/android/internal/app/TetherActivity.java
index 2b93dbc..a48ccf9 100644
--- a/core/java/com/android/internal/app/TetherActivity.java
+++ b/core/java/com/android/internal/app/TetherActivity.java
@@ -25,7 +25,6 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IMountService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -33,16 +32,19 @@ import android.widget.Toast;
import android.util.Log;
/**
- * This activity is shown to the user for him/her to connect/disconnect a Tether
- * connection. It will display notification when a suitable connection is made
- * to allow the tether to be setup. A second notification will be show when a
- * tether is active, allowing the user to manage tethered connections.
+ * This activity is shown to the user in two cases: when a connection is possible via
+ * a usb tether and when any type of tether is connected. In the connecting case
+ * It allows them to start a USB tether. In the Tethered/disconnecting case it
+ * will disconnect all tethers.
*/
public class TetherActivity extends AlertActivity implements
DialogInterface.OnClickListener {
private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+ // count of the number of tethered connections at activity create time.
+ private int mTethered;
+
/* Used to detect when the USB cable is unplugged, so we can call finish() */
private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
@Override
@@ -53,8 +55,6 @@ public class TetherActivity extends AlertActivity implements
}
};
- private boolean mWantTethering;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -62,17 +62,18 @@ public class TetherActivity extends AlertActivity implements
// determine if we advertise tethering or untethering
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm.getTetheredIfaces().length > 0) {
- mWantTethering = false;
- } else if (cm.getTetherableIfaces().length > 0) {
- mWantTethering = true;
- } else {
+ mTethered = cm.getTetheredIfaces().length;
+ int tetherable = cm.getTetherableIfaces().length;
+ if ((mTethered == 0) && (tetherable == 0)) {
finish();
return;
}
- // Set up the "dialog"
- if (mWantTethering == true) {
+ // Set up the dialog
+ // if we have a tethered connection we put up a "Do you want to Disconect" dialog
+ // otherwise we must have a tetherable interface (else we'd return above)
+ // and so we want to put up the "do you want to connect" dialog
+ if (mTethered == 0) {
mAlertParams.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
mAlertParams.mTitle = getString(com.android.internal.R.string.tether_title);
mAlertParams.mMessage = getString(com.android.internal.R.string.tether_message);
@@ -115,17 +116,36 @@ public class TetherActivity extends AlertActivity implements
* {@inheritDoc}
*/
public void onClick(DialogInterface dialog, int which) {
+ boolean error = false;
if (which == POSITIVE_BUTTON) {
- ConnectivityManager connManager =
+ ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
// start/stop tethering
- if (mWantTethering) {
- if (!connManager.tether("ppp0")) {
+ String[] tethered = cm.getTetheredIfaces();
+
+ if (tethered.length == 0) {
+ String[] tetherable = cm.getTetherableIfaces();
+ String[] usbRegexs = cm.getTetherableUsbRegexs();
+ for (String t : tetherable) {
+ for (String r : usbRegexs) {
+ if (t.matches(r)) {
+ if (!cm.tether(t))
+ error = true;
+ break;
+ }
+ }
+ }
+ if (error) {
showTetheringError();
}
} else {
- if (!connManager.untether("ppp0")) {
+ for (String t : tethered) {
+ if (!cm.untether("ppp0")) {
+ error = true;
+ }
+ }
+ if (error) {
showUnTetheringError();
}
}
@@ -135,7 +155,12 @@ public class TetherActivity extends AlertActivity implements
}
private void handleTetherStateChanged(Intent intent) {
- finish();
+ // determine if we advertise tethering or untethering
+ ConnectivityManager cm =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (mTethered != cm.getTetheredIfaces().length) {
+ finish();
+ }
}
private void showTetheringError() {
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 34ae2b4..0000000
--- a/core/java/com/android/internal/app/UsbStorageActivity.java
+++ /dev/null
@@ -1,191 +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.Activity;
-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.Environment;
-import android.os.IMountService;
-import android.os.MountServiceResultCode;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.widget.ImageView;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-import android.view.View;
-
-/**
- * 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 Activity {
- private Button mMountButton;
- private Button mUnmountButton;
- private TextView mBanner;
- private TextView mMessage;
- private ImageView mIcon;
-
- /** 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);
-
- setTitle(getString(com.android.internal.R.string.usb_storage_activity_title));
-
- setContentView(com.android.internal.R.layout.usb_storage_activity);
-
- mIcon = (ImageView) findViewById(com.android.internal.R.id.icon);
- mBanner = (TextView) findViewById(com.android.internal.R.id.banner);
- mMessage = (TextView) findViewById(com.android.internal.R.id.message);
-
- mMountButton = (Button) findViewById(com.android.internal.R.id.mount_button);
- mMountButton.setOnClickListener(
- new View.OnClickListener() {
- public void onClick(View v) {
- mountAsUsbStorage();
- // TODO: replace with forthcoming MountService callbacks
- switchDisplay(true);
- }
- });
-
- mUnmountButton = (Button) findViewById(com.android.internal.R.id.unmount_button);
- mUnmountButton.setOnClickListener(
- new View.OnClickListener() {
- public void onClick(View v) {
- stopUsbStorage();
- // TODO: replace with forthcoming MountService callbacks
- switchDisplay(false);
- }
- });
- }
-
- private void switchDisplay(boolean usbStorageInUse) {
- if (usbStorageInUse) {
- mUnmountButton.setVisibility(View.VISIBLE);
- mMountButton.setVisibility(View.GONE);
- mIcon.setImageResource(com.android.internal.R.drawable.usb_android_connected);
- mBanner.setText(com.android.internal.R.string.usb_storage_stop_title);
- mMessage.setText(com.android.internal.R.string.usb_storage_stop_message);
- } else {
- mUnmountButton.setVisibility(View.GONE);
- mMountButton.setVisibility(View.VISIBLE);
- mIcon.setImageResource(com.android.internal.R.drawable.usb_android);
- mBanner.setText(com.android.internal.R.string.usb_storage_title);
- mMessage.setText(com.android.internal.R.string.usb_storage_message);
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-
- boolean umsOn = false;
- try {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService != null) {
- umsOn = mountService.getVolumeShared(
- Environment.getExternalStorageDirectory().getPath(), "ums");
- }
- } catch (android.os.RemoteException exc) {
- // pass
- }
- switchDisplay(umsOn);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- unregisterReceiver(mBatteryReceiver);
- }
-
- private void mountAsUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showSharingError();
- return;
- }
-
- try {
- if (mountService.shareVolume(
- Environment.getExternalStorageDirectory().getPath(), "ums") !=
- MountServiceResultCode.OperationSucceeded) {
- showSharingError();
- }
- } catch (RemoteException e) {
- showSharingError();
- }
- }
-
- private void stopUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showStoppingError();
- return;
- }
-
- try {
- mountService.unshareVolume(
- Environment.getExternalStorageDirectory().getPath(), "ums");
- } 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 showSharingError() {
- Toast.makeText(this, com.android.internal.R.string.usb_storage_error_message,
- Toast.LENGTH_LONG).show();
- }
-
- 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/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
new file mode 100644
index 0000000..bc7dbf4
--- /dev/null
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+ private static final boolean DEBUG_SD_INSTALL = true;
+ private static final String TAG = "PackageHelper";
+
+ 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/(1024*1024));
+ if ((len - (mbLen * 1024 * 1024)) > 0) {
+ mbLen++;
+ }
+ if (DEBUG_SD_INSTALL) Log.i(TAG, "Size of resource " + mbLen);
+
+ try {
+ int rc = mountService.createSecureContainer(
+ cid, mbLen, "vfat", sdEncKey, uid);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, "Failed to create secure container " + cid);
+ return null;
+ }
+ String cachePath = mountService.getSecureContainerPath(cid);
+ if (DEBUG_SD_INSTALL) 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, false);
+ 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 {
+ int rc = getMountService().destroySecureContainer(cid, false);
+ 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/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 57a28e6..c134d88 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -22,7 +22,6 @@ import android.app.IActivityManager;
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;
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/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index a1c5078..b4af79c 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -1021,7 +1021,7 @@ public class HierarchicalStateMachine {
* @param msg that couldn't be handled.
*/
protected void unhandledMessage(Message msg) {
- Log.e(TAG, "unhandledMessage: msg.what=" + msg.what);
+ Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
}
/**
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 15dcbd6..22c6e79 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -1,5 +1,6 @@
package com.android.internal.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -17,7 +18,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/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..c2862b0
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -0,0 +1,229 @@
+/*
+ * 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.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.provider.Settings;
+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 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;
+
+ public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
+ mContext = context;
+ mTargetView = targetView;
+ mKeyboardView = keyboardView;
+ createKeyboards();
+ mKeyboardView.setOnKeyboardActionListener(this);
+ }
+
+ 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 keyEventCode) {
+ Handler handler = mTargetView.getHandler();
+ KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.ALPHA).getEvents(
+ new char[] { (char) keyEventCode });
+ if (events != null) {
+ for (KeyEvent event : events) {
+ 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();
+ }
+ }
+ }
+
+ 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) {
+ // TODO: vibration support.
+ }
+
+ 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/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java
index b90204e..3d09f08 100644
--- a/core/java/com/android/internal/widget/WeightedLinearLayout.java
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -52,7 +52,8 @@ public class WeightedLinearLayout extends LinearLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+ final int screenWidth = metrics.widthPixels;
+ final boolean isPortrait = screenWidth < metrics.heightPixels;
final int widthMode = getMode(widthMeasureSpec);
@@ -62,14 +63,13 @@ public class WeightedLinearLayout extends LinearLayout {
int height = getMeasuredHeight();
boolean measure = false;
- final int widthSize = getSize(widthMeasureSpec);
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 < (widthSize * widthWeight)) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * widthWeight),
+ if (width < (screenWidth * widthWeight)) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (screenWidth * widthWeight),
EXACTLY);
measure = true;
}
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;
- }
-};