summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountAuthenticatorActivity.java2
-rw-r--r--core/java/android/accounts/AccountManager.java3
-rw-r--r--core/java/android/accounts/AccountManagerService.java12
-rw-r--r--core/java/android/accounts/ChooseAccountActivity.java8
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java17
-rw-r--r--core/java/android/animation/AnimatorInflater.java6
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java8
-rw-r--r--core/java/android/app/ActionBar.java87
-rw-r--r--core/java/android/app/Activity.java68
-rw-r--r--core/java/android/app/ActivityGroup.java2
-rw-r--r--core/java/android/app/ActivityManager.java147
-rw-r--r--core/java/android/app/ActivityManagerNative.java89
-rw-r--r--core/java/android/app/ActivityThread.java131
-rw-r--r--core/java/android/app/ApplicationPackageManager.java59
-rw-r--r--core/java/android/app/ApplicationThreadNative.java10
-rw-r--r--core/java/android/app/BackStackRecord.java78
-rw-r--r--core/java/android/app/Fragment.java47
-rw-r--r--core/java/android/app/FragmentManager.java97
-rw-r--r--core/java/android/app/FragmentTransaction.java39
-rw-r--r--core/java/android/app/IActivityManager.java14
-rw-r--r--core/java/android/app/IApplicationThread.java4
-rw-r--r--core/java/android/app/IThumbnailRetriever.aidl24
-rw-r--r--core/java/android/app/Instrumentation.java1
-rw-r--r--core/java/android/app/KeyguardManager.java20
-rw-r--r--core/java/android/app/ListFragment.java6
-rw-r--r--core/java/android/app/LoaderManager.java8
-rw-r--r--core/java/android/app/NotificationManager.java3
-rw-r--r--core/java/android/app/Service.java20
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java3
-rw-r--r--core/java/android/app/backup/WallpaperBackupHelper.java7
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java9
-rw-r--r--core/java/android/content/AsyncTaskLoader.java19
-rw-r--r--core/java/android/content/ContentResolver.java5
-rw-r--r--core/java/android/content/ContentService.java5
-rw-r--r--core/java/android/content/Intent.java17
-rw-r--r--core/java/android/content/IntentFilter.java9
-rw-r--r--core/java/android/content/Loader.java15
-rw-r--r--core/java/android/content/pm/ActivityInfo.java37
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageManager.java204
-rw-r--r--core/java/android/content/pm/PackageParser.java23
-rw-r--r--core/java/android/content/pm/ServiceInfo.java20
-rw-r--r--core/java/android/content/pm/UserInfo.aidl20
-rw-r--r--core/java/android/content/pm/UserInfo.java105
-rw-r--r--core/java/android/content/res/AssetManager.java6
-rw-r--r--core/java/android/content/res/Configuration.java178
-rwxr-xr-xcore/java/android/content/res/Resources.java3
-rw-r--r--core/java/android/content/res/StringBlock.java3
-rw-r--r--core/java/android/database/AbstractCursor.java3
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java3
-rw-r--r--core/java/android/database/DatabaseUtils.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java19
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java86
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java23
-rw-r--r--core/java/android/ddm/DdmHandleAppName.java3
-rw-r--r--core/java/android/ddm/DdmHandleExit.java3
-rw-r--r--core/java/android/ddm/DdmHandleHeap.java17
-rw-r--r--core/java/android/ddm/DdmHandleHello.java11
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java9
-rw-r--r--core/java/android/ddm/DdmHandleThread.java3
-rw-r--r--core/java/android/ddm/DdmRegister.java3
-rw-r--r--core/java/android/hardware/Camera.java247
-rw-r--r--core/java/android/hardware/Sensor.java12
-rw-r--r--core/java/android/hardware/SensorEvent.java8
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java33
-rw-r--r--core/java/android/net/ConnectivityManager.java11
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java16
-rw-r--r--core/java/android/net/SntpClient.java7
-rw-r--r--core/java/android/net/http/Headers.java5
-rw-r--r--core/java/android/net/http/HttpLog.java3
-rw-r--r--core/java/android/net/http/HttpsConnection.java6
-rw-r--r--core/java/android/os/AsyncTask.java11
-rw-r--r--core/java/android/os/BatteryStats.java497
-rw-r--r--core/java/android/os/Binder.java38
-rw-r--r--core/java/android/os/Build.java5
-rw-r--r--core/java/android/os/Debug.java9
-rw-r--r--core/java/android/os/IBinder.java10
-rw-r--r--core/java/android/os/Looper.java1
-rw-r--r--core/java/android/os/MemoryFile.java7
-rw-r--r--core/java/android/os/MessageQueue.java3
-rw-r--r--core/java/android/os/Parcel.java2
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java78
-rw-r--r--core/java/android/os/PowerManager.java26
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/RecoverySystem.java7
-rw-r--r--core/java/android/os/storage/IMountService.java29
-rw-r--r--core/java/android/os/storage/StorageManager.java26
-rw-r--r--core/java/android/pim/ICalendar.java3
-rw-r--r--core/java/android/pim/RecurrenceSet.java5
-rw-r--r--core/java/android/preference/MultiSelectListPreference.java6
-rw-r--r--core/java/android/preference/Preference.java15
-rw-r--r--core/java/android/preference/PreferenceActivity.java123
-rw-r--r--core/java/android/preference/PreferenceFragment.java11
-rw-r--r--core/java/android/provider/Calendar.java49
-rw-r--r--core/java/android/provider/ContactsContract.java21
-rw-r--r--core/java/android/provider/MediaStore.java11
-rw-r--r--core/java/android/provider/Settings.java38
-rw-r--r--core/java/android/provider/Telephony.java19
-rw-r--r--core/java/android/server/BluetoothA2dpService.java14
-rw-r--r--core/java/android/server/BluetoothAdapterProperties.java3
-rw-r--r--core/java/android/server/BluetoothBondState.java86
-rw-r--r--core/java/android/speech/RecognitionListener.java3
-rw-r--r--core/java/android/speech/RecognizerIntent.java39
-rw-r--r--core/java/android/speech/SpeechRecognizer.java16
-rw-r--r--core/java/android/speech/srec/Recognizer.java1
-rw-r--r--core/java/android/speech/tts/BlockingMediaPlayer.java146
-rw-r--r--core/java/android/speech/tts/FileSynthesisRequest.java251
-rwxr-xr-xcore/java/android/speech/tts/ITextToSpeechCallback.aidl (renamed from core/java/android/speech/tts/ITtsCallback.aidl)8
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl140
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl69
-rw-r--r--core/java/android/speech/tts/PlaybackSynthesisRequest.java322
-rw-r--r--core/java/android/speech/tts/SynthesisRequest.java193
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java1426
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java714
-rw-r--r--core/java/android/text/CharSequenceIterator.java100
-rw-r--r--core/java/android/text/GraphicsOperations.java7
-rw-r--r--core/java/android/text/Selection.java46
-rw-r--r--core/java/android/text/SpannableStringBuilder.java29
-rw-r--r--core/java/android/text/StaticLayout.java2
-rw-r--r--core/java/android/text/TextLine.java21
-rw-r--r--core/java/android/text/TextUtils.java76
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java20
-rw-r--r--core/java/android/text/method/BaseMovementMethod.java22
-rw-r--r--core/java/android/text/method/WordIterator.java220
-rw-r--r--core/java/android/text/style/SuggestionSpan.aidl19
-rw-r--r--core/java/android/text/style/SuggestionSpan.java157
-rw-r--r--core/java/android/util/Config.java60
-rw-r--r--core/java/android/util/JsonReader.java19
-rw-r--r--core/java/android/util/LruCache.java3
-rw-r--r--core/java/android/util/TimeUtils.java2
-rw-r--r--core/java/android/util/Xml.java40
-rw-r--r--core/java/android/view/Display.java141
-rw-r--r--core/java/android/view/GLES20Canvas.java145
-rw-r--r--core/java/android/view/GestureDetector.java17
-rw-r--r--core/java/android/view/HardwareRenderer.java20
-rw-r--r--core/java/android/view/IWindowManager.aidl4
-rwxr-xr-xcore/java/android/view/InputDevice.java4
-rwxr-xr-xcore/java/android/view/InputEvent.java46
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java730
-rw-r--r--core/java/android/view/KeyCharacterMap.java2
-rwxr-xr-xcore/java/android/view/KeyEvent.java97
-rw-r--r--core/java/android/view/MotionEvent.java75
-rwxr-xr-xcore/java/android/view/OrientationEventListener.java3
-rw-r--r--core/java/android/view/PointerIcon.aidl19
-rw-r--r--core/java/android/view/PointerIcon.java435
-rw-r--r--core/java/android/view/ScaleGestureDetector.java23
-rw-r--r--core/java/android/view/SurfaceView.java3
-rw-r--r--core/java/android/view/VelocityTracker.java335
-rw-r--r--core/java/android/view/View.java329
-rw-r--r--core/java/android/view/ViewConfiguration.java22
-rw-r--r--core/java/android/view/ViewDebug.java21
-rw-r--r--core/java/android/view/ViewGroup.java576
-rw-r--r--core/java/android/view/ViewParent.java19
-rw-r--r--core/java/android/view/ViewRoot.java192
-rw-r--r--core/java/android/view/WindowManagerImpl.java3
-rw-r--r--core/java/android/view/WindowManagerPolicy.java15
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java475
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java38
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java415
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java2
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java5
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java3
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java7
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java50
-rw-r--r--core/java/android/webkit/BrowserFrame.java17
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java4
-rw-r--r--core/java/android/webkit/WebHistoryItem.java2
-rw-r--r--core/java/android/webkit/WebSettings.java14
-rw-r--r--core/java/android/webkit/WebTextView.java3
-rw-r--r--core/java/android/webkit/WebView.java36
-rw-r--r--core/java/android/webkit/webdriver/By.java252
-rw-r--r--core/java/android/webkit/webdriver/WebDriver.java551
-rw-r--r--core/java/android/webkit/webdriver/WebDriverException.java38
-rw-r--r--core/java/android/webkit/webdriver/WebElement.java255
-rw-r--r--core/java/android/webkit/webdriver/WebElementNotFoundException.java41
-rw-r--r--core/java/android/webkit/webdriver/WebElementStaleException.java42
-rw-r--r--core/java/android/webkit/webdriver/WebchromeClientWrapper.java193
-rw-r--r--core/java/android/widget/AbsListView.java17
-rw-r--r--core/java/android/widget/AbsSeekBar.java3
-rw-r--r--core/java/android/widget/AdapterView.java28
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java66
-rw-r--r--core/java/android/widget/CheckedTextView.java9
-rw-r--r--core/java/android/widget/CompoundButton.java26
-rw-r--r--core/java/android/widget/CursorAdapter.java3
-rw-r--r--core/java/android/widget/CursorTreeAdapter.java3
-rw-r--r--core/java/android/widget/DatePicker.java89
-rw-r--r--core/java/android/widget/FrameLayout.java3
-rw-r--r--core/java/android/widget/LinearLayout.java139
-rw-r--r--core/java/android/widget/ListView.java62
-rw-r--r--core/java/android/widget/ProgressBar.java54
-rw-r--r--core/java/android/widget/RemoteViews.java106
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java21
-rw-r--r--core/java/android/widget/RemoteViewsService.java71
-rw-r--r--core/java/android/widget/StackView.java44
-rw-r--r--core/java/android/widget/TabWidget.java15
-rw-r--r--core/java/android/widget/TextView.java1078
-rw-r--r--core/java/android/widget/TimePicker.java5
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java174
-rwxr-xr-xcore/java/com/android/internal/app/IMediaContainerService.aidl5
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java3
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java3
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java11
-rw-r--r--core/java/com/android/internal/net/DomainNameValidator.java3
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java243
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java1
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java7
-rw-r--r--core/java/com/android/internal/os/SamplingProfilerIntegration.java57
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java92
-rw-r--r--core/java/com/android/internal/util/AsyncChannel.java35
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java2
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl1
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java2
-rw-r--r--core/java/com/android/internal/view/StandaloneActionMode.java2
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java11
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java441
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuView.java345
-rw-r--r--core/java/com/android/internal/view/menu/BaseMenuPresenter.java195
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java25
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java4
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuPresenter.java141
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuView.java99
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java16
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuPresenter.java202
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java759
-rw-r--r--core/java/com/android/internal/view/menu/MenuDialogHelper.java48
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java189
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java148
-rw-r--r--core/java/com/android/internal/view/menu/MenuPresenter.java110
-rw-r--r--core/java/com/android/internal/view/menu/MenuView.java14
-rw-r--r--core/java/com/android/internal/view/menu/SubMenuBuilder.java6
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java40
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java26
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java131
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java1
-rw-r--r--core/java/com/android/internal/widget/IRemoteViewsFactory.aidl2
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java6
-rw-r--r--core/java/com/google/android/mms/pdu/EncodedStringValue.java3
-rwxr-xr-xcore/java/com/google/android/mms/pdu/PduParser.java3
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java3
-rw-r--r--core/java/com/google/android/mms/util/AbstractCache.java3
-rw-r--r--core/java/com/google/android/mms/util/PduCache.java3
247 files changed, 13592 insertions, 5173 deletions
diff --git a/core/java/android/accounts/AccountAuthenticatorActivity.java b/core/java/android/accounts/AccountAuthenticatorActivity.java
index 5cce6da..6a55ddf 100644
--- a/core/java/android/accounts/AccountAuthenticatorActivity.java
+++ b/core/java/android/accounts/AccountAuthenticatorActivity.java
@@ -26,7 +26,7 @@ import android.os.Bundle;
* to handle the request then it can have the activity extend AccountAuthenticatorActivity.
* The AbstractAccountAuthenticator passes in the response to the intent using the following:
* <pre>
- * intent.putExtra(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY, response);
+ * intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
* </pre>
* The activity then sets the result that is to be handed to the response via
* {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 5bdc79d..2156425 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -179,6 +179,7 @@ public class AccountManager {
public static final String KEY_PASSWORD = "password";
public static final String KEY_ACCOUNTS = "accounts";
+
public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -1269,7 +1270,7 @@ public class AccountManager {
/** Handles the responses from the AccountManager */
private class Response extends IAccountManagerResponse.Stub {
public void onResult(Bundle bundle) {
- Intent intent = bundle.getParcelable("intent");
+ Intent intent = bundle.getParcelable(KEY_INTENT);
if (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
// that we see
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 93983a6..20d5b96 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -37,6 +37,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.RegisteredServicesCacheListener;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
@@ -73,8 +74,7 @@ import java.util.concurrent.atomic.AtomicReference;
* accounts on the device. Some of these calls are implemented with the help of the corresponding
* {@link IAccountAuthenticator} services. This service is not accessed by users directly,
* instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
- * AccountManager accountManager =
- * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
+ * AccountManager accountManager = AccountManager.get(context);
* @hide
*/
public class AccountManagerService
@@ -1064,14 +1064,18 @@ public class AccountManagerService
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("unknown account type: " + accountType);
}
- return authContext.getString(serviceInfo.type.labelId);
+ try {
+ return authContext.getString(serviceInfo.type.labelId);
+ } catch (Resources.NotFoundException e) {
+ throw new IllegalArgumentException("unknown account type: " + accountType);
+ }
}
private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
- // See FLAT_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
+ // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
// Since it was set in Eclair+ we can't change it without breaking apps using
// the intent from a non-Activity context.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 293df78..bfbae24 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -18,6 +18,7 @@ package android.accounts;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
@@ -103,7 +104,12 @@ public class ChooseAccountActivity extends Activity {
} catch (PackageManager.NameNotFoundException e) {
// Nothing we can do much here, just log
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No icon for account type " + accountType);
+ Log.w(TAG, "No icon name for account type " + accountType);
+ }
+ } catch (Resources.NotFoundException e) {
+ // Nothing we can do much here, just log
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "No icon resource for account type " + accountType);
}
}
}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 89eee6d..0ee683c 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -58,6 +58,12 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
// Grant 'account'/'type' to mUID
mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
@@ -73,8 +79,15 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
return;
}
- final String accountTypeLabel = accountManagerService.getAccountLabel(mAccount.type);
-
+ String accountTypeLabel;
+ try {
+ accountTypeLabel = accountManagerService.getAccountLabel(mAccount.type);
+ } catch (IllegalArgumentException e) {
+ // label or resource was missing. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
final TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
authTokenTypeView.setVisibility(View.GONE);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index bcab66e..ed4036d 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -31,11 +31,11 @@ import java.io.IOException;
import java.util.ArrayList;
/**
- * This class is used to instantiate menu XML files into Animator objects.
+ * This class is used to instantiate animator XML files into Animator objects.
* <p>
- * For performance reasons, menu inflation relies heavily on pre-processing of
+ * For performance reasons, inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
- * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*/
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f562851..1dcaa04 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1173,13 +1173,11 @@ public class ValueAnimator extends Animator {
if (oldValues != null) {
int numValues = oldValues.length;
anim.mValues = new PropertyValuesHolder[numValues];
- for (int i = 0; i < numValues; ++i) {
- anim.mValues[i] = oldValues[i].clone();
- }
anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
- PropertyValuesHolder valuesHolder = mValues[i];
- anim.mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+ PropertyValuesHolder newValuesHolder = oldValues[i].clone();
+ anim.mValues[i] = newValuesHolder;
+ anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
}
}
return anim;
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index fc5fac6..cac06ec 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -107,6 +107,18 @@ public abstract class ActionBar {
public static final int DISPLAY_SHOW_CUSTOM = 0x10;
/**
+ * Disable the 'home' element. This may be combined with
+ * {@link #DISPLAY_SHOW_HOME} to create a non-focusable/non-clickable
+ * 'home' element. Useful for a level of your app's navigation hierarchy
+ * where clicking 'home' doesn't do anything.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayDisableHomeEnabled(boolean)
+ */
+ public static final int DISPLAY_DISABLE_HOME = 0x20;
+
+ /**
* Set the action bar into custom navigation mode, supplying a view
* for custom navigation.
*
@@ -160,6 +172,66 @@ public abstract class ActionBar {
public abstract void setCustomView(int resId);
/**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(int resId);
+
+ /**
+ * Set the icon to display in the 'home' section of the action bar.
+ * The action bar will use an icon specified by its style or the
+ * activity icon by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param icon Drawable to show as an icon.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setIcon(Drawable icon);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param resId Resource ID of a drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(int resId);
+
+ /**
+ * Set the logo to display in the 'home' section of the action bar.
+ * The action bar will use a logo specified by its style or the
+ * activity logo by default.
+ *
+ * Whether the home section shows an icon or logo is controlled
+ * by the display option {@link #DISPLAY_USE_LOGO}.
+ *
+ * @param logo Drawable to show as a logo.
+ *
+ * @see #setDisplayUseLogoEnabled(boolean)
+ * @see #setDisplayShowHomeEnabled(boolean)
+ */
+ public abstract void setLogo(Drawable logo);
+
+ /**
* Set the adapter and navigation callback for list navigation mode.
*
* The supplied adapter will provide views for the expanded list as well as
@@ -333,6 +405,21 @@ public abstract class ActionBar {
public abstract void setDisplayShowCustomEnabled(boolean showCustom);
/**
+ * Set whether the 'home' affordance on the action bar should be disabled.
+ * If set, the 'home' element will not be focusable or clickable, useful if
+ * the user is at the top level of the app's navigation hierarchy.
+ *
+ * <p>To set several display options at once, see the setDisplayOptions methods.
+ *
+ * @param disableHome true to disable the 'home' element.
+ *
+ * @see #setDisplayOptions(int)
+ * @see #setDisplayOptions(int, int)
+ * @see #DISPLAY_DISABLE_HOME
+ */
+ public abstract void setDisplayDisableHomeEnabled(boolean disableHome);
+
+ /**
* Set the ActionBar's background.
*
* @param d Background drawable
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index edfd6ef..f5849c2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -51,7 +51,6 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
@@ -819,18 +818,6 @@ public class Activity extends ContextThemeWrapper
return mWindow != null ? mWindow.getCurrentFocus() : null;
}
- @Override
- public int getWallpaperDesiredMinimumWidth() {
- int width = super.getWallpaperDesiredMinimumWidth();
- return width <= 0 ? getWindowManager().getDefaultDisplay().getWidth() : width;
- }
-
- @Override
- public int getWallpaperDesiredMinimumHeight() {
- int height = super.getWallpaperDesiredMinimumHeight();
- return height <= 0 ? getWindowManager().getDefaultDisplay().getHeight() : height;
- }
-
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
@@ -1438,6 +1425,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
@@ -1454,12 +1445,17 @@ public class Activity extends ContextThemeWrapper
* @return Returns the object previously returned by
* {@link #onRetainNonConfigurationInstance()}.
*/
+ @Deprecated
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
/**
+ * @deprecated Use the new {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Called by the system, as part of destroying an
* activity due to a configuration change, when it is known that a new
* instance will immediately be created for the new configuration. You
@@ -1675,6 +1671,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* This method allows the activity to take care of managing the given
* {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
* That is, when the activity is stopped it will automatically call
@@ -1690,8 +1690,6 @@ public class Activity extends ContextThemeWrapper
*
* @see #managedQuery(android.net.Uri , String[], String, String[], String)
* @see #stopManagingCursor
- *
- * @deprecated Use {@link CursorLoader} instead.
*/
@Deprecated
public void startManagingCursor(Cursor c) {
@@ -1701,6 +1699,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Given a Cursor that was previously given to
* {@link #startManagingCursor}, stop the activity's management of that
* cursor.
@@ -1708,8 +1710,6 @@ public class Activity extends ContextThemeWrapper
* @param c The Cursor that was being managed.
*
* @see #startManagingCursor
- *
- * @deprecated Use {@link CursorLoader} instead.
*/
@Deprecated
public void stopManagingCursor(Cursor c) {
@@ -2715,6 +2715,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Callback for creating dialogs that are managed (saved and restored) for you
* by the activity. The default implementation calls through to
* {@link #onCreateDialog(int)} for compatibility.
@@ -2743,6 +2747,7 @@ public class Activity extends ContextThemeWrapper
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
protected Dialog onCreateDialog(int id, Bundle args) {
return onCreateDialog(id);
}
@@ -2757,6 +2762,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Provides an opportunity to prepare a managed dialog before it is being
* shown. The default implementation calls through to
* {@link #onPrepareDialog(int, Dialog)} for compatibility.
@@ -2776,20 +2785,30 @@ public class Activity extends ContextThemeWrapper
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
onPrepareDialog(id, dialog);
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Simple version of {@link #showDialog(int, Bundle)} that does not
* take any arguments. Simply calls {@link #showDialog(int, Bundle)}
* with null arguments.
*/
+ @Deprecated
public final void showDialog(int id) {
showDialog(id, null);
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)}
* will be made with the same id the first time this is called for a given
* id. From thereafter, the dialog will be automatically saved and restored.
@@ -2815,6 +2834,7 @@ public class Activity extends ContextThemeWrapper
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
public final boolean showDialog(int id, Bundle args) {
if (mManagedDialogs == null) {
mManagedDialogs = new SparseArray<ManagedDialog>();
@@ -2836,6 +2856,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
*
* @param id The id of the managed dialog.
@@ -2848,6 +2872,7 @@ public class Activity extends ContextThemeWrapper
* @see #showDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
public final void dismissDialog(int id) {
if (mManagedDialogs == null) {
throw missingDialog(id);
@@ -2870,6 +2895,10 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Removes any internal references to a dialog managed by this Activity.
* If the dialog is showing, it will dismiss it as part of the clean up.
*
@@ -2887,6 +2916,7 @@ public class Activity extends ContextThemeWrapper
* @see #showDialog(int)
* @see #dismissDialog(int)
*/
+ @Deprecated
public final void removeDialog(int id) {
if (mManagedDialogs != null) {
final ManagedDialog md = mManagedDialogs.get(id);
@@ -3597,7 +3627,7 @@ public class Activity extends ContextThemeWrapper
resultCode = mResultCode;
resultData = mResultData;
}
- if (Config.LOGV) Log.v(TAG, "Finishing self: token=" + mToken);
+ if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
@@ -4533,7 +4563,7 @@ public class Activity extends ContextThemeWrapper
void dispatchActivityResult(String who, int requestCode,
int resultCode, Intent data) {
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ ", resCode=" + resultCode + ", data=" + data);
mFragments.noteStateNotSaved();
diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java
index f1216f9..5b04253 100644
--- a/core/java/android/app/ActivityGroup.java
+++ b/core/java/android/app/ActivityGroup.java
@@ -110,7 +110,7 @@ public class ActivityGroup extends Activity {
if (who != null) {
Activity act = mLocalActivityManager.getActivity(who);
/*
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ ", resCode=" + resultCode + ", data=" + data
+ ", rec=" + rec);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b6581e9..fca6868 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,9 @@
package android.app;
+import com.android.internal.app.IUsageStats;
+import com.android.internal.os.PkgUsageStats;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,18 +29,17 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Debug;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
-import com.android.internal.app.IUsageStats;
-import com.android.internal.os.PkgUsageStats;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -47,8 +49,7 @@ import java.util.Map;
*/
public class ActivityManager {
private static String TAG = "ActivityManager";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
private final Context mContext;
private final Handler mHandler;
@@ -204,13 +205,6 @@ public class ActivityManager {
public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
/**
- * Flag for use with {@link #getRecentTasks}: also return the thumbnail
- * bitmap (if available) for each recent task.
- * @hide
- */
- public static final int TASKS_GET_THUMBNAILS = 0x0001000;
-
- /**
* Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -240,7 +234,7 @@ public class ActivityManager {
/**
* Information you can retrieve about a particular task that is currently
* "running" in the system. Note that a running task does not mean the
- * given task actual has a process it is actively running in; it simply
+ * given task actually has a process it is actively running in; it simply
* means that the user has gone to it and never closed it, but currently
* the system may have killed its process and is only holding on to its
* last state in order to restart it when the user returns.
@@ -395,10 +389,118 @@ public class ActivityManager {
return getRunningTasks(maxNum, 0, null);
}
+ /**
+ * Remove some end of a task's activity stack that is not part of
+ * the main application. The selected activities will be finished, so
+ * they are no longer part of the main task.
+ *
+ * @param taskId The identifier of the task.
+ * @param subTaskIndex The number of the sub-task; this corresponds
+ * to the index of the thumbnail returned by {@link #getTaskThumbnails(int)}.
+ * @return Returns true if the sub-task was found and was removed.
+ *
+ * @hide
+ */
+ public boolean removeSubTask(int taskId, int subTaskIndex)
+ throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().removeSubTask(taskId, subTaskIndex);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
+ /**
+ * If set, the process of the root activity of the task will be killed
+ * as part of removing the task.
+ * @hide
+ */
+ public static final int REMOVE_TASK_KILL_PROCESS = 0x0001;
+
+ /**
+ * Completely remove the given task.
+ *
+ * @param taskId Identifier of the task to be removed.
+ * @param flags Additional operational flags. May be 0 or
+ * {@link #REMOVE_TASK_KILL_PROCESS}.
+ * @return Returns true if the given task was found and removed.
+ *
+ * @hide
+ */
+ public boolean removeTask(int taskId, int flags)
+ throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().removeTask(taskId, flags);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
+ /** @hide */
+ public static class TaskThumbnails implements Parcelable {
+ public Bitmap mainThumbnail;
+
+ public int numSubThumbbails;
+
+ /** @hide */
+ public IThumbnailRetriever retriever;
+
+ public TaskThumbnails() {
+ }
+
+ public Bitmap getSubThumbnail(int index) {
+ try {
+ return retriever.getThumbnail(index);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mainThumbnail != null) {
+ dest.writeInt(1);
+ mainThumbnail.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(numSubThumbbails);
+ dest.writeStrongInterface(retriever);
+ }
+
+ public void readFromParcel(Parcel source) {
+ if (source.readInt() != 0) {
+ mainThumbnail = Bitmap.CREATOR.createFromParcel(source);
+ } else {
+ mainThumbnail = null;
+ }
+ numSubThumbbails = source.readInt();
+ retriever = IThumbnailRetriever.Stub.asInterface(source.readStrongBinder());
+ }
+
+ public static final Creator<TaskThumbnails> CREATOR = new Creator<TaskThumbnails>() {
+ public TaskThumbnails createFromParcel(Parcel source) {
+ return new TaskThumbnails(source);
+ }
+ public TaskThumbnails[] newArray(int size) {
+ return new TaskThumbnails[size];
+ }
+ };
+
+ private TaskThumbnails(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
/** @hide */
- public Bitmap getTaskThumbnail(int id) throws SecurityException {
+ public TaskThumbnails getTaskThumbnails(int id) throws SecurityException {
try {
- return ActivityManagerNative.getDefault().getTaskThumbnail(id);
+ return ActivityManagerNative.getDefault().getTaskThumbnails(id);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
return null;
@@ -614,7 +716,7 @@ public class ActivityManager {
public List<RunningServiceInfo> getRunningServices(int maxNum)
throws SecurityException {
try {
- return (List<RunningServiceInfo>)ActivityManagerNative.getDefault()
+ return ActivityManagerNative.getDefault()
.getServices(maxNum, 0);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
@@ -1269,4 +1371,17 @@ public class ActivityManager {
return new HashMap<String, Integer>();
}
}
+
+ /**
+ * @param userid the user's id. Zero indicates the default user
+ * @hide
+ */
+ public boolean switchUser(int userid) {
+ try {
+ return ActivityManagerNative.getDefault().switchUser(userid);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6426635..4b09b34c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@ import android.os.Parcel;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.util.Singleton;
@@ -442,10 +441,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case GET_TASK_THUMBNAIL_TRANSACTION: {
+ case GET_TASK_THUMBNAILS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int id = data.readInt();
- Bitmap bm = getTaskThumbnail(id);
+ ActivityManager.TaskThumbnails bm = getTaskThumbnails(id);
reply.writeNoException();
if (bm != null) {
reply.writeInt(1);
@@ -1398,6 +1397,37 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SWITCH_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ boolean result = switchUser(userid);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case REMOVE_SUB_TASK_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int subTaskIndex = data.readInt();
+ boolean result = removeSubTask(taskId, subTaskIndex);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case REMOVE_TASK_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ int fl = data.readInt();
+ boolean result = removeTask(taskId, fl);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -1410,11 +1440,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
- if (Config.LOGV) {
+ if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
- if (Config.LOGV) {
+ if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
@@ -1831,16 +1861,16 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return list;
}
- public Bitmap getTaskThumbnail(int id) throws RemoteException {
+ public ActivityManager.TaskThumbnails getTaskThumbnails(int id) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(id);
- mRemote.transact(GET_TASK_THUMBNAIL_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_TASK_THUMBNAILS_TRANSACTION, data, reply, 0);
reply.readException();
- Bitmap bm = null;
+ ActivityManager.TaskThumbnails bm = null;
if (reply.readInt() != 0) {
- bm = Bitmap.CREATOR.createFromParcel(reply);
+ bm = ActivityManager.TaskThumbnails.CREATOR.createFromParcel(reply);
}
data.recycle();
reply.recycle();
@@ -3142,5 +3172,46 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public boolean switchUser(int userid) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(subTaskIndex);
+ mRemote.transact(REMOVE_SUB_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public boolean removeTask(int taskId, int flags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(flags);
+ mRemote.transact(REMOVE_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bd83762..4dfba91 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -45,6 +45,7 @@ import android.graphics.Canvas;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -59,7 +60,6 @@ import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -124,7 +124,7 @@ public final class ActivityThread {
public static final String TAG = "ActivityThread";
private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
private static final boolean DEBUG = false;
- static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean localLOGV = false;
static final boolean DEBUG_MESSAGES = false;
/** @hide */
public static final boolean DEBUG_BROADCAST = false;
@@ -334,6 +334,7 @@ public final class ActivityThread {
private static final class ServiceArgsData {
IBinder token;
+ boolean taskRemoved;
int startId;
int flags;
Intent args;
@@ -362,11 +363,10 @@ public final class ActivityThread {
}
private static final class DumpComponentInfo {
- FileDescriptor fd;
+ ParcelFileDescriptor fd;
IBinder token;
String prefix;
String[] args;
- boolean dumped;
}
private static final class ResultData {
@@ -393,6 +393,8 @@ public final class ActivityThread {
ParcelFileDescriptor fd;
}
+ native private void dumpGraphicsInfo(FileDescriptor fd);
+
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
@@ -533,10 +535,11 @@ public final class ActivityThread {
queueOrSendMessage(H.UNBIND_SERVICE, s);
}
- public final void scheduleServiceArgs(IBinder token, int startId,
+ public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
int flags ,Intent args) {
ServiceArgsData s = new ServiceArgsData();
s.token = token;
+ s.taskRemoved = taskRemoved;
s.startId = startId;
s.flags = flags;
s.args = args;
@@ -618,20 +621,13 @@ public final class ActivityThread {
public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
- data.fd = fd;
- data.token = servicetoken;
- data.args = args;
- data.dumped = false;
- queueOrSendMessage(H.DUMP_SERVICE, data);
- synchronized (data) {
- while (!data.dumped) {
- try {
- data.wait();
- } catch (InterruptedException e) {
- // no need to do anything here, we will keep waiting until
- // dumped is set
- }
- }
+ try {
+ data.fd = ParcelFileDescriptor.dup(fd);
+ data.token = servicetoken;
+ data.args = args;
+ queueOrSendMessage(H.DUMP_SERVICE, data);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpService failed", e);
}
}
@@ -693,26 +689,24 @@ public final class ActivityThread {
public void dumpActivity(FileDescriptor fd, IBinder activitytoken,
String prefix, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
- data.fd = fd;
- data.token = activitytoken;
- data.prefix = prefix;
- data.args = args;
- data.dumped = false;
- queueOrSendMessage(H.DUMP_ACTIVITY, data);
- synchronized (data) {
- while (!data.dumped) {
- try {
- data.wait();
- } catch (InterruptedException e) {
- // no need to do anything here, we will keep waiting until
- // dumped is set
- }
- }
+ try {
+ data.fd = ParcelFileDescriptor.dup(fd);
+ data.token = activitytoken;
+ data.prefix = prefix;
+ data.args = args;
+ queueOrSendMessage(H.DUMP_ACTIVITY, data);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpActivity failed", e);
}
}
-
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (args != null && args.length == 1 && args[0].equals("graphics")) {
+ pw.flush();
+ dumpGraphicsInfo(fd);
+ return;
+ }
long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -915,7 +909,7 @@ public final class ActivityThread {
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
- public static final int DESTROY_ACTIVITY = 109;
+ public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
@@ -1129,8 +1123,8 @@ public final class ActivityThread {
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
}
- void maybeSnapshot() {
- if (mBoundApplication != null) {
+ private void maybeSnapshot() {
+ if (mBoundApplication != null && SamplingProfilerIntegration.isEnabled()) {
// convert the *private* ActivityThread.PackageInfo to *public* known
// android.content.pm.PackageInfo
String packageName = mBoundApplication.info.mPackageName;
@@ -2105,33 +2099,27 @@ public final class ActivityThread {
}
private void handleDumpService(DumpComponentInfo info) {
- try {
- Service s = mServices.get(info.token);
- if (s != null) {
- PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd));
- s.dump(info.fd, pw, info.args);
- pw.close();
- }
- } finally {
- synchronized (info) {
- info.dumped = true;
- info.notifyAll();
+ Service s = mServices.get(info.token);
+ if (s != null) {
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor()));
+ s.dump(info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ try {
+ info.fd.close();
+ } catch (IOException e) {
}
}
}
private void handleDumpActivity(DumpComponentInfo info) {
- try {
- ActivityClientRecord r = mActivities.get(info.token);
- if (r != null && r.activity != null) {
- PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd));
- r.activity.dump(info.prefix, info.fd, pw, info.args);
- pw.close();
- }
- } finally {
- synchronized (info) {
- info.dumped = true;
- info.notifyAll();
+ ActivityClientRecord r = mActivities.get(info.token);
+ if (r != null && r.activity != null) {
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor()));
+ r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+ pw.flush();
+ try {
+ info.fd.close();
+ } catch (IOException e) {
}
}
}
@@ -2143,7 +2131,13 @@ public final class ActivityThread {
if (data.args != null) {
data.args.setExtrasClassLoader(s.getClassLoader());
}
- int res = s.onStartCommand(data.args, data.flags, data.startId);
+ int res;
+ if (!data.taskRemoved) {
+ res = s.onStartCommand(data.args, data.flags, data.startId);
+ } else {
+ s.onTaskRemoved(data.args);
+ res = Service.START_TASK_REMOVED_COMPLETE;
+ }
QueuedWork.waitToFinish();
@@ -2673,7 +2667,7 @@ public final class ActivityThread {
r.stopped = false;
}
if (r.activity.mDecor != null) {
- if (Config.LOGV) Slog.v(
+ if (false) Slog.v(
TAG, "Handle window " + r + " visibility: " + show);
updateVisibility(r, show);
}
@@ -3395,8 +3389,7 @@ public final class ActivityThread {
}
final void handleLowMemory() {
- ArrayList<ComponentCallbacks> callbacks
- = new ArrayList<ComponentCallbacks>();
+ ArrayList<ComponentCallbacks> callbacks;
synchronized (mPackages) {
callbacks = collectComponentCallbacksLocked(true, null);
@@ -3427,6 +3420,14 @@ public final class ActivityThread {
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName);
+ // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
+ // implementation to use the pool executor. Normally, we use the
+ // serialized executor as the default. This has to happen in the
+ // main thread so the main looper is set right.
+ if (data.appInfo.targetSdkVersion <= 12) {
+ AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
/*
* Before spawning a new process, reset the time zone to be the system time zone.
* This needs to be done because the system time zone could have changed after the
@@ -3874,7 +3875,7 @@ public final class ActivityThread {
info.applicationInfo.sourceDir);
return null;
}
- if (Config.LOGV) Slog.v(
+ if (false) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 50e56c7..85918cf 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -33,13 +33,13 @@ import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -1106,6 +1106,63 @@ final class ApplicationPackageManager extends PackageManager {
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
+ // Multi-user support
+
+ /**
+ * @hide
+ */
+ @Override
+ public UserInfo createUser(String name, int flags) {
+ try {
+ return mPM.createUser(name, flags);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public List<UserInfo> getUsers() {
+ // TODO:
+ // Dummy code, always returns just the primary user
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>();
+ UserInfo primary = new UserInfo(0, "Root!",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ users.add(primary);
+ return users;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean removeUser(int id) {
+ try {
+ return mPM.removeUser(id);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateUserName(int id, String name) {
+ // TODO:
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void updateUserFlags(int id, int flags) {
+ // TODO:
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index aa26b04..0e511f2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -219,6 +219,7 @@ public abstract class ApplicationThreadNative extends Binder
{
data.enforceInterface(IApplicationThread.descriptor);
IBinder token = data.readStrongBinder();
+ boolean taskRemoved = data.readInt() != 0;
int startId = data.readInt();
int fl = data.readInt();
Intent args;
@@ -227,7 +228,7 @@ public abstract class ApplicationThreadNative extends Binder
} else {
args = null;
}
- scheduleServiceArgs(token, startId, fl, args);
+ scheduleServiceArgs(token, taskRemoved, startId, fl, args);
return true;
}
@@ -688,11 +689,12 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public final void scheduleServiceArgs(IBinder token, int startId,
+ public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
int flags, Intent args) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
+ data.writeInt(taskRemoved ? 1 : 0);
data.writeInt(startId);
data.writeInt(flags);
if (args != null) {
@@ -822,7 +824,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeFileDescriptor(fd);
data.writeStrongBinder(token);
data.writeStringArray(args);
- mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, 0);
+ mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
data.recycle();
}
@@ -944,7 +946,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeStrongBinder(token);
data.writeString(prefix);
data.writeStringArray(args);
- mRemote.transact(DUMP_ACTIVITY_TRANSACTION, data, null, 0);
+ mRemote.transact(DUMP_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
data.recycle();
}
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 1d217f0..e5a7980 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -43,7 +43,7 @@ final class BackStackState implements Parcelable {
if (op.removed != null) numRemoved += op.removed.size();
op = op.next;
}
- mOps = new int[bse.mNumOp*5 + numRemoved];
+ mOps = new int[bse.mNumOp*7 + numRemoved];
if (!bse.mAddToBackStack) {
throw new IllegalStateException("Not on back stack");
@@ -56,6 +56,8 @@ final class BackStackState implements Parcelable {
mOps[pos++] = op.fragment.mIndex;
mOps[pos++] = op.enterAnim;
mOps[pos++] = op.exitAnim;
+ mOps[pos++] = op.popEnterAnim;
+ mOps[pos++] = op.popExitAnim;
if (op.removed != null) {
final int N = op.removed.size();
mOps[pos++] = N;
@@ -101,6 +103,8 @@ final class BackStackState implements Parcelable {
op.fragment = f;
op.enterAnim = mOps[pos++];
op.exitAnim = mOps[pos++];
+ op.popEnterAnim = mOps[pos++];
+ op.popExitAnim = mOps[pos++];
final int N = mOps[pos++];
if (N > 0) {
op.removed = new ArrayList<Fragment>(N);
@@ -169,6 +173,8 @@ final class BackStackRecord extends FragmentTransaction implements
static final int OP_REMOVE = 3;
static final int OP_HIDE = 4;
static final int OP_SHOW = 5;
+ static final int OP_DETACH = 6;
+ static final int OP_ATTACH = 7;
static final class Op {
Op next;
@@ -177,6 +183,8 @@ final class BackStackRecord extends FragmentTransaction implements
Fragment fragment;
int enterAnim;
int exitAnim;
+ int popEnterAnim;
+ int popExitAnim;
ArrayList<Fragment> removed;
}
@@ -185,6 +193,8 @@ final class BackStackRecord extends FragmentTransaction implements
int mNumOp;
int mEnterAnim;
int mExitAnim;
+ int mPopEnterAnim;
+ int mPopExitAnim;
int mTransition;
int mTransitionStyle;
boolean mAddToBackStack;
@@ -241,6 +251,11 @@ final class BackStackRecord extends FragmentTransaction implements
writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim);
writer.print(" exitAnim="); writer.println(op.exitAnim);
}
+ if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
+ writer.print(prefix);
+ writer.print("popEnterAnim="); writer.print(op.popEnterAnim);
+ writer.print(" popExitAnim="); writer.println(op.popExitAnim);
+ }
if (op.removed != null && op.removed.size() > 0) {
for (int i=0; i<op.removed.size(); i++) {
writer.print(innerPrefix);
@@ -299,6 +314,8 @@ final class BackStackRecord extends FragmentTransaction implements
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
+ op.popEnterAnim = mPopEnterAnim;
+ op.popExitAnim = mPopExitAnim;
mNumOp++;
}
@@ -401,9 +418,42 @@ final class BackStackRecord extends FragmentTransaction implements
return this;
}
+ public FragmentTransaction detach(Fragment fragment) {
+ //if (fragment.mImmediateActivity == null) {
+ // throw new IllegalStateException("Fragment not added: " + fragment);
+ //}
+
+ Op op = new Op();
+ op.cmd = OP_DETACH;
+ op.fragment = fragment;
+ addOp(op);
+
+ return this;
+ }
+
+ public FragmentTransaction attach(Fragment fragment) {
+ //if (fragment.mImmediateActivity == null) {
+ // throw new IllegalStateException("Fragment not added: " + fragment);
+ //}
+
+ Op op = new Op();
+ op.cmd = OP_ATTACH;
+ op.fragment = fragment;
+ addOp(op);
+
+ return this;
+ }
+
public FragmentTransaction setCustomAnimations(int enter, int exit) {
+ return setCustomAnimations(enter, exit, 0, 0);
+ }
+
+ public FragmentTransaction setCustomAnimations(int enter, int exit,
+ int popEnter, int popExit) {
mEnterAnim = enter;
mExitAnim = exit;
+ mPopEnterAnim = popEnter;
+ mPopExitAnim = popExit;
return this;
}
@@ -567,6 +617,16 @@ final class BackStackRecord extends FragmentTransaction implements
f.mNextAnim = op.enterAnim;
mManager.showFragment(f, mTransition, mTransitionStyle);
} break;
+ case OP_DETACH: {
+ Fragment f = op.fragment;
+ f.mNextAnim = op.exitAnim;
+ mManager.detachFragment(f, mTransition, mTransitionStyle);
+ } break;
+ case OP_ATTACH: {
+ Fragment f = op.fragment;
+ f.mNextAnim = op.enterAnim;
+ mManager.attachFragment(f, mTransition, mTransitionStyle);
+ } break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
@@ -593,6 +653,7 @@ final class BackStackRecord extends FragmentTransaction implements
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
+ f.mNextAnim = op.popExitAnim;
f.mImmediateActivity = null;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
@@ -600,6 +661,7 @@ final class BackStackRecord extends FragmentTransaction implements
} break;
case OP_REPLACE: {
Fragment f = op.fragment;
+ f.mNextAnim = op.popExitAnim;
f.mImmediateActivity = null;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
@@ -607,6 +669,7 @@ final class BackStackRecord extends FragmentTransaction implements
if (op.removed != null) {
for (int i=0; i<op.removed.size(); i++) {
Fragment old = op.removed.get(i);
+ old.mNextAnim = op.popEnterAnim;
f.mImmediateActivity = mManager.mActivity;
mManager.addFragment(old, false);
}
@@ -614,19 +677,32 @@ final class BackStackRecord extends FragmentTransaction implements
} break;
case OP_REMOVE: {
Fragment f = op.fragment;
+ f.mNextAnim = op.popEnterAnim;
f.mImmediateActivity = mManager.mActivity;
mManager.addFragment(f, false);
} break;
case OP_HIDE: {
Fragment f = op.fragment;
+ f.mNextAnim = op.popEnterAnim;
mManager.showFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
case OP_SHOW: {
Fragment f = op.fragment;
+ f.mNextAnim = op.popExitAnim;
mManager.hideFragment(f,
FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
} break;
+ case OP_DETACH: {
+ Fragment f = op.fragment;
+ mManager.attachFragment(f,
+ FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+ } break;
+ case OP_ATTACH: {
+ Fragment f = op.fragment;
+ mManager.detachFragment(f,
+ FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+ } break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 53dc7c8..6f0bbd7 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -52,6 +52,7 @@ final class FragmentState implements Parcelable {
final int mContainerId;
final String mTag;
final boolean mRetainInstance;
+ final boolean mDetached;
final Bundle mArguments;
Bundle mSavedFragmentState;
@@ -66,6 +67,7 @@ final class FragmentState implements Parcelable {
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
+ mDetached = frag.mDetached;
mArguments = frag.mArguments;
}
@@ -77,6 +79,7 @@ final class FragmentState implements Parcelable {
mContainerId = in.readInt();
mTag = in.readString();
mRetainInstance = in.readInt() != 0;
+ mDetached = in.readInt() != 0;
mArguments = in.readBundle();
mSavedFragmentState = in.readBundle();
}
@@ -103,6 +106,7 @@ final class FragmentState implements Parcelable {
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
+ mInstance.mDetached = mDetached;
mInstance.mFragmentManager = activity.mFragments;
return mInstance;
@@ -120,6 +124,7 @@ final class FragmentState implements Parcelable {
dest.writeInt(mContainerId);
dest.writeString(mTag);
dest.writeInt(mRetainInstance ? 1 : 0);
+ dest.writeInt(mDetached ? 1 : 0);
dest.writeBundle(mArguments);
dest.writeBundle(mSavedFragmentState);
}
@@ -159,11 +164,21 @@ final class FragmentState implements Parcelable {
*
* <p>Topics covered here:
* <ol>
+ * <li><a href="#OlderPlatforms">Older Platforms</a>
* <li><a href="#Lifecycle">Lifecycle</a>
* <li><a href="#Layout">Layout</a>
* <li><a href="#BackStack">Back Stack</a>
* </ol>
*
+ * <a name="OlderPlatforms"></a>
+ * <h3>Older Platforms</h3>
+ *
+ * While the Fragment API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms. See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
* <a name="Lifecycle"></a>
* <h3>Lifecycle</h3>
*
@@ -321,8 +336,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
- static final int STARTED = 3; // Created and started, not resumed.
- static final int RESUMED = 4; // Created started and resumed.
+ static final int STOPPED = 3; // Fully created, not started.
+ static final int STARTED = 4; // Created and started, not resumed.
+ static final int RESUMED = 5; // Created started and resumed.
int mState = INITIALIZING;
@@ -404,6 +420,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
// from the user.
boolean mHidden;
+ // Set to true when the app has requested that this fragment be detached.
+ boolean mDetached;
+
// If set this fragment would like its instance retained across
// configuration changes.
boolean mRetainInstance;
@@ -511,23 +530,27 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
}
}
- void restoreViewState() {
+ final void restoreViewState() {
if (mSavedViewState != null) {
mView.restoreHierarchyState(mSavedViewState);
mSavedViewState = null;
}
}
- void setIndex(int index) {
+ final void setIndex(int index) {
mIndex = index;
mWho = "android:fragment:" + mIndex;
}
- void clearIndex() {
+ final void clearIndex() {
mIndex = -1;
mWho = null;
}
+ final boolean isInBackStack() {
+ return mBackStackNesting > 0;
+ }
+
/**
* Subclasses can not override equals().
*/
@@ -947,6 +970,19 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
}
/**
+ * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+ * has returned, but before any saved state has been restored in to the view.
+ * This gives subclasses a chance to initialize themselves once
+ * they know their view hierarchy has been completely created. The fragment's
+ * view hierarchy is not however attached to its parent at this point.
+ * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ }
+
+ /**
* Called to have the fragment instantiate its user interface view.
* This is optional, and non-graphical fragments can return null (which
* is the default implementation). This will be called between
@@ -1280,6 +1316,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
writer.print(" mFromLayout="); writer.print(mFromLayout);
writer.print(" mInLayout="); writer.println(mInLayout);
writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
+ writer.print(" mDetached="); writer.print(mDetached);
writer.print(" mRetainInstance="); writer.print(mRetainInstance);
writer.print(" mRetaining="); writer.print(mRetaining);
writer.print(" mHasMenu="); writer.println(mHasMenu);
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index ab60cf0..0da656f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -714,13 +714,14 @@ final class FragmentManagerImpl extends FragmentManager {
null, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
+ if (f.mHidden) f.mView.setVisibility(View.GONE);
f.restoreViewState();
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
- if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
+ if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
@@ -744,9 +745,10 @@ final class FragmentManagerImpl extends FragmentManager {
anim.start();
}
container.addView(f.mView);
- f.restoreViewState();
}
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ if (f.mHidden) f.mView.setVisibility(View.GONE);
+ f.restoreViewState();
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
@@ -756,10 +758,13 @@ final class FragmentManagerImpl extends FragmentManager {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onActivityCreated()");
}
+ if (f.mView != null) {
+ }
f.mSavedFragmentState = null;
}
case Fragment.ACTIVITY_CREATED:
- if (newState > Fragment.ACTIVITY_CREATED) {
+ case Fragment.STOPPED:
+ if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
f.mCalled = false;
f.onStart();
@@ -803,9 +808,10 @@ final class FragmentManagerImpl extends FragmentManager {
+ " did not call through to super.onStop()");
}
}
+ case Fragment.STOPPED:
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
- if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
+ if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
if (f.mView != null) {
// Need to save the current view state if not
// done already.
@@ -971,32 +977,36 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
- mAdded.add(fragment);
- makeActive(fragment);
if (DEBUG) Log.v(TAG, "add: " + fragment);
- fragment.mAdded = true;
- fragment.mRemoving = false;
- if (fragment.mHasMenu) {
- mNeedMenuInvalidate = true;
- }
- if (moveToStateNow) {
- moveToState(fragment);
+ makeActive(fragment);
+ if (!fragment.mDetached) {
+ mAdded.add(fragment);
+ fragment.mAdded = true;
+ fragment.mRemoving = false;
+ if (fragment.mHasMenu) {
+ mNeedMenuInvalidate = true;
+ }
+ if (moveToStateNow) {
+ moveToState(fragment);
+ }
}
}
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
- mAdded.remove(fragment);
- final boolean inactive = fragment.mBackStackNesting <= 0;
- if (fragment.mHasMenu) {
- mNeedMenuInvalidate = true;
- }
- fragment.mAdded = false;
- fragment.mRemoving = true;
- moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
- transition, transitionStyle);
- if (inactive) {
- makeInactive(fragment);
+ final boolean inactive = !fragment.isInBackStack();
+ if (!fragment.mDetached || inactive) {
+ mAdded.remove(fragment);
+ if (fragment.mHasMenu) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mAdded = false;
+ fragment.mRemoving = true;
+ moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
+ transition, transitionStyle);
+ if (inactive) {
+ makeInactive(fragment);
+ }
}
}
@@ -1052,6 +1062,39 @@ final class FragmentManagerImpl extends FragmentManager {
}
}
+ public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
+ if (DEBUG) Log.v(TAG, "detach: " + fragment);
+ if (!fragment.mDetached) {
+ fragment.mDetached = true;
+ if (fragment.mAdded) {
+ // We are not already in back stack, so need to remove the fragment.
+ mAdded.remove(fragment);
+ if (fragment.mHasMenu) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mAdded = false;
+ fragment.mRemoving = true;
+ moveToState(fragment, Fragment.CREATED, transition, transitionStyle);
+ }
+ }
+ }
+
+ public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
+ if (DEBUG) Log.v(TAG, "attach: " + fragment);
+ if (fragment.mDetached) {
+ fragment.mDetached = false;
+ if (!fragment.mAdded) {
+ mAdded.add(fragment);
+ fragment.mAdded = true;
+ fragment.mRemoving = false;
+ if (fragment.mHasMenu) {
+ mNeedMenuInvalidate = true;
+ }
+ moveToState(fragment, mCurState, transition, transitionStyle);
+ }
+ }
+ }
+
public Fragment findFragmentById(int id) {
if (mActive != null) {
// First look through added fragments.
@@ -1594,7 +1637,7 @@ final class FragmentManagerImpl extends FragmentManager {
}
public void dispatchStop() {
- moveToState(Fragment.ACTIVITY_CREATED, false);
+ moveToState(Fragment.STOPPED, false);
}
public void dispatchDestroy() {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 0cc774d..c1f3cd6 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -87,6 +87,31 @@ public abstract class FragmentTransaction {
public abstract FragmentTransaction show(Fragment fragment);
/**
+ * Detach the given fragment from the UI. This is the same state as
+ * when it is put on the back stack: the fragment is removed from
+ * the UI, however its state is still being actively managed by the
+ * fragment manager. When going into this state its view hierarchy
+ * is destroyed.
+ *
+ * @param fragment The fragment to be detached.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ public abstract FragmentTransaction detach(Fragment fragment);
+
+ /**
+ * Re-attach a fragment after it had previously been deatched from
+ * the UI with {@link #detach(Fragment)}. This
+ * causes its view hierarchy to be re-created, attached to the UI,
+ * and displayed.
+ *
+ * @param fragment The fragment to be attached.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ public abstract FragmentTransaction attach(Fragment fragment);
+
+ /**
* @return <code>true</code> if this transaction contains no operations,
* <code>false</code> otherwise.
*/
@@ -116,10 +141,20 @@ public abstract class FragmentTransaction {
/**
* Set specific animation resources to run for the fragments that are
- * entering and exiting in this transaction.
+ * entering and exiting in this transaction. These animations will not be
+ * played when popping the back stack.
*/
public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
-
+
+ /**
+ * Set specific animation resources to run for the fragments that are
+ * entering and exiting in this transaction. The <code>popEnter</code>
+ * and <code>popExit</code> animations will be played for enter/exit
+ * operations specifically when popping the back stack.
+ */
+ public abstract FragmentTransaction setCustomAnimations(int enter, int exit,
+ int popEnter, int popExit);
+
/**
* Select a standard transition animation for this transaction. May be
* one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 61e6fc8..bec697a 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -133,7 +133,7 @@ public interface IActivityManager extends IInterface {
IThumbnailReceiver receiver) throws RemoteException;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
int flags) throws RemoteException;
- public Bitmap getTaskThumbnail(int taskId) throws RemoteException;
+ public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException;
public List getServices(int maxNum, int flags) throws RemoteException;
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
@@ -342,6 +342,13 @@ public interface IActivityManager extends IInterface {
public int startActivitiesInPackage(int uid,
Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+ // Multi-user APIs
+ public boolean switchUser(int userid) throws RemoteException;
+
+ public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException;
+
+ public boolean removeTask(int taskId, int flags) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -515,7 +522,7 @@ public interface IActivityManager extends IInterface {
int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
- int GET_TASK_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
+ int GET_TASK_THUMBNAILS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
@@ -557,4 +564,7 @@ public interface IActivityManager extends IInterface {
int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120;
int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122;
+ int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123;
+ int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124;
+ int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+125;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 55177a9..b29b088 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -73,8 +73,8 @@ public interface IApplicationThread extends IInterface {
Intent intent, boolean rebind) throws RemoteException;
void scheduleUnbindService(IBinder token,
Intent intent) throws RemoteException;
- void scheduleServiceArgs(IBinder token, int startId, int flags, Intent args)
- throws RemoteException;
+ void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
+ int flags, Intent args) throws RemoteException;
void scheduleStopService(IBinder token) throws RemoteException;
static final int DEBUG_OFF = 0;
static final int DEBUG_ON = 1;
diff --git a/core/java/android/app/IThumbnailRetriever.aidl b/core/java/android/app/IThumbnailRetriever.aidl
new file mode 100644
index 0000000..2a6737d
--- /dev/null
+++ b/core/java/android/app/IThumbnailRetriever.aidl
@@ -0,0 +1,24 @@
+/* Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.graphics.Bitmap;
+
+/**
+ * System private API for retrieving thumbnails
+ */
+interface IThumbnailRetriever {
+ Bitmap getThumbnail(int index);
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index cd278be..7b02763 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -33,7 +33,6 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.ServiceManager;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import android.view.IWindowManager;
import android.view.KeyCharacterMap;
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index a601fbf..0fe7b5c 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -35,6 +35,12 @@ public class KeyguardManager {
private IWindowManager mWM;
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
* you to disable / reenable the keyguard.
*/
@@ -103,6 +109,12 @@ public class KeyguardManager {
}
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Enables you to lock or unlock the keyboard. Get an instance of this class by
* calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
* This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
@@ -112,6 +124,7 @@ public class KeyguardManager {
* @return A {@link KeyguardLock} handle to use to disable and reenable the
* keyguard.
*/
+ @Deprecated
public KeyguardLock newKeyguardLock(String tag) {
return new KeyguardLock(tag);
}
@@ -168,6 +181,12 @@ public class KeyguardManager {
}
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Exit the keyguard securely. The use case for this api is that, after
* disabling the keyguard, your app, which was granted permission to
* disable the keyguard and show a limited amount of information deemed
@@ -181,6 +200,7 @@ public class KeyguardManager {
* it is safe to launch anything that would normally be considered safe
* once the user has gotten past the keyguard.
*/
+ @Deprecated
public void exitKeyguardSecurely(final OnKeyguardExitResult callback) {
try {
mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 6e2f4b6..a5ee26c 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -195,11 +195,11 @@ public class ListFragment extends Fragment {
}
/**
- * Attach to list view once Fragment is ready to run.
+ * Attach to list view once the view hierarchy has been created.
*/
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
ensureList();
}
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 1ee386d..164141c 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -34,12 +34,18 @@ import java.lang.reflect.Modifier;
* {@link android.content.CursorLoader}, however applications are free to write
* their own loaders for loading other types of data.
*
+ * While the LoaderManager API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms. See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
* <p>As an example, here is the full implementation of a {@link Fragment}
* that displays a {@link android.widget.ListView} containing the results of
* a query against the contacts content provider. It uses a
* {@link android.content.CursorLoader} to manage the query on the provider.
*
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
* fragment_cursor}
*/
public abstract class LoaderManager {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 6541c54..4913e78 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -61,8 +61,7 @@ import android.util.Log;
public class NotificationManager
{
private static String TAG = "NotificationManager";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
private static INotificationManager sService;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 05b9781..c179b35 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -371,6 +371,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
public static final int START_REDELIVER_INTENT = 3;
/**
+ * Special constant for reporting that we are done processing
+ * {@link #onTaskRemoved(Intent)}.
+ * @hide
+ */
+ public static final int START_TASK_REMOVED_COMPLETE = 1000;
+
+ /**
* This flag is set in {@link #onStartCommand} if the Intent is a
* re-delivery of a previously delivered intent, because the service
* had previously returned {@link #START_REDELIVER_INTENT} but had been
@@ -500,6 +507,19 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
}
/**
+ * This is called if the service is currently running and the user has
+ * removed a task that comes from the service's application. If you have
+ * set {@link android.content.pm.ServiceInfo#FLAG_STOP_WITH_TASK ServiceInfo.FLAG_STOP_WITH_TASK}
+ * then you will not receive this callback; instead, the service will simply
+ * be stopped.
+ *
+ * @param rootIntent The original root Intent that was used to launch
+ * the task that is being removed.
+ */
+ public void onTaskRemoved(Intent rootIntent) {
+ }
+
+ /**
* Stop the service, if it was previously started. This is the same as
* calling {@link android.content.Context#stopService} for this particular service.
*
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 29f8caf..473aec6 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -52,8 +52,7 @@ import android.os.Bundle;
*/
public class DeviceAdminReceiver extends BroadcastReceiver {
private static String TAG = "DevicePolicy";
- private static boolean DEBUG = false;
- private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+ private static boolean localLOGV = false;
/**
* This is the primary action that a device administrator must implement to be
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index 55368d6..0c034cf 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -19,6 +19,7 @@ package android.app.backup;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.BitmapFactory;
+import android.graphics.Point;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
import android.view.Display;
@@ -70,8 +71,10 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
if (mDesiredMinWidth <= 0 || mDesiredMinHeight <= 0) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display d = wm.getDefaultDisplay();
- mDesiredMinWidth = d.getWidth();
- mDesiredMinHeight = d.getHeight();
+ Point size = new Point();
+ d.getSize(size);
+ mDesiredMinWidth = size.x;
+ mDesiredMinHeight = size.y;
}
if (DEBUG) {
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index fa55520..8a9bef0 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -603,7 +603,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*/
public boolean setAudioState(BluetoothDevice device, int state) {
if (DBG) log("setAudioState");
- if (mService != null && isEnabled()) {
+ if (mService != null && !isDisabled()) {
try {
return mService.setAudioState(device, state);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -622,7 +622,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*/
public int getAudioState(BluetoothDevice device) {
if (DBG) log("getAudioState");
- if (mService != null && isEnabled()) {
+ if (mService != null && !isDisabled()) {
try {
return mService.getAudioState(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
@@ -705,6 +705,11 @@ public final class BluetoothHeadset implements BluetoothProfile {
return false;
}
+ private boolean isDisabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
+ return false;
+ }
+
private boolean isValidDevice(BluetoothDevice device) {
if (device == null) return false;
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 383cb6b..0b54396 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -27,7 +27,24 @@ import java.io.PrintWriter;
import java.util.concurrent.CountDownLatch;
/**
- * Abstract Loader that provides an {@link AsyncTask} to do the work.
+ * Abstract Loader that provides an {@link AsyncTask} to do the work. See
+ * {@link Loader} and {@link android.app.LoaderManager} for more details.
+ *
+ * <p>Here is an example implementation of an AsyncTaskLoader subclass that
+ * loads the currently installed applications from the package manager. This
+ * implementation takes care of retrieving the application labels and sorting
+ * its result set from them, monitoring for changes to the installed
+ * applications, and rebuilding the list when a change in configuration requires
+ * this (such as a locale change).
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * loader}
+ *
+ * <p>An example implementation of a fragment that uses the above loader to show
+ * the currently installed applications in a list is below.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * fragment}
*
* @param <D> the data type to be loaded.
*/
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2d03e7c..364821e 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -35,7 +35,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.text.TextUtils;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
@@ -1627,9 +1626,9 @@ public abstract class ContentResolver {
return sContentService;
}
IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
- if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
+ if (false) Log.v("ContentService", "default service binder = " + b);
sContentService = IContentService.Stub.asInterface(b);
- if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
+ if (false) Log.v("ContentService", "default service = " + sContentService);
return sContentService;
}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index afe8483..a2af558 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -25,7 +25,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.util.Config;
import android.util.Log;
import android.Manifest;
@@ -104,7 +103,7 @@ public final class ContentService extends IContentService.Stub {
}
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode);
- if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+ if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
" with notifyForDescendents " + notifyForDescendents);
}
}
@@ -115,7 +114,7 @@ public final class ContentService extends IContentService.Stub {
}
synchronized (mRootNode) {
mRootNode.removeObserverLocked(observer);
- if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
+ if (false) Log.v(TAG, "Unregistered observer " + observer);
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7bdd1b9..3d637e9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1878,7 +1878,7 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.USB_ANLG_HEADSET_PLUG";
/**
- * Broadcast Action: An analog audio speaker/headset plugged in or unplugged.
+ * Broadcast Action: A digital audio speaker/headset plugged in or unplugged.
*
* <p>The intent will have the following extra values:
* <ul>
@@ -1908,6 +1908,21 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.HDMI_AUDIO_PLUG";
/**
+ * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
+ * <ul>
+ * <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @hide
+ */
+ //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_ADVANCED_SETTINGS_CHANGED
+ = "android.intent.action.ADVANCED_SETTINGS";
+
+ /**
* Broadcast Action: An outgoing call is about to be placed.
*
* <p>The Intent will have the following extra value:
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 06c1ecb..2a0ebcf 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -31,7 +31,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
import android.util.AndroidException;
-import android.util.Config;
import android.util.Log;
import android.util.Printer;
@@ -670,7 +669,7 @@ public class IntentFilter implements Parcelable {
if (host == null) {
return NO_MATCH_DATA;
}
- if (Config.LOGV) Log.v("IntentFilter",
+ if (false) Log.v("IntentFilter",
"Match host " + host + ": " + mHost);
if (mWild) {
if (host.length() < mHost.length()) {
@@ -1095,14 +1094,14 @@ public class IntentFilter implements Parcelable {
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag) {
if (action != null && !matchAction(action)) {
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
}
int dataMatch = matchData(type, scheme, data);
if (dataMatch < 0) {
- if (Config.LOGV) {
+ if (false) {
if (dataMatch == NO_MATCH_TYPE) {
Log.v(logTag, "No matching type " + type
+ " for " + this);
@@ -1117,7 +1116,7 @@ public class IntentFilter implements Parcelable {
String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {
- if (Config.LOGV) {
+ if (false) {
Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
}
return NO_MATCH_CATEGORY;
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index a9d6117..4e70b74 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -26,7 +26,7 @@ import java.io.PrintWriter;
/**
* An abstract class that performs asynchronous loading of data. While Loaders are active
* they should monitor the source of their data and deliver new results when the contents
- * change.
+ * change. See {@link android.app.LoaderManager} for more detail.
*
* <p><b>Note on threading:</b> Clients of loaders should as a rule perform
* any calls on to a Loader from the main thread of their process (that is,
@@ -36,7 +36,10 @@ import java.io.PrintWriter;
* be done on the main thread.</p>
*
* <p>Subclasses generally must implement at least {@link #onStartLoading()},
- * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.
+ * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
+ *
+ * <p>Most implementations should not derive directly from this class, but
+ * instead inherit from {@link AsyncTaskLoader}.</p>
*
* @param <D> The result returned when the load is complete
*/
@@ -76,8 +79,12 @@ public class Loader<D> {
}
/**
- * Stores away the application context associated with context. Since Loaders can be used
- * across multiple activities it's dangerous to store the context directly.
+ * Stores away the application context associated with context.
+ * Since Loaders can be used across multiple activities it's dangerous to
+ * store the context directly; always use {@link #getContext()} to retrieve
+ * the Loader's Context, don't use the constructor argument directly.
+ * The Context returned by {@link #getContext} is safe to use across
+ * Activity instances.
*
* @param context used to retrieve the application context.
*/
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 46f611f..64c437d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -334,6 +334,12 @@ public class ActivityInfo extends ComponentInfo
public static final int CONFIG_UI_MODE = 0x0200;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the screen size. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_SCREEN_SIZE = 0x0400;
+ /**
+ * 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
@@ -341,6 +347,37 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int CONFIG_FONT_SCALE = 0x40000000;
+ /** @hide
+ * Unfortunately the constants for config changes in native code are
+ * different from ActivityInfo. :( Here are the values we should use for the
+ * native side given the bit we have assigned in ActivityInfo.
+ */
+ public static int[] CONFIG_NATIVE_BITS = new int[] {
+ 0x0001, // MNC
+ 0x0002, // MCC
+ 0x0004, // LOCALE
+ 0x0008, // TOUCH SCREEN
+ 0x0010, // KEYBOARD
+ 0x0020, // KEYBOARD HIDDEN
+ 0x0040, // NAVIGATION
+ 0x0080, // ORIENTATION
+ 0x0800, // SCREEN LAYOUT
+ 0x1000, // UI MODE
+ 0x0200, // SCREEN SIZE
+ };
+ /** @hide
+ * Convert Java change bits to native.
+ */
+ public static int activityInfoConfigToNative(int input) {
+ int output = 0;
+ for (int i=0; i<CONFIG_NATIVE_BITS.length; i++) {
+ if ((input&(1<<i)) != 0) {
+ output |= CONFIG_NATIVE_BITS[i];
+ }
+ }
+ return output;
+ }
+
/**
* Bit mask of kinds of configuration changes that this activity
* can handle itself (without being restarted by the system).
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fbf8f92..11cd446 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -35,6 +35,7 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.content.IntentSender;
@@ -329,4 +330,7 @@ interface IPackageManager {
boolean setInstallLocation(int loc);
int getInstallLocation();
+
+ UserInfo createUser(in String name, int flags);
+ boolean removeUser(int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 80bed0d..ff817c1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -150,21 +150,21 @@ public abstract class PackageManager {
* {@link PackageInfo#permissions}.
*/
public static final int GET_PERMISSIONS = 0x00001000;
-
+
/**
* Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
- * This state could have resulted if applications have been deleted with flag
+ * This state could have resulted if applications have been deleted with flag
* DONT_DELETE_DATA
* with a possibility of being replaced or reinstalled in future
*/
public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
-
+
/**
* {@link PackageInfo} flag: return information about
* hardware preferences in
* {@link PackageInfo#configPreferences PackageInfo.configPreferences} and
* requested features in {@link PackageInfo#reqFeatures
- * PackageInfo.reqFeatures}.
+ * PackageInfo.reqFeatures}.
*/
public static final int GET_CONFIGURATIONS = 0x00004000;
@@ -244,7 +244,7 @@ public abstract class PackageManager {
public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
/**
- * Flag parameter for {@link #installPackage} to indicate that you want to
+ * Flag parameter for {@link #installPackage} to indicate that you want to
* allow test packages (those that have set android:testOnly in their
* manifest) to be installed.
* @hide
@@ -555,7 +555,7 @@ public abstract class PackageManager {
* Return code for when package deletion succeeds. This is passed to the
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* succeeded in deleting the package.
- *
+ *
* @hide
*/
public static final int DELETE_SUCCEEDED = 1;
@@ -564,7 +564,7 @@ public abstract class PackageManager {
* Deletion failed return code: this is passed to the
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* failed to delete the package for an unspecified reason.
- *
+ *
* @hide
*/
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
@@ -574,7 +574,7 @@ public abstract class PackageManager {
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* failed to delete the package because it is the active DevicePolicy
* manager.
- *
+ *
* @hide
*/
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
@@ -583,7 +583,7 @@ public abstract class PackageManager {
* Return code that is passed to the {@link IPackageMoveObserver} by
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)} when the
* package has been successfully moved by the system.
- *
+ *
* @hide
*/
public static final int MOVE_SUCCEEDED = 1;
@@ -641,7 +641,7 @@ public abstract class PackageManager {
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the
* specified package already has an operation pending in the
* {@link PackageHandler} queue.
- *
+ *
* @hide
*/
public static final int MOVE_FAILED_OPERATION_PENDING = -7;
@@ -662,10 +662,15 @@ public abstract class PackageManager {
public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
/**
- * Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device's audio pipeline is low-latency,
- * more suitable for audio applications sensitive to delays or lag in
- * sound input or output.
+ * Range of IDs allocated for a user.
+ * @hide
+ */
+ public static final int PER_USER_RANGE = 100000;
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
+ * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
+ * lag in sound input or output.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
@@ -789,7 +794,7 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a telephony radio with data
@@ -797,14 +802,14 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a CDMA telephony stack.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a GSM telephony stack.
@@ -847,8 +852,8 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
-
-
+
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's touch screen supports
@@ -856,7 +861,7 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's touch screen is capable of
@@ -932,11 +937,11 @@ public abstract class PackageManager {
* @return Returns a PackageInfo object containing information about the package.
* If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
* found in the list of installed applications, the package information is
- * retrieved from the list of uninstalled applications(which includes
+ * retrieved from the list of uninstalled applications(which includes
* installed applications as well as applications
* with data directory ie applications which had been
* deleted with DONT_DELTE_DATA flag set).
- *
+ *
* @see #GET_ACTIVITIES
* @see #GET_GIDS
* @see #GET_CONFIGURATIONS
@@ -947,7 +952,7 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
+ *
*/
public abstract PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException;
@@ -960,7 +965,7 @@ public abstract class PackageManager {
* the canonical name for each package.
*/
public abstract String[] currentToCanonicalPackageNames(String[] names);
-
+
/**
* Map from a packages canonical name to the current name in use on the device.
* @param names Array of new names to be mapped.
@@ -968,7 +973,7 @@ public abstract class PackageManager {
* the current name for each package.
*/
public abstract String[] canonicalToCurrentPackageNames(String[] names);
-
+
/**
* Return a "good" intent to launch a front-door activity in a package,
* for use for example to implement an "open" button when browsing through
@@ -976,12 +981,12 @@ public abstract class PackageManager {
* activity in the category {@link Intent#CATEGORY_INFO}, next for a
* main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
* null if neither are found.
- *
+ *
* <p>Throws {@link NameNotFoundException} if a package with the given
* name can not be found on the system.
*
* @param packageName The name of the package to inspect.
- *
+ *
* @return Returns either a fully-qualified Intent that can be used to
* launch the main activity in the package, or null if the package does
* not contain such an activity.
@@ -1077,16 +1082,16 @@ public abstract class PackageManager {
*
* @param packageName The full name (i.e. com.google.apps.contacts) of an
* application.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
- * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
+ * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
* information about the package.
* If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
- * found in the list of installed applications,
- * the application information is retrieved from the
- * list of uninstalled applications(which includes
+ * found in the list of installed applications,
+ * the application information is retrieved from the
+ * list of uninstalled applications(which includes
* installed applications as well as applications
* with data directory ie applications which had been
* deleted with DONT_DELTE_DATA flag set).
@@ -1108,7 +1113,7 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data (in ApplicationInfo) returned.
*
@@ -1131,7 +1136,7 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
*
@@ -1154,12 +1159,12 @@ public abstract class PackageManager {
* @param component The full component name (i.e.
* com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service
* class.
- * @param flags Additional option flags. Use any combination of
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
*
* @return ServiceInfo containing information about the service.
- *
+ *
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
*/
@@ -1206,7 +1211,7 @@ public abstract class PackageManager {
*
* @return A List of PackageInfo objects, one for each package that is
* installed on the device. In the unlikely case of there being no
- * installed packages, an empty list is returned.
+ * installed packages, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
* applications including those deleted with DONT_DELETE_DATA
* (partially installed apps with data directory) will be returned.
@@ -1221,7 +1226,7 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
+ *
*/
public abstract List<PackageInfo> getInstalledPackages(int flags);
@@ -1283,7 +1288,7 @@ public abstract class PackageManager {
* the device is rebooted before it is written.
*/
public abstract boolean addPermissionAsync(PermissionInfo info);
-
+
/**
* Removes a permission that was previously added with
* {@link #addPermission(PermissionInfo)}. The same ownership rules apply
@@ -1370,7 +1375,7 @@ public abstract class PackageManager {
* user id is not currently assigned.
*/
public abstract String getNameForUid(int uid);
-
+
/**
* Return the user id associated with a shared user name. Multiple
* applications can specify a shared user name in their manifest and thus
@@ -1391,38 +1396,38 @@ public abstract class PackageManager {
* device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
* applications including those deleted with DONT_DELETE_DATA(partially
* installed apps with data directory) will be returned.
- *
- * @param flags Additional option flags. Use any combination of
+ *
+ * @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* {link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
* @return A List of ApplicationInfo objects, one for each application that
* is installed on the device. In the unlikely case of there being
- * no installed applications, an empty list is returned.
+ * no installed applications, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
* applications including those deleted with DONT_DELETE_DATA
* (partially installed apps with data directory) will be returned.
- *
+ *
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
* @see #GET_UNINSTALLED_PACKAGES
*/
public abstract List<ApplicationInfo> getInstalledApplications(int flags);
-
+
/**
* Get a list of shared libraries that are available on the
* system.
- *
+ *
* @return An array of shared library names that are
* available on the system, or null if none are installed.
- *
+ *
*/
public abstract String[] getSystemSharedLibraryNames();
/**
* Get a list of features that are available on the
* system.
- *
+ *
* @return An array of FeatureInfo classes describing the features
* that are available on the system, or null if there are none(!!).
*/
@@ -1431,7 +1436,7 @@ public abstract class PackageManager {
/**
* Check whether the given feature name is one of the available
* features as returned by {@link #getSystemAvailableFeatures()}.
- *
+ *
* @return Returns true if the devices supports the feature, else
* false.
*/
@@ -1448,7 +1453,7 @@ public abstract class PackageManager {
* that {@link android.content.Context#startActivity(Intent)} and
* {@link android.content.Intent#resolveActivity(PackageManager)
* Intent.resolveActivity(PackageManager)} do.</p>
- *
+ *
* @param intent An intent containing all of the desired specification
* (action, data, type, category, and/or component).
* @param flags Additional option flags. The most important is
@@ -1747,7 +1752,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo or null if the activity has no
* logo specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for the given
* activity could not be loaded.
*
@@ -1768,7 +1773,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo, or null if the activity has no
* logo specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for application
* matching the given intent could not be loaded.
*
@@ -1801,7 +1806,7 @@ public abstract class PackageManager {
*
* @return Returns the image of the logo, or null if no application logo
* has been specified.
- *
+ *
* @throws NameNotFoundException Thrown if the resources for the given
* application could not be loaded.
*
@@ -1935,7 +1940,7 @@ public abstract class PackageManager {
* @see #GET_RECEIVERS
* @see #GET_SERVICES
* @see #GET_SIGNATURES
- *
+ *
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
PackageParser packageParser = new PackageParser(archiveFilePath);
@@ -1952,7 +1957,7 @@ public abstract class PackageManager {
/**
* @hide
- *
+ *
* Install a package. Since this may take a little while, the result will
* be posted back to the given observer. An installation will fail if the calling context
* lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
@@ -2012,11 +2017,11 @@ public abstract class PackageManager {
/**
* Retrieve the package name of the application that installed a package. This identifies
* which market the package came from.
- *
+ *
* @param packageName The name of the package to query
*/
public abstract String getInstallerPackageName(String packageName);
-
+
/**
* Attempts to clear the user data directory of an application.
* Since this may take a little while, the result will
@@ -2071,7 +2076,7 @@ public abstract class PackageManager {
* of bytes if possible.
* @param observer call back used to notify when
* the operation is completed
- *
+ *
* @hide
*/
public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
@@ -2096,7 +2101,7 @@ public abstract class PackageManager {
* @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
- *
+ *
* @hide
*/
public abstract void freeStorage(long freeStorageSize, IntentSender pi);
@@ -2172,7 +2177,7 @@ public abstract class PackageManager {
* @deprecated This is a protected API that should not have been available
* to third party applications. It is the platform's responsibility for
* assigning preferred activities and this can not be directly modified.
- *
+ *
* Add a new preferred activity mapping to the system. This will be used
* to automatically select the given activity component when
* {@link Context#startActivity(Intent) Context.startActivity()} finds
@@ -2195,7 +2200,7 @@ public abstract class PackageManager {
* @deprecated This is a protected API that should not have been available
* to third party applications. It is the platform's responsibility for
* assigning preferred activities and this can not be directly modified.
- *
+ *
* Replaces an existing preferred activity mapping to the system, and if that were not present
* adds a new preferred activity. This will be used
* to automatically select the given activity component when
@@ -2304,7 +2309,7 @@ public abstract class PackageManager {
*/
public abstract void setApplicationEnabledSetting(String packageName,
int newState, int flags);
-
+
/**
* Return the the enabled setting for an application. This returns
* the last value set by
@@ -2345,4 +2350,79 @@ public abstract class PackageManager {
*/
public abstract void movePackage(
String packageName, IPackageMoveObserver observer, int flags);
+
+ /**
+ * Creates a user with the specified name and options.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @see UserInfo
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public abstract UserInfo createUser(String name, int flags);
+
+ /**
+ * @return the list of users that were created
+ * @hide
+ */
+ public abstract List<UserInfo> getUsers();
+
+ /**
+ * @param id the ID of the user, where 0 is the primary user.
+ * @hide
+ */
+ public abstract boolean removeUser(int id);
+
+ /**
+ * Updates the user's name.
+ *
+ * @param id the user's id
+ * @param name the new name for the user
+ * @hide
+ */
+ public abstract void updateUserName(int id, String name);
+
+ /**
+ * Changes the user's properties specified by the flags.
+ *
+ * @param id the user's id
+ * @param flags the new flags for the user
+ * @hide
+ */
+ public abstract void updateUserFlags(int id, int flags);
+
+ /**
+ * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+ * user.
+ * @hide
+ */
+ public static boolean isSameUser(int uid1, int uid2) {
+ return getUserId(uid1) == getUserId(uid2);
+ }
+
+ /**
+ * Returns the user id for a given uid.
+ * @hide
+ */
+ public static int getUserId(int uid) {
+ return uid / PER_USER_RANGE;
+ }
+
+ /**
+ * Returns the uid that is composed from the userId and the appId.
+ * @hide
+ */
+ public static int getUid(int userId, int appId) {
+ return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+ }
+
+ /**
+ * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+ * @hide
+ */
+ public static int getAppId(int uid) {
+ return uid % PER_USER_RANGE;
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7ebfda4..564f4f4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,11 +24,11 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
@@ -384,7 +384,7 @@ public class PackageParser {
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGD) Log.d(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.d(
TAG, "Scanning package: " + mArchiveSourcePath);
XmlResourceParser parser = null;
@@ -396,7 +396,7 @@ public class PackageParser {
int cookie = assmgr.addAssetPath(mArchiveSourcePath);
if (cookie != 0) {
res = new Resources(assmgr, metrics, null);
- assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
assetError = false;
@@ -596,7 +596,7 @@ public class PackageParser {
AssetManager assmgr = null;
try {
assmgr = new AssetManager();
- assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
int cookie = assmgr.addAssetPath(packageFilePath);
parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
@@ -666,7 +666,7 @@ public class PackageParser {
outError[0] = "No start tag found";
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.v(
TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
outError[0] = "No <manifest> tag";
@@ -701,7 +701,7 @@ public class PackageParser {
outError[0] = "No start tag found";
return null;
}
- if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ if ((flags&PARSE_CHATTY) != 0 && false) Log.v(
TAG, "Root element name: '" + parser.getName() + "'");
if (!parser.getName().equals("manifest")) {
outError[0] = "No <manifest> tag";
@@ -1931,6 +1931,10 @@ public class PackageParser {
a.info.configChanges = sa.getInt(
com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
0);
+ if (owner.applicationInfo.targetSdkVersion
+ < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ a.info.configChanges |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
a.info.softInputMode = sa.getInt(
com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
0);
@@ -2470,6 +2474,13 @@ public class PackageParser {
s.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
+ s.info.flags = 0;
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_STOP_WITH_TASK;
+ }
+
sa.recycle();
if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 087a4fe..612e345 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -33,17 +33,35 @@ public class ServiceInfo extends ComponentInfo
*/
public String permission;
+ /**
+ * Bit in {@link #flags}: If set, the service will automatically be
+ * stopped by the system if the user removes a task that is rooted
+ * in one of the application's activities. Set from the
+ * {@link android.R.attr#stopWithTask} attribute.
+ */
+ public static final int FLAG_STOP_WITH_TASK = 0x0001;
+
+ /**
+ * Options that have been set in the service declaration in the
+ * manifest.
+ * These include:
+ * {@link #FLAG_STOP_WITH_TASK}
+ */
+ public int flags;
+
public ServiceInfo() {
}
public ServiceInfo(ServiceInfo orig) {
super(orig);
permission = orig.permission;
+ flags = orig.flags;
}
public void dump(Printer pw, String prefix) {
super.dumpFront(pw, prefix);
pw.println(prefix + "permission=" + permission);
+ pw.println(prefix + "flags=0x" + Integer.toHexString(flags));
}
public String toString() {
@@ -59,6 +77,7 @@ public class ServiceInfo extends ComponentInfo
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeString(permission);
+ dest.writeInt(flags);
}
public static final Creator<ServiceInfo> CREATOR =
@@ -74,5 +93,6 @@ public class ServiceInfo extends ComponentInfo
private ServiceInfo(Parcel source) {
super(source);
permission = source.readString();
+ flags = source.readInt();
}
}
diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl
new file mode 100644
index 0000000..2e7cb8f
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+parcelable UserInfo;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
new file mode 100644
index 0000000..ba5331c
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Per-user information.
+ * @hide
+ */
+public class UserInfo implements Parcelable {
+ /**
+ * Primary user. Only one user can have this flag set. Meaning of this
+ * flag TBD.
+ */
+ public static final int FLAG_PRIMARY = 0x00000001;
+
+ /**
+ * User with administrative privileges. Such a user can create and
+ * delete users.
+ */
+ public static final int FLAG_ADMIN = 0x00000002;
+
+ /**
+ * Indicates a guest user that may be transient.
+ */
+ public static final int FLAG_GUEST = 0x00000004;
+
+ public int id;
+ public String name;
+ public int flags;
+
+ public UserInfo(int id, String name, int flags) {
+ this.id = id;
+ this.name = name;
+ this.flags = flags;
+ }
+
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
+ }
+
+ public boolean isAdmin() {
+ return (flags & FLAG_ADMIN) == FLAG_ADMIN;
+ }
+
+ public boolean isGuest() {
+ return (flags & FLAG_GUEST) == FLAG_GUEST;
+ }
+
+ public UserInfo() {
+ }
+
+ public UserInfo(UserInfo orig) {
+ name = orig.name;
+ id = orig.id;
+ flags = orig.flags;
+ }
+
+ @Override
+ public String toString() {
+ return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(id);
+ dest.writeString(name);
+ dest.writeInt(flags);
+ }
+
+ public static final Parcelable.Creator<UserInfo> CREATOR
+ = new Parcelable.Creator<UserInfo>() {
+ public UserInfo createFromParcel(Parcel source) {
+ return new UserInfo(source);
+ }
+ public UserInfo[] newArray(int size) {
+ return new UserInfo[size];
+ }
+ };
+
+ private UserInfo(Parcel source) {
+ id = source.readInt();
+ name = source.readString();
+ flags = source.readInt();
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index fc6761d..be67e96 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.os.ParcelFileDescriptor;
-import android.util.Config;
import android.util.Log;
import android.util.TypedValue;
@@ -58,7 +57,7 @@ public final class AssetManager {
public static final int ACCESS_BUFFER = 3;
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean localLOGV = false || false;
private static final boolean DEBUG_REFS = false;
@@ -653,7 +652,8 @@ 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 uiMode, int majorVersion);
+ int screenWidthDp, int screenHeightDp, 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 47c2623..93b3429 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -245,6 +245,20 @@ public final class Configuration implements Parcelable, Comparable<Configuration
*/
public int uiMode;
+ public static final int SCREEN_WIDTH_DP_UNDEFINED = 0;
+
+ /**
+ * The current width of the available screen space, in dp units.
+ */
+ public int screenWidthDp;
+
+ public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0;
+
+ /**
+ * The current height of the available screen space, in dp units.
+ */
+ public int screenHeightDp;
+
/**
* @hide Internal book-keeping.
*/
@@ -282,46 +296,113 @@ public final class Configuration implements Parcelable, Comparable<Configuration
orientation = o.orientation;
screenLayout = o.screenLayout;
uiMode = o.uiMode;
+ screenWidthDp = o.screenWidthDp;
+ screenHeightDp = o.screenHeightDp;
seq = o.seq;
}
public String toString() {
StringBuilder sb = new StringBuilder(128);
- sb.append("{ scale=");
+ sb.append("{");
sb.append(fontScale);
- sb.append(" imsi=");
+ sb.append("x imsi=");
sb.append(mcc);
sb.append("/");
sb.append(mnc);
- sb.append(" loc=");
- sb.append(locale);
- sb.append(" touch=");
- sb.append(touchscreen);
- sb.append(" keys=");
- sb.append(keyboard);
- sb.append("/");
- sb.append(keyboardHidden);
- sb.append("/");
- sb.append(hardKeyboardHidden);
- sb.append(" nav=");
- sb.append(navigation);
- sb.append("/");
- sb.append(navigationHidden);
- sb.append(" orien=");
- switch(orientation) {
- case ORIENTATION_LANDSCAPE:
- sb.append("L"); break;
- case ORIENTATION_PORTRAIT:
- sb.append("P"); break;
- default:
- sb.append(orientation);
- }
- sb.append(" layout=0x");
- sb.append(java.lang.Integer.toHexString(screenLayout));
- sb.append(" uiMode=0x");
- sb.append(java.lang.Integer.toHexString(uiMode));
+ if (locale != null) {
+ sb.append(" ");
+ sb.append(locale);
+ } else {
+ sb.append(" (no locale)");
+ }
+ switch (touchscreen) {
+ case TOUCHSCREEN_UNDEFINED: sb.append(" ?touch"); break;
+ case TOUCHSCREEN_NOTOUCH: sb.append(" -touch"); break;
+ case TOUCHSCREEN_STYLUS: sb.append(" stylus"); break;
+ case TOUCHSCREEN_FINGER: sb.append(" finger"); break;
+ default: sb.append(" touch="); sb.append(touchscreen); break;
+ }
+ switch (keyboard) {
+ case KEYBOARD_UNDEFINED: sb.append(" ?keyb"); break;
+ case KEYBOARD_NOKEYS: sb.append(" -keyb"); break;
+ case KEYBOARD_QWERTY: sb.append(" qwerty"); break;
+ case KEYBOARD_12KEY: sb.append(" 12key"); break;
+ default: sb.append(" keys="); sb.append(keyboard); break;
+ }
+ switch (keyboardHidden) {
+ case KEYBOARDHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case KEYBOARDHIDDEN_NO: sb.append("/v"); break;
+ case KEYBOARDHIDDEN_YES: sb.append("/h"); break;
+ case KEYBOARDHIDDEN_SOFT: sb.append("/s"); break;
+ default: sb.append("/"); sb.append(keyboardHidden); break;
+ }
+ switch (hardKeyboardHidden) {
+ case HARDKEYBOARDHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case HARDKEYBOARDHIDDEN_NO: sb.append("/v"); break;
+ case HARDKEYBOARDHIDDEN_YES: sb.append("/h"); break;
+ default: sb.append("/"); sb.append(hardKeyboardHidden); break;
+ }
+ switch (navigation) {
+ case NAVIGATION_UNDEFINED: sb.append(" ?nav"); break;
+ case NAVIGATION_NONAV: sb.append(" -nav"); break;
+ case NAVIGATION_DPAD: sb.append(" dpad"); break;
+ case NAVIGATION_TRACKBALL: sb.append(" tball"); break;
+ case NAVIGATION_WHEEL: sb.append(" wheel"); break;
+ default: sb.append(" nav="); sb.append(navigation); break;
+ }
+ switch (navigationHidden) {
+ case NAVIGATIONHIDDEN_UNDEFINED: sb.append("/?"); break;
+ case NAVIGATIONHIDDEN_NO: sb.append("/v"); break;
+ case NAVIGATIONHIDDEN_YES: sb.append("/h"); break;
+ default: sb.append("/"); sb.append(navigationHidden); break;
+ }
+ switch (orientation) {
+ case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break;
+ case ORIENTATION_LANDSCAPE: sb.append(" land"); break;
+ case ORIENTATION_PORTRAIT: sb.append(" port"); break;
+ default: sb.append(" orien="); sb.append(orientation); break;
+ }
+ switch ((screenLayout&SCREENLAYOUT_SIZE_MASK)) {
+ case SCREENLAYOUT_SIZE_UNDEFINED: sb.append(" ?lsize"); break;
+ case SCREENLAYOUT_SIZE_SMALL: sb.append(" smll"); break;
+ case SCREENLAYOUT_SIZE_NORMAL: sb.append(" nrml"); break;
+ case SCREENLAYOUT_SIZE_LARGE: sb.append(" lrg"); break;
+ case SCREENLAYOUT_SIZE_XLARGE: sb.append(" xlrg"); break;
+ default: sb.append(" layoutSize=");
+ sb.append(screenLayout&SCREENLAYOUT_SIZE_MASK); break;
+ }
+ switch ((screenLayout&SCREENLAYOUT_LONG_MASK)) {
+ case SCREENLAYOUT_LONG_UNDEFINED: sb.append(" ?long"); break;
+ case SCREENLAYOUT_LONG_NO: /* not-long is not interesting to print */ break;
+ case SCREENLAYOUT_LONG_YES: sb.append(" long"); break;
+ default: sb.append(" layoutLong=");
+ sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); break;
+ }
+ switch ((uiMode&UI_MODE_TYPE_MASK)) {
+ case UI_MODE_TYPE_UNDEFINED: sb.append(" ?uimode"); break;
+ case UI_MODE_TYPE_NORMAL: /* normal is not interesting to print */ break;
+ case UI_MODE_TYPE_DESK: sb.append(" desk"); break;
+ case UI_MODE_TYPE_CAR: sb.append(" car"); break;
+ default: sb.append(" uimode="); sb.append(uiMode&UI_MODE_TYPE_MASK); break;
+ }
+ switch ((uiMode&UI_MODE_NIGHT_MASK)) {
+ case UI_MODE_NIGHT_UNDEFINED: sb.append(" ?night"); break;
+ case UI_MODE_NIGHT_NO: /* not-night is not interesting to print */ break;
+ case UI_MODE_NIGHT_YES: sb.append(" night"); break;
+ default: sb.append(" night="); sb.append(uiMode&UI_MODE_NIGHT_MASK); break;
+ }
+ if (screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
+ sb.append(" w"); sb.append(screenWidthDp); sb.append("dp");
+ } else {
+ sb.append("?wdp");
+ }
+ if (screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+ sb.append(" h"); sb.append(screenHeightDp); sb.append("dp");
+ } else {
+ sb.append("?hdp");
+ }
if (seq != 0) {
- sb.append(" seq=");
+ sb.append(" s.");
sb.append(seq);
}
sb.append('}');
@@ -345,6 +426,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_SIZE_UNDEFINED;
uiMode = UI_MODE_TYPE_UNDEFINED;
+ screenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+ screenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
seq = 0;
}
@@ -438,6 +521,16 @@ public final class Configuration implements Parcelable, Comparable<Configuration
| (delta.uiMode&UI_MODE_NIGHT_MASK);
}
}
+ if (delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+ && screenWidthDp != delta.screenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ screenWidthDp = delta.screenWidthDp;
+ }
+ if (delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED
+ && screenHeightDp != delta.screenHeightDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ screenHeightDp = delta.screenHeightDp;
+ }
if (delta.seq != 0) {
seq = delta.seq;
@@ -467,9 +560,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION
* PackageManager.ActivityInfo.CONFIG_NAVIGATION},
* {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
- * PackageManager.ActivityInfo.CONFIG_ORIENTATION}, or
+ * PackageManager.ActivityInfo.CONFIG_ORIENTATION},
* {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT
- * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}.
+ * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}, or
+ * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE
+ * PackageManager.ActivityInfo.CONFIG_SCREEN_SIZE}.
*/
public int diff(Configuration delta) {
int changed = 0;
@@ -522,6 +617,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& uiMode != delta.uiMode) {
changed |= ActivityInfo.CONFIG_UI_MODE;
}
+ if (delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+ && screenWidthDp != delta.screenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
+ if (delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED
+ && screenHeightDp != delta.screenHeightDp) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
return changed;
}
@@ -603,6 +706,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(orientation);
dest.writeInt(screenLayout);
dest.writeInt(uiMode);
+ dest.writeInt(screenWidthDp);
+ dest.writeInt(screenHeightDp);
dest.writeInt(seq);
}
@@ -624,6 +729,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
orientation = source.readInt();
screenLayout = source.readInt();
uiMode = source.readInt();
+ screenWidthDp = source.readInt();
+ screenHeightDp = source.readInt();
seq = source.readInt();
}
@@ -684,6 +791,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.screenLayout - that.screenLayout;
if (n != 0) return n;
n = this.uiMode - that.uiMode;
+ if (n != 0) return n;
+ n = this.screenWidthDp - that.screenWidthDp;
+ if (n != 0) return n;
+ n = this.screenHeightDp - that.screenHeightDp;
//if (n != 0) return n;
return n;
}
@@ -708,6 +819,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
+ this.touchscreen
+ this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
+ this.navigation + this.navigationHidden
- + this.orientation + this.screenLayout + this.uiMode;
+ + this.orientation + this.screenLayout + this.uiMode
+ + this.screenWidthDp + this.screenHeightDp;
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 81eb09c..2e6ae70 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -21,6 +21,7 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.content.pm.ActivityInfo;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
@@ -1404,6 +1405,7 @@ public class Resources {
int configChanges = 0xfffffff;
if (config != null) {
configChanges = mConfiguration.updateFrom(config);
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
@@ -1443,6 +1445,7 @@ public class Resources {
mConfiguration.touchscreen,
(int)(mMetrics.density*160), mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
+ mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
Build.VERSION.RESOURCES_SDK_INT);
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 23a6f97..63e33ce 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -18,7 +18,6 @@ package android.content.res;
import android.text.*;
import android.text.style.*;
-import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.graphics.Paint;
@@ -34,7 +33,7 @@ import com.android.internal.util.XmlUtils;
*/
final class StringBlock {
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean localLOGV = false || false;
private final int mNative;
private final boolean mUseSparse;
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 3ffc714..b6487bd 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -19,7 +19,6 @@ package android.database;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
-import android.util.Config;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -285,7 +284,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
- if (Config.LOGV) {
+ if (false) {
if (getCount() > 0) {
Log.w("AbstractCursor", "Unknown column " + columnName);
}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 8bc7de2..8fa4d3b 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -19,7 +19,6 @@ package android.database;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Config;
import android.util.Log;
@@ -77,7 +76,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
if (mCursor instanceof AbstractWindowedCursor) {
AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
if (windowedCursor.hasWindow()) {
- if (Log.isLoggable(TAG, Log.VERBOSE) || Config.LOGV) {
+ if (Log.isLoggable(TAG, Log.VERBOSE) || false) {
Log.v(TAG, "Cross process cursor has a local window before setWindow in "
+ providerName, new RuntimeException());
}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index f428aad..8e6f699 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -33,7 +33,6 @@ import android.database.sqlite.SQLiteStatement;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.io.FileNotFoundException;
@@ -49,7 +48,7 @@ public class DatabaseUtils {
private static final String TAG = "DatabaseUtils";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final String[] countProjection = new String[]{"count(*)"};
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 4c2d123..ea9346d 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -23,7 +23,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.StrictMode;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -241,7 +240,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
mColumnNameMap = null;
mQuery = query;
- query.mDatabase.lock();
+ query.mDatabase.lock(query.mSql);
try {
// Setup the list of columns
int columnCount = mQuery.columnCountLocked();
@@ -251,7 +250,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
for (int i = 0; i < columnCount; i++) {
String columnName = mQuery.columnNameLocked(i);
mColumns[i] = columnName;
- if (Config.LOGV) {
+ if (false) {
Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+ mColumns[i]);
}
@@ -366,13 +365,13 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
private void deactivateCommon() {
- if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
+ if (false) Log.v(TAG, "<<< Releasing cursor " + this);
mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
- if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
+ if (false) Log.v("DatabaseWindow", "closing window in release()");
}
@Override
@@ -398,7 +397,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
return false;
}
long timeStart = 0;
- if (Config.LOGV) {
+ if (false) {
timeStart = System.currentTimeMillis();
}
@@ -419,7 +418,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// since we need to use a different database connection handle,
// re-compile the query
try {
- db.lock();
+ db.lock(mQuery.mSql);
} catch (IllegalStateException e) {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
@@ -453,7 +452,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
}
- if (Config.LOGV) {
+ if (false) {
Log.v("DatabaseWindow", "closing window in requery()");
Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
}
@@ -465,7 +464,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
}
- if (Config.LOGV) {
+ if (false) {
long timeEnd = System.currentTimeMillis();
Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
}
@@ -513,7 +512,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() +
", table = " + mEditTable + ", query = " + mQuery.mSql);
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 90a5b5d..93a6ad3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -30,7 +30,6 @@ import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.util.LruCache;
@@ -230,9 +229,23 @@ public class SQLiteDatabase extends SQLiteClosable {
private static int sQueryLogTimeInMillis = 0; // lazily initialized
private static final int QUERY_LOG_SQL_LENGTH = 64;
private static final String COMMIT_SQL = "COMMIT;";
+ private static final String BEGIN_SQL = "BEGIN;";
private final Random mRandom = new Random();
+ /** the last non-commit/rollback sql statement in a transaction */
+ // guarded by 'this'
private String mLastSqlStatement = null;
+ synchronized String getLastSqlStatement() {
+ return mLastSqlStatement;
+ }
+
+ synchronized void setLastSqlStatement(String sql) {
+ mLastSqlStatement = sql;
+ }
+
+ /** guarded by {@link #mLock} */
+ private long mTransStartTime;
+
// String prefix for slow database query EventLog records that show
// lock acquistions of the database.
/* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
@@ -386,11 +399,16 @@ public class SQLiteDatabase extends SQLiteClosable {
*
* @see #unlock()
*/
- /* package */ void lock() {
- lock(false);
+ /* package */ void lock(String sql) {
+ lock(sql, false);
+ }
+
+ /* pachage */ void lock() {
+ lock(null, false);
}
+
private static final long LOCK_WAIT_PERIOD = 30L;
- private void lock(boolean forced) {
+ private void lock(String sql, boolean forced) {
// make sure this method is NOT being called from a 'synchronized' method
if (Thread.holdsLock(this)) {
Log.w(TAG, "don't lock() while in a synchronized method");
@@ -398,6 +416,7 @@ public class SQLiteDatabase extends SQLiteClosable {
verifyDbIsOpen();
if (!forced && !mLockingEnabled) return;
boolean done = false;
+ long timeStart = SystemClock.uptimeMillis();
while (!done) {
try {
// wait for 30sec to acquire the lock
@@ -420,6 +439,9 @@ public class SQLiteDatabase extends SQLiteClosable {
mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
}
}
+ if (sql != null) {
+ logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
+ }
}
private static class DatabaseReentrantLock extends ReentrantLock {
DatabaseReentrantLock(boolean fair) {
@@ -444,7 +466,11 @@ public class SQLiteDatabase extends SQLiteClosable {
* @see #unlockForced()
*/
private void lockForced() {
- lock(true);
+ lock(null, true);
+ }
+
+ private void lockForced(String sql) {
+ lock(sql, true);
}
/**
@@ -612,7 +638,7 @@ public class SQLiteDatabase extends SQLiteClosable {
private void beginTransaction(SQLiteTransactionListener transactionListener,
boolean exclusive) {
verifyDbIsOpen();
- lockForced();
+ lockForced(BEGIN_SQL);
boolean ok = false;
try {
// If this thread already had the lock then get out
@@ -635,6 +661,7 @@ public class SQLiteDatabase extends SQLiteClosable {
} else {
execSQL("BEGIN IMMEDIATE;");
}
+ mTransStartTime = SystemClock.uptimeMillis();
mTransactionListener = transactionListener;
mTransactionIsSuccessful = true;
mInnerTransactionIsSuccessful = false;
@@ -698,6 +725,8 @@ public class SQLiteDatabase extends SQLiteClosable {
Log.i(TAG, "PRAGMA wal_Checkpoint done");
}
}
+ // log the transaction time to the Eventlog.
+ logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
} else {
try {
execSQL("ROLLBACK;");
@@ -705,7 +734,7 @@ public class SQLiteDatabase extends SQLiteClosable {
throw savedException;
}
} catch (SQLException e) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "exception during rollback, maybe the DB previously "
+ "performed an auto-rollback");
}
@@ -714,7 +743,7 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
mTransactionListener = null;
unlockForced();
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "unlocked " + Thread.currentThread()
+ ", holdCount is " + mLock.getHoldCount());
}
@@ -1527,7 +1556,7 @@ public class SQLiteDatabase extends SQLiteClosable {
BlockGuard.getThreadPolicy().onReadFromDisk();
long timeStart = 0;
- if (Config.LOGV || mSlowQueryThreshold != -1) {
+ if (false || mSlowQueryThreshold != -1) {
timeStart = System.currentTimeMillis();
}
@@ -1540,7 +1569,7 @@ public class SQLiteDatabase extends SQLiteClosable {
cursorFactory != null ? cursorFactory : mFactory,
selectionArgs);
} finally {
- if (Config.LOGV || mSlowQueryThreshold != -1) {
+ if (false || mSlowQueryThreshold != -1) {
// Force query execution
int count = -1;
@@ -1550,7 +1579,7 @@ public class SQLiteDatabase extends SQLiteClosable {
long duration = System.currentTimeMillis() - timeStart;
- if (Config.LOGV || duration >= mSlowQueryThreshold) {
+ if (false || duration >= mSlowQueryThreshold) {
Log.v(SQLiteCursor.TAG,
"query (" + duration + " ms): " + driver.toString() + ", args are "
+ (selectionArgs != null
@@ -1855,24 +1884,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException if the SQL string is invalid
*/
public void execSQL(String sql) throws SQLException {
- int stmtType = DatabaseUtils.getSqlStatementType(sql);
- if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
- disableWriteAheadLogging();
- }
- long timeStart = SystemClock.uptimeMillis();
- logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
executeSql(sql, null);
-
- if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
- mHasAttachedDbs = true;
- }
- // Log commit statements along with the most recently executed
- // SQL statement for disambiguation.
- if (stmtType == DatabaseUtils.STATEMENT_COMMIT) {
- logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
- } else {
- logTimeStat(sql, timeStart, null);
- }
}
/**
@@ -1926,19 +1938,19 @@ public class SQLiteDatabase extends SQLiteClosable {
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
- long timeStart = SystemClock.uptimeMillis();
- int n;
+ if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+ disableWriteAheadLogging();
+ mHasAttachedDbs = true;
+ }
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
- n = statement.executeUpdateDelete();
+ return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
- logTimeStat(sql, timeStart);
- return n;
}
@Override
@@ -2027,12 +2039,7 @@ public class SQLiteDatabase extends SQLiteClosable {
logTimeStat(sql, beginMillis, null);
}
- /* package */ void logTimeStat(String sql, long beginMillis, String prefix) {
- // Keep track of the last statement executed here, as this is
- // the common funnel through which all methods of hitting
- // libsqlite eventually flow.
- mLastSqlStatement = sql;
-
+ private void logTimeStat(String sql, long beginMillis, String prefix) {
// Sample fast queries in proportion to the time taken.
// Quantize the % first, so the logged sampling probability
// exactly equals the actual sampling rate for this query.
@@ -2059,7 +2066,6 @@ public class SQLiteDatabase extends SQLiteClosable {
if (prefix != null) {
sql = prefix + sql;
}
-
if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
// ActivityThread.currentPackageName() only returns non-null if the
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index de2fca9..a5e762e 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -42,7 +42,7 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
SQLiteQuery query = null;
try {
- mDatabase.lock();
+ mDatabase.lock(mSql);
mDatabase.closePendingStatements();
query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 88246e8..89552dc 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -105,12 +105,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
case DatabaseUtils.STATEMENT_SELECT:
mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
break;
- case DatabaseUtils.STATEMENT_ATTACH:
case DatabaseUtils.STATEMENT_BEGIN:
case DatabaseUtils.STATEMENT_COMMIT:
case DatabaseUtils.STATEMENT_ABORT:
- case DatabaseUtils.STATEMENT_DDL:
- case DatabaseUtils.STATEMENT_UNPREPARED:
mStatementType = n | STATEMENT_DONT_PREPARE;
break;
default:
@@ -353,13 +350,10 @@ public abstract class SQLiteProgram extends SQLiteClosable {
/* package */ void compileAndbindAllArgs() {
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
- // no need to prepare this SQL statement
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- if (mBindArgs != null) {
- throw new IllegalArgumentException("no need to pass bindargs for this sql :" +
- mSql);
- }
+ if (mBindArgs != null) {
+ throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql);
}
+ // no need to prepare this SQL statement
return;
}
if (nStatement == 0) {
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index e9e0172..dc882d9 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -70,9 +70,8 @@ public class SQLiteQuery extends SQLiteProgram {
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
+ mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
- mDatabase.lock();
- mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index c76cc6c..ff973a7 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -80,7 +80,8 @@ public class SQLiteStatement extends SQLiteProgram
*/
public int executeUpdateDelete() {
try {
- long timeStart = acquireAndLock(WRITE);
+ saveSqlAsLastSqlStatement();
+ acquireAndLock(WRITE);
int numChanges = 0;
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
// since the statement doesn't have to be prepared,
@@ -90,7 +91,6 @@ public class SQLiteStatement extends SQLiteProgram
} else {
numChanges = native_execute();
}
- mDatabase.logTimeStat(mSql, timeStart);
return numChanges;
} finally {
releaseAndUnlock();
@@ -108,15 +108,22 @@ public class SQLiteStatement extends SQLiteProgram
*/
public long executeInsert() {
try {
- long timeStart = acquireAndLock(WRITE);
- long lastInsertedRowId = native_executeInsert();
- mDatabase.logTimeStat(mSql, timeStart);
- return lastInsertedRowId;
+ saveSqlAsLastSqlStatement();
+ acquireAndLock(WRITE);
+ return native_executeInsert();
} finally {
releaseAndUnlock();
}
}
+ private void saveSqlAsLastSqlStatement() {
+ if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+ DatabaseUtils.STATEMENT_UPDATE) ||
+ (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
+ DatabaseUtils.STATEMENT_BEGIN) {
+ mDatabase.setLastSqlStatement(mSql);
+ }
+ }
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
@@ -199,7 +206,7 @@ public class SQLiteStatement extends SQLiteProgram
* <li>if the SQL statement is an update, start transaction if not already in one.
* otherwise, get lock on the database</li>
* <li>acquire reference on this object</li>
- * <li>and then return the current time _before_ the database lock was acquired</li>
+ * <li>and then return the current time _after_ the database lock was acquired</li>
* </ul>
* <p>
* This method removes the duplicate code from the other public
@@ -243,7 +250,7 @@ public class SQLiteStatement extends SQLiteProgram
}
// do I have database lock? if not, grab it.
if (!mDatabase.isDbLockedByCurrentThread()) {
- mDatabase.lock();
+ mDatabase.lock(mSql);
mState = LOCK_ACQUIRED;
}
diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java
index 4a57d12..78dd23e 100644
--- a/core/java/android/ddm/DdmHandleAppName.java
+++ b/core/java/android/ddm/DdmHandleAppName.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -88,7 +87,7 @@ public class DdmHandleAppName extends ChunkHandler {
* Send an APNM (APplication NaMe) chunk.
*/
private static void sendAPNM(String appName) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm", "Sending app name");
ByteBuffer out = ByteBuffer.allocate(4 + appName.length()*2);
diff --git a/core/java/android/ddm/DdmHandleExit.java b/core/java/android/ddm/DdmHandleExit.java
index 8a0b9a4..74ae37a 100644
--- a/core/java/android/ddm/DdmHandleExit.java
+++ b/core/java/android/ddm/DdmHandleExit.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -59,7 +58,7 @@ public class DdmHandleExit extends ChunkHandler {
* Handle a chunk of data. We're only registered for "EXIT".
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-exit", "Handling " + name(request.type) + " chunk");
/*
diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java
index fa0fbbf..cece556 100644
--- a/core/java/android/ddm/DdmHandleHeap.java
+++ b/core/java/android/ddm/DdmHandleHeap.java
@@ -21,7 +21,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
import android.os.Debug;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -78,7 +77,7 @@ public class DdmHandleHeap extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -113,7 +112,7 @@ public class DdmHandleHeap extends ChunkHandler {
ByteBuffer in = wrapChunk(request);
int when = in.get();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Heap segment enable: when=" + when);
boolean ok = DdmVmInternal.heapInfoNotify(when);
@@ -132,7 +131,7 @@ public class DdmHandleHeap extends ChunkHandler {
int when = in.get();
int what = in.get();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Heap segment enable: when=" + when
+ ", what=" + what + ", isNative=" + isNative);
@@ -160,7 +159,7 @@ public class DdmHandleHeap extends ChunkHandler {
/* get the filename for the output file */
int len = in.getInt();
String fileName = getString(in, len);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap dump: file='" + fileName + "'");
try {
@@ -192,7 +191,7 @@ public class DdmHandleHeap extends ChunkHandler {
byte result;
/* get the filename for the output file */
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap dump: [DDMS]");
String failMsg = null;
@@ -218,7 +217,7 @@ public class DdmHandleHeap extends ChunkHandler {
private Chunk handleHPGC(Chunk request) {
//ByteBuffer in = wrapChunk(request);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Heap GC request");
System.gc();
@@ -234,7 +233,7 @@ public class DdmHandleHeap extends ChunkHandler {
enable = (in.get() != 0);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Recent allocation enable request: " + enable);
DdmVmInternal.enableRecentAllocations(enable);
@@ -259,7 +258,7 @@ public class DdmHandleHeap extends ChunkHandler {
private Chunk handleREAL(Chunk request) {
//ByteBuffer in = wrapChunk(request);
- if (Config.LOGD)
+ if (false)
Log.d("ddm-heap", "Recent allocations request");
/* generate the reply in a ready-to-go format */
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 714a611..5088d22 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -19,7 +19,6 @@ package android.ddm;
import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
import android.os.Debug;
@@ -53,7 +52,7 @@ public class DdmHandleHello extends ChunkHandler {
* send messages to the server.
*/
public void connected() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Connected!");
if (false) {
@@ -70,7 +69,7 @@ public class DdmHandleHello extends ChunkHandler {
* periodic transmissions or clean up saved state.
*/
public void disconnected() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Disconnected!");
}
@@ -78,7 +77,7 @@ public class DdmHandleHello extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -105,7 +104,7 @@ public class DdmHandleHello extends ChunkHandler {
ByteBuffer in = wrapChunk(request);
int serverProtoVers = in.getInt();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-hello", "Server version is " + serverProtoVers);
/*
@@ -150,7 +149,7 @@ public class DdmHandleHello extends ChunkHandler {
// is actually compiled in
final String[] features = Debug.getVmFeatureList();
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Got feature list request");
int size = 4 + 4 * features.length;
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index 63ee445..e0db5e7 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.os.Debug;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -69,7 +68,7 @@ public class DdmHandleProfiling extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Handling " + name(request.type) + " chunk");
int type = request.type;
@@ -99,7 +98,7 @@ public class DdmHandleProfiling extends ChunkHandler {
int flags = in.getInt();
int len = in.getInt();
String fileName = getString(in, len);
- if (Config.LOGV)
+ if (false)
Log.v("ddm-heap", "Method profiling start: filename='" + fileName
+ "', size=" + bufferSize + ", flags=" + flags);
@@ -139,7 +138,7 @@ public class DdmHandleProfiling extends ChunkHandler {
int bufferSize = in.getInt();
int flags = in.getInt();
- if (Config.LOGV) {
+ if (false) {
Log.v("ddm-heap", "Method prof stream start: size=" + bufferSize
+ ", flags=" + flags);
}
@@ -158,7 +157,7 @@ public class DdmHandleProfiling extends ChunkHandler {
private Chunk handleMPSE(Chunk request) {
byte result;
- if (Config.LOGV) {
+ if (false) {
Log.v("ddm-heap", "Method prof stream end");
}
diff --git a/core/java/android/ddm/DdmHandleThread.java b/core/java/android/ddm/DdmHandleThread.java
index c307988..613ab75 100644
--- a/core/java/android/ddm/DdmHandleThread.java
+++ b/core/java/android/ddm/DdmHandleThread.java
@@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
-import android.util.Config;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -66,7 +65,7 @@ public class DdmHandleThread extends ChunkHandler {
* Handle a chunk of data.
*/
public Chunk handleChunk(Chunk request) {
- if (Config.LOGV)
+ if (false)
Log.v("ddm-thread", "Handling " + name(request.type) + " chunk");
int type = request.type;
diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java
index debf189..ecd450d 100644
--- a/core/java/android/ddm/DdmRegister.java
+++ b/core/java/android/ddm/DdmRegister.java
@@ -17,7 +17,6 @@
package android.ddm;
import org.apache.harmony.dalvik.ddmc.DdmServer;
-import android.util.Config;
import android.util.Log;
/**
@@ -44,7 +43,7 @@ public class DdmRegister {
* we finish here.
*/
public static void registerHandlers() {
- if (Config.LOGV)
+ if (false)
Log.v("ddm", "Registering DDM message handlers");
DdmHandleHello.register();
DdmHandleThread.register();
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 97f0e1b..77c2d1b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.graphics.ImageFormat;
+import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
@@ -374,6 +375,12 @@ public class Camera {
* The preview surface texture may not otherwise change while preview is
* running.
*
+ * The timestamps provided by {@link SurfaceTexture#getTimestamp()} for a
+ * SurfaceTexture set as the preview texture have an unspecified zero point,
+ * and cannot be directly compared between different cameras or different
+ * instances of the same camera, or across multiple runs of the same
+ * program.
+ *
* @param surfaceTexture the {@link SurfaceTexture} to which the preview
* images are to be sent or null to remove the current preview surface
* texture
@@ -410,8 +417,9 @@ public class Camera {
/**
* Starts capturing and drawing preview frames to the screen.
- * Preview will not actually start until a surface is supplied with
- * {@link #setPreviewDisplay(SurfaceHolder)}.
+ * Preview will not actually start until a surface is supplied
+ * with {@link #setPreviewDisplay(SurfaceHolder)} or
+ * {@link #setPreviewTexture(SurfaceTexture)}.
*
* <p>If {@link #setPreviewCallback(Camera.PreviewCallback)},
* {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}, or
@@ -1076,6 +1084,52 @@ public class Camera {
};
/**
+ * Area class for focus.
+ *
+ * @see #setFocusAreas(List)
+ * @see #getFocusAreas()
+ * @hide
+ */
+ public static class Area {
+ /**
+ * Create an area with specified rectangle and weight.
+ *
+ * @param rect the rectangle of the area
+ * @param weight the weight of the area
+ */
+ public Area(Rect rect, int weight) {
+ this.rect = rect;
+ this.weight = weight;
+ }
+ /**
+ * Compares {@code obj} to this area.
+ *
+ * @param obj the object to compare this area with.
+ * @return {@code true} if the rectangle and weight of {@code obj} is
+ * the same as those of this area. {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Area)) {
+ return false;
+ }
+ Area a = (Area) obj;
+ if (rect == null) {
+ if (a.rect != null) return false;
+ } else {
+ if (!rect.equals(a.rect)) return false;
+ }
+ return weight == a.weight;
+ }
+
+ /** rectangle of the area */
+ public Rect rect;
+
+ /** weight of the area */
+ public int weight;
+ };
+
+ /**
* Camera service settings.
*
* <p>To make camera parameters take effect, applications have to call
@@ -1117,6 +1171,8 @@ public class Camera {
private static final String KEY_SCENE_MODE = "scene-mode";
private static final String KEY_FLASH_MODE = "flash-mode";
private static final String KEY_FOCUS_MODE = "focus-mode";
+ private static final String KEY_FOCUS_AREAS = "focus-areas";
+ private static final String KEY_MAX_NUM_FOCUS_AREAS = "max-num-focus-areas";
private static final String KEY_FOCAL_LENGTH = "focal-length";
private static final String KEY_HORIZONTAL_VIEW_ANGLE = "horizontal-view-angle";
private static final String KEY_VERTICAL_VIEW_ANGLE = "vertical-view-angle";
@@ -1124,6 +1180,8 @@ public class Camera {
private static final String KEY_MAX_EXPOSURE_COMPENSATION = "max-exposure-compensation";
private static final String KEY_MIN_EXPOSURE_COMPENSATION = "min-exposure-compensation";
private static final String KEY_EXPOSURE_COMPENSATION_STEP = "exposure-compensation-step";
+ private static final String KEY_METERING_AREAS = "metering-areas";
+ private static final String KEY_MAX_NUM_METERING_AREAS = "max-num-metering-areas";
private static final String KEY_ZOOM = "zoom";
private static final String KEY_MAX_ZOOM = "max-zoom";
private static final String KEY_ZOOM_RATIOS = "zoom-ratios";
@@ -1462,6 +1520,27 @@ public class Camera {
mMap.put(key, Integer.toString(value));
}
+ private void set(String key, List<Area> areas) {
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < areas.size(); i++) {
+ Area area = areas.get(i);
+ Rect rect = area.rect;
+ buffer.append('(');
+ buffer.append(rect.left);
+ buffer.append(',');
+ buffer.append(rect.top);
+ buffer.append(',');
+ buffer.append(rect.right);
+ buffer.append(',');
+ buffer.append(rect.bottom);
+ buffer.append(',');
+ buffer.append(area.weight);
+ buffer.append(')');
+ if (i != areas.size() - 1) buffer.append(',');
+ }
+ set(key, buffer.toString());
+ }
+
/**
* Returns the value of a String parameter.
*
@@ -2492,6 +2571,144 @@ public class Camera {
splitFloat(get(KEY_FOCUS_DISTANCES), output);
}
+ /**
+ * Gets the maximum number of focus areas supported. This is the maximum
+ * length of the list in {@link #setFocusAreas(List)} and
+ * {@link #getFocusAreas()}.
+ *
+ * @return the maximum number of focus areas supported by the camera.
+ * @see #getFocusAreas()
+ * @hide
+ */
+ public int getMaxNumFocusAreas() {
+ return getInt(KEY_MAX_NUM_FOCUS_AREAS, 0);
+ }
+
+ /**
+ * Gets the current focus areas. Camera driver uses the areas to decide
+ * focus.
+ *
+ * Before using this API or {@link #setFocusAreas(List)}, apps should
+ * call {@link #getMaxNumFocusAreas()} to know the maximum number of
+ * focus areas first. If the value is 0, focus area is not supported.
+ *
+ * Each focus area is a rectangle with specified weight. The direction
+ * is relative to the sensor orientation, that is, what the sensor sees.
+ * The direction is not affected by the rotation or mirroring of
+ * {@link #setDisplayOrientation(int)}. Coordinates of the rectangle
+ * range from -1000 to 1000. (-1000, -1000) is the upper left point.
+ * (1000, 1000) is the lower right point. The length and width of focus
+ * areas cannot be 0 or negative.
+ *
+ * The weight must range from 1 to 1000. The weight should be
+ * interpreted as a per-pixel weight - all pixels in the area have the
+ * specified weight. This means a small area with the same weight as a
+ * larger area will have less influence on the focusing than the larger
+ * area. Focus areas can partially overlap and the driver will add the
+ * weights in the overlap region.
+ *
+ * A special case of all-zero single focus area means driver to decide
+ * the focus area. For example, the driver may use more signals to
+ * decide focus areas and change them dynamically. Apps can set all-zero
+ * if they want the driver to decide focus areas.
+ *
+ * Focus areas are relative to the current field of view
+ * ({@link #getZoom()}). No matter what the zoom level is, (-1000,-1000)
+ * represents the top of the currently visible camera frame. The focus
+ * area cannot be set to be outside the current field of view, even
+ * when using zoom.
+ *
+ * Focus area only has effect if the current focus mode is
+ * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, or
+ * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}.
+ *
+ * @return a list of current focus areas
+ * @hide
+ */
+ public List<Area> getFocusAreas() {
+ return splitArea(KEY_FOCUS_AREAS);
+ }
+
+ /**
+ * Sets focus areas. See {@link #getFocusAreas()} for documentation.
+ *
+ * @param focusAreas the focus areas
+ * @see #getFocusAreas()
+ * @hide
+ */
+ public void setFocusAreas(List<Area> focusAreas) {
+ set(KEY_FOCUS_AREAS, focusAreas);
+ }
+
+ /**
+ * Gets the maximum number of metering areas supported. This is the
+ * maximum length of the list in {@link #setMeteringAreas(List)} and
+ * {@link #getMeteringAreas()}.
+ *
+ * @return the maximum number of metering areas supported by the camera.
+ * @see #getMeteringAreas()
+ * @hide
+ */
+ public int getMaxNumMeteringAreas() {
+ return getInt(KEY_MAX_NUM_METERING_AREAS, 0);
+ }
+
+ /**
+ * Gets the current metering areas. Camera driver uses these areas to
+ * decide exposure.
+ *
+ * Before using this API or {@link #setMeteringAreas(List)}, apps should
+ * call {@link #getMaxNumMeteringAreas()} to know the maximum number of
+ * metering areas first. If the value is 0, metering area is not
+ * supported.
+ *
+ * Each metering area is a rectangle with specified weight. The
+ * direction is relative to the sensor orientation, that is, what the
+ * sensor sees. The direction is not affected by the rotation or
+ * mirroring of {@link #setDisplayOrientation(int)}. Coordinates of the
+ * rectangle range from -1000 to 1000. (-1000, -1000) is the upper left
+ * point. (1000, 1000) is the lower right point. The length and width of
+ * metering areas cannot be 0 or negative.
+ *
+ * The weight must range from 1 to 1000, and represents a weight for
+ * every pixel in the area. This means that a large metering area with
+ * the same weight as a smaller area will have more effect in the
+ * metering result. Metering areas can partially overlap and the driver
+ * will add the weights in the overlap region.
+ *
+ * A special case of all-zero single metering area means driver to
+ * decide the metering area. For example, the driver may use more
+ * signals to decide metering areas and change them dynamically. Apps
+ * can set all-zero if they want the driver to decide metering areas.
+ *
+ * Metering areas are relative to the current field of view
+ * ({@link #getZoom()}). No matter what the zoom level is, (-1000,-1000)
+ * represents the top of the currently visible camera frame. The
+ * metering area cannot be set to be outside the current field of view,
+ * even when using zoom.
+ *
+ * No matter what metering areas are, the final exposure are compensated
+ * by {@link #setExposureCompensation(int)}.
+ *
+ * @return a list of current metering areas
+ * @hide
+ */
+ public List<Area> getMeteringAreas() {
+ return splitArea(KEY_METERING_AREAS);
+ }
+
+ /**
+ * Sets metering areas. See {@link #getMeteringAreas()} for
+ * documentation.
+ *
+ * @param meteringAreas the metering areas
+ * @see #getMeteringAreas()
+ * @hide
+ */
+ public void setMeteringAreas(List<Area> meteringAreas) {
+ set(KEY_METERING_AREAS, meteringAreas);
+ }
+
// Splits a comma delimited string to an ArrayList of String.
// Return null if the passing string is null or the size is 0.
private ArrayList<String> split(String str) {
@@ -2617,5 +2834,31 @@ public class Camera {
if (rangeList.size() == 0) return null;
return rangeList;
}
+
+ // Splits a comma delimited string to an ArrayList of Area objects.
+ // Example string: "(-10,-10,0,0,300),(0,0,10,10,700)". Return null if
+ // the passing string is null or the size is 0.
+ private ArrayList<Area> splitArea(String str) {
+ if (str == null || str.charAt(0) != '('
+ || str.charAt(str.length() - 1) != ')') {
+ Log.e(TAG, "Invalid area string=" + str);
+ return null;
+ }
+
+ ArrayList<Area> result = new ArrayList<Area>();
+ int endIndex, fromIndex = 1;
+ int[] array = new int[5];
+ do {
+ endIndex = str.indexOf("),(", fromIndex);
+ if (endIndex == -1) endIndex = str.length() - 1;
+ splitInt(str.substring(fromIndex, endIndex), array);
+ Rect rect = new Rect(array[0], array[1], array[2], array[3]);
+ result.add(new Area(rect, array[4]));
+ fromIndex = endIndex + 3;
+ } while (endIndex != str.length() - 1);
+
+ if (result.size() == 0) return null;
+ return result;
+ }
};
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index f2b907b..a4ba3bd 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -66,7 +66,14 @@ public class Sensor {
/** A constant describing a pressure sensor type */
public static final int TYPE_PRESSURE = 6;
- /** A constant describing a temperature sensor type */
+ /**
+ * A constant describing a temperature sensor type
+ *
+ * @deprecated use
+ * {@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE
+ * Sensor.TYPE_AMBIENT_TEMPERATURE} instead.
+ */
+ @Deprecated
public static final int TYPE_TEMPERATURE = 7;
/**
@@ -97,6 +104,9 @@ public class Sensor {
*/
public static final int TYPE_ROTATION_VECTOR = 11;
+ /** A constant describing an ambient temperature sensor type */
+ public static final int TYPE_AMBIENT_TEMPERATURE = 13;
+
/**
* A constant describing all sensor types.
*/
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 78d7991..91f0098 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -305,6 +305,14 @@ public class SensorEvent {
* positive in the counter-clockwise direction).
* </p>
*
+ * <h4>{@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE Sensor.TYPE_AMBIENT_TEMPERATURE}:
+ * </h4>
+ *
+ * <ul>
+ * <p>
+ * values[0]: ambient (room) temperature in degree Celsius.
+ * </ul>
+ *
* @see SensorEvent
* @see GeomagneticField
*/
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index ab5c78a..dfc70ef 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -142,7 +142,8 @@ public class KeyboardView extends View implements View.OnClickListener {
private int mPreviewTextSizeLarge;
private int mPreviewOffset;
private int mPreviewHeight;
- private int[] mOffsetInWindow;
+ // Working variable
+ private final int[] mCoordinates = new int[2];
private PopupWindow mPopupKeyboard;
private View mMiniKeyboardContainer;
@@ -152,7 +153,6 @@ public class KeyboardView extends View implements View.OnClickListener {
private int mMiniKeyboardOffsetX;
private int mMiniKeyboardOffsetY;
private Map<Key,View> mMiniKeyboardCache;
- private int[] mWindowOffset;
private Key[] mKeys;
/** Listener for {@link OnKeyboardActionListener}. */
@@ -905,23 +905,19 @@ public class KeyboardView extends View implements View.OnClickListener {
mPopupPreviewY = - mPreviewText.getMeasuredHeight();
}
mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- if (mOffsetInWindow == null) {
- mOffsetInWindow = new int[2];
- getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
- int[] mWindowLocation = new int[2];
- getLocationOnScreen(mWindowLocation);
- mWindowY = mWindowLocation[1];
- }
+ getLocationInWindow(mCoordinates);
+ mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
+
// Set the preview background state
mPreviewText.getBackground().setState(
key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- mPopupPreviewX += mOffsetInWindow[0];
- mPopupPreviewY += mOffsetInWindow[1];
+ mPopupPreviewX += mCoordinates[0];
+ mPopupPreviewY += mCoordinates[1];
// If the popup cannot be shown above the key, put it on the side
- if (mPopupPreviewY + mWindowY < 0) {
+ getLocationOnScreen(mCoordinates);
+ if (mPopupPreviewY + mCoordinates[1] < 0) {
// If the key you're pressing is on the left side of the keyboard, show the popup on
// the right, offset by enough to see at least one key to the left/right.
if (key.x + key.width <= getWidth() / 2) {
@@ -1057,16 +1053,13 @@ public class KeyboardView extends View implements View.OnClickListener {
mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
com.android.internal.R.id.keyboardView);
}
- if (mWindowOffset == null) {
- mWindowOffset = new int[2];
- getLocationInWindow(mWindowOffset);
- }
+ getLocationInWindow(mCoordinates);
mPopupX = popupKey.x + mPaddingLeft;
mPopupY = popupKey.y + mPaddingTop;
mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
- final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
- final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+ final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
+ final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
mMiniKeyboard.setShifted(isShifted());
mPopupKeyboard.setContentView(mMiniKeyboardContainer);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index b541ec3..419288b 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -215,15 +215,20 @@ public class ConnectivityManager
/**
* Bluetooth data connection. This is used for Bluetooth reverse tethering.
- * @hide
*/
public static final int TYPE_BLUETOOTH = 7;
- /** {@hide} */
+ /**
+ * Dummy data connection. This should not be used on shipping devices.
+ */
public static final int TYPE_DUMMY = 8;
- /** {@hide} */
+ /**
+ * Ethernet data connection. This may be via USB dongle or more
+ * traditional means.
+ */
public static final int TYPE_ETHERNET = 9;
+
/**
* Over the air Adminstration.
* {@hide}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index f8f8a29..3bf64b2 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -17,18 +17,12 @@
package android.net;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
@@ -40,7 +34,6 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
@@ -128,7 +121,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return a new SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
@@ -144,7 +137,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return an insecure SSLSocketFactory with the specified parameters
*/
public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
@@ -157,12 +150,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, or null for no cache.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
* @return a new SocketFactory with the specified parameters
*/
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
- int handshakeTimeoutMillis,
- SSLSessionCache cache) {
+ int handshakeTimeoutMillis, SSLSessionCache cache) {
return new org.apache.http.conn.ssl.SSLSocketFactory(
new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
}
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 3e21e2d..316440f 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -17,7 +17,6 @@
package android.net;
import android.os.SystemClock;
-import android.util.Config;
import android.util.Log;
import java.io.IOException;
@@ -112,8 +111,8 @@ public class SntpClient
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
- // if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
- // if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");
+ // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
+ // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");
// save our results - use the times on this side of the network latency
// (response rather than request time)
@@ -121,7 +120,7 @@ public class SntpClient
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
- if (Config.LOGD) Log.d(TAG, "request time failed: " + e);
+ if (false) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
index 74c0de8..657e071 100644
--- a/core/java/android/net/http/Headers.java
+++ b/core/java/android/net/http/Headers.java
@@ -16,7 +16,6 @@
package android.net.http;
-import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -201,7 +200,7 @@ public final class Headers {
try {
contentLength = Long.parseLong(val);
} catch (NumberFormatException e) {
- if (Config.LOGV) {
+ if (false) {
Log.v(LOGTAG, "Headers.headers(): error parsing"
+ " content length: " + buffer.toString());
}
@@ -449,7 +448,7 @@ public final class Headers {
}
int extraLen = mExtraHeaderNames.size();
for (int i = 0; i < extraLen; i++) {
- if (Config.LOGV) {
+ if (false) {
HttpLog.v("Headers.getHeaders() extra: " + i + " " +
mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
}
diff --git a/core/java/android/net/http/HttpLog.java b/core/java/android/net/http/HttpLog.java
index 30bf647..0934664 100644
--- a/core/java/android/net/http/HttpLog.java
+++ b/core/java/android/net/http/HttpLog.java
@@ -23,7 +23,6 @@ package android.net.http;
import android.os.SystemClock;
import android.util.Log;
-import android.util.Config;
/**
* {@hide}
@@ -32,7 +31,7 @@ class HttpLog {
private final static String LOGTAG = "http";
private static final boolean DEBUG = false;
- static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean LOGV = false;
static void v(String logMe) {
Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index d77e9d9..84765a5 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -289,11 +289,9 @@ public class HttpsConnection extends Connection {
} else {
// if we do not have a proxy, we simply connect to the host
try {
- sslSock = (SSLSocket) getSocketFactory().createSocket();
-
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ mHost.getHostName(), mHost.getPort());
sslSock.setSoTimeout(SOCKET_TIMEOUT);
- sslSock.connect(new InetSocketAddress(mHost.getHostName(),
- mHost.getPort()));
} catch(IOException e) {
if (sslSock != null) {
sslSock.close();
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 1803604..64bba54 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -153,7 +153,6 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
-
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@@ -183,6 +182,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final InternalHandler sHandler = new InternalHandler();
+ private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
@@ -240,6 +240,11 @@ public abstract class AsyncTask<Params, Progress, Result> {
sHandler.getLooper();
}
+ /** @hide */
+ public static void setDefaultExecutor(Executor exec) {
+ sDefaultExecutor = exec;
+ }
+
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
@@ -496,7 +501,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- return executeOnExecutor(THREAD_POOL_EXECUTOR, params);
+ return executeOnExecutor(sDefaultExecutor, params);
}
/**
@@ -559,7 +564,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
* a simple Runnable object.
*/
public static void execute(Runnable runnable) {
- THREAD_POOL_EXECUTOR.execute(runnable);
+ sDefaultExecutor.execute(runnable);
}
/**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 90e2e79..b0f3ac3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -26,6 +26,7 @@ import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
import android.util.Log;
import android.util.Printer;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -408,15 +409,19 @@ public abstract class BatteryStats implements Parcelable {
}
public final static class HistoryItem implements Parcelable {
+ static final String TAG = "HistoryItem";
+ static final boolean DEBUG = false;
+
public HistoryItem next;
public long time;
- public static final byte CMD_UPDATE = 0;
- public static final byte CMD_START = 1;
- public static final byte CMD_OVERFLOW = 2;
+ public static final byte CMD_NULL = 0;
+ public static final byte CMD_UPDATE = 1;
+ public static final byte CMD_START = 2;
+ public static final byte CMD_OVERFLOW = 3;
- public byte cmd;
+ public byte cmd = CMD_NULL;
public byte batteryLevel;
public byte batteryStatus;
@@ -427,33 +432,38 @@ public abstract class BatteryStats implements Parcelable {
public char batteryVoltage;
// Constants from SCREEN_BRIGHTNESS_*
- public static final int STATE_BRIGHTNESS_MASK = 0x000000f;
+ public static final int STATE_BRIGHTNESS_MASK = 0x0000000f;
public static final int STATE_BRIGHTNESS_SHIFT = 0;
// Constants from SIGNAL_STRENGTH_*
- public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0;
+ public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0;
public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4;
// Constants from ServiceState.STATE_*
- public static final int STATE_PHONE_STATE_MASK = 0x0000f00;
+ public static final int STATE_PHONE_STATE_MASK = 0x00000f00;
public static final int STATE_PHONE_STATE_SHIFT = 8;
// Constants from DATA_CONNECTION_*
- public static final int STATE_DATA_CONNECTION_MASK = 0x000f000;
+ public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000;
public static final int STATE_DATA_CONNECTION_SHIFT = 12;
- public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30;
- public static final int STATE_SCREEN_ON_FLAG = 1<<29;
+ // These states always appear directly in the first int token
+ // of a delta change; they should be ones that change relatively
+ // frequently.
+ public static final int STATE_WAKE_LOCK_FLAG = 1<<30;
+ public static final int STATE_SENSOR_ON_FLAG = 1<<29;
public static final int STATE_GPS_ON_FLAG = 1<<28;
- public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27;
- public static final int STATE_PHONE_SCANNING_FLAG = 1<<26;
- public static final int STATE_WIFI_ON_FLAG = 1<<25;
- public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
- public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23;
- public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22;
- public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21;
- public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20;
- public static final int STATE_AUDIO_ON_FLAG = 1<<19;
- public static final int STATE_VIDEO_ON_FLAG = 1<<18;
- public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
- public static final int STATE_SENSOR_ON_FLAG = 1<<16;
+ public static final int STATE_PHONE_SCANNING_FLAG = 1<<27;
+ public static final int STATE_WIFI_RUNNING_FLAG = 1<<26;
+ public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25;
+ public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24;
+ public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23;
+ // These are on the lower bits used for the command; if they change
+ // we need to write another int of data.
+ public static final int STATE_AUDIO_ON_FLAG = 1<<22;
+ public static final int STATE_VIDEO_ON_FLAG = 1<<21;
+ public static final int STATE_SCREEN_ON_FLAG = 1<<20;
+ public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
+ public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+ public static final int STATE_WIFI_ON_FLAG = 1<<17;
+ public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
@@ -466,16 +476,7 @@ public abstract class BatteryStats implements Parcelable {
public HistoryItem(long time, Parcel src) {
this.time = time;
- int bat = src.readInt();
- cmd = (byte)(bat&0xff);
- batteryLevel = (byte)((bat>>8)&0xff);
- batteryStatus = (byte)((bat>>16)&0xf);
- batteryHealth = (byte)((bat>>20)&0xf);
- batteryPlugType = (byte)((bat>>24)&0xf);
- bat = src.readInt();
- batteryTemperature = (char)(bat&0xffff);
- batteryVoltage = (char)((bat>>16)&0xffff);
- states = src.readInt();
+ readFromParcel(src);
}
public int describeContents() {
@@ -495,6 +496,174 @@ public abstract class BatteryStats implements Parcelable {
dest.writeInt(bat);
dest.writeInt(states);
}
+
+ private void readFromParcel(Parcel src) {
+ int bat = src.readInt();
+ cmd = (byte)(bat&0xff);
+ batteryLevel = (byte)((bat>>8)&0xff);
+ batteryStatus = (byte)((bat>>16)&0xf);
+ batteryHealth = (byte)((bat>>20)&0xf);
+ batteryPlugType = (byte)((bat>>24)&0xf);
+ bat = src.readInt();
+ batteryTemperature = (char)(bat&0xffff);
+ batteryVoltage = (char)((bat>>16)&0xffff);
+ states = src.readInt();
+ }
+
+ // Part of initial delta int that specifies the time delta.
+ static final int DELTA_TIME_MASK = 0x3ffff;
+ static final int DELTA_TIME_ABS = 0x3fffd; // Following is an entire abs update.
+ static final int DELTA_TIME_INT = 0x3fffe; // The delta is a following int
+ static final int DELTA_TIME_LONG = 0x3ffff; // The delta is a following long
+ // Part of initial delta int holding the command code.
+ static final int DELTA_CMD_MASK = 0x3;
+ static final int DELTA_CMD_SHIFT = 18;
+ // Flag in delta int: a new battery level int follows.
+ static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20;
+ // Flag in delta int: a new full state and battery status int follows.
+ static final int DELTA_STATE_FLAG = 1<<21;
+ static final int DELTA_STATE_MASK = 0xffc00000;
+
+ public void writeDelta(Parcel dest, HistoryItem last) {
+ if (last == null || last.cmd != CMD_UPDATE) {
+ dest.writeInt(DELTA_TIME_ABS);
+ writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = time - last.time;
+ final int lastBatteryLevelInt = last.buildBatteryLevelInt();
+ final int lastStateInt = last.buildStateInt();
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = DELTA_TIME_LONG;
+ } else if (deltaTime >= DELTA_TIME_ABS) {
+ deltaTimeToken = DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int)deltaTime;
+ }
+ int firstToken = deltaTimeToken
+ | (cmd<<DELTA_CMD_SHIFT)
+ | (states&DELTA_STATE_MASK);
+ final int batteryLevelInt = buildBatteryLevelInt();
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt();
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= DELTA_STATE_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+
+ if (deltaTimeToken >= DELTA_TIME_INT) {
+ if (deltaTimeToken == DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+ dest.writeInt((int)deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + batteryLevel
+ + " batteryTemp=" + (int)batteryTemperature
+ + " batteryVolt=" + (int)batteryVoltage);
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + batteryStatus
+ + " batteryHealth=" + batteryHealth
+ + " batteryPlugType=" + batteryPlugType
+ + " states=0x" + Integer.toHexString(states));
+ }
+ }
+
+ private int buildBatteryLevelInt() {
+ return ((((int)batteryLevel)<<24)&0xff000000)
+ | ((((int)batteryTemperature)<<14)&0x00ffc000)
+ | (((int)batteryVoltage)&0x00003fff);
+ }
+
+ private int buildStateInt() {
+ return ((((int)batteryStatus)<<28)&0xf0000000)
+ | ((((int)batteryHealth)<<24)&0x0f000000)
+ | ((((int)batteryPlugType)<<22)&0x00c00000)
+ | (states&(~DELTA_STATE_MASK));
+ }
+
+ public void readDelta(Parcel src) {
+ int firstToken = src.readInt();
+ int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+ cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTimeToken=" + deltaTimeToken);
+
+ if (deltaTimeToken < DELTA_TIME_ABS) {
+ time += deltaTimeToken;
+ } else if (deltaTimeToken == DELTA_TIME_ABS) {
+ time = src.readLong();
+ readFromParcel(src);
+ return;
+ } else if (deltaTimeToken == DELTA_TIME_INT) {
+ int delta = src.readInt();
+ time += delta;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+ } else {
+ long delta = src.readLong();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+ time += delta;
+ }
+
+ if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
+ int batteryLevelInt = src.readInt();
+ batteryLevel = (byte)((batteryLevelInt>>24)&0xff);
+ batteryTemperature = (char)((batteryLevelInt>>14)&0x3ff);
+ batteryVoltage = (char)(batteryLevelInt&0x3fff);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + batteryLevel
+ + " batteryTemp=" + (int)batteryTemperature
+ + " batteryVolt=" + (int)batteryVoltage);
+ }
+
+ if ((firstToken&DELTA_STATE_FLAG) != 0) {
+ int stateInt = src.readInt();
+ states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
+ batteryStatus = (byte)((stateInt>>28)&0xf);
+ batteryHealth = (byte)((stateInt>>24)&0xf);
+ batteryPlugType = (byte)((stateInt>>22)&0x3);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + batteryStatus
+ + " batteryHealth=" + batteryHealth
+ + " batteryPlugType=" + batteryPlugType
+ + " states=0x" + Integer.toHexString(states));
+ } else {
+ states = (firstToken&DELTA_STATE_MASK) | (states&(~DELTA_STATE_MASK));
+ }
+ }
+
+ public void clear() {
+ time = 0;
+ cmd = CMD_NULL;
+ batteryLevel = 0;
+ batteryStatus = 0;
+ batteryHealth = 0;
+ batteryPlugType = 0;
+ batteryTemperature = 0;
+ batteryVoltage = 0;
+ states = 0;
+ }
public void setTo(HistoryItem o) {
time = o.time;
@@ -556,11 +725,14 @@ public abstract class BatteryStats implements Parcelable {
public abstract boolean getNextHistoryLocked(HistoryItem out);
- /**
- * Return the current history of battery state changes.
- */
- public abstract HistoryItem getHistory();
-
+ public abstract void finishIteratingHistoryLocked();
+
+ public abstract boolean startIteratingOldHistoryLocked();
+
+ public abstract boolean getNextOldHistoryLocked(HistoryItem out);
+
+ public abstract void finishIteratingOldHistoryLocked();
+
/**
* Return the base time offset for the battery history.
*/
@@ -1729,7 +1901,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
+ static void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
int diff = oldval ^ newval;
if (diff == 0) return;
for (int i=0; i<descriptions.length; i++) {
@@ -1753,6 +1925,125 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ public void prepareForDumpLocked() {
+ }
+
+ public static class HistoryPrinter {
+ int oldState = 0;
+ int oldStatus = -1;
+ int oldHealth = -1;
+ int oldPlug = -1;
+ int oldTemp = -1;
+ int oldVolt = -1;
+
+ public void printNextItem(PrintWriter pw, HistoryItem rec, long now) {
+ pw.print(" ");
+ TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ pw.print(" ");
+ if (rec.cmd == HistoryItem.CMD_START) {
+ pw.println(" START");
+ } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+ pw.println(" *OVERFLOW*");
+ } else {
+ if (rec.batteryLevel < 10) pw.print("00");
+ else if (rec.batteryLevel < 100) pw.print("0");
+ pw.print(rec.batteryLevel);
+ pw.print(" ");
+ if (rec.states < 0x10) pw.print("0000000");
+ else if (rec.states < 0x100) pw.print("000000");
+ else if (rec.states < 0x1000) pw.print("00000");
+ else if (rec.states < 0x10000) pw.print("0000");
+ else if (rec.states < 0x100000) pw.print("000");
+ else if (rec.states < 0x1000000) pw.print("00");
+ else if (rec.states < 0x10000000) pw.print("0");
+ pw.print(Integer.toHexString(rec.states));
+ if (oldStatus != rec.batteryStatus) {
+ oldStatus = rec.batteryStatus;
+ pw.print(" status=");
+ switch (oldStatus) {
+ case BatteryManager.BATTERY_STATUS_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ pw.print("charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ pw.print("discharging");
+ break;
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ pw.print("not-charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_FULL:
+ pw.print("full");
+ break;
+ default:
+ pw.print(oldStatus);
+ break;
+ }
+ }
+ if (oldHealth != rec.batteryHealth) {
+ oldHealth = rec.batteryHealth;
+ pw.print(" health=");
+ switch (oldHealth) {
+ case BatteryManager.BATTERY_HEALTH_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_HEALTH_GOOD:
+ pw.print("good");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+ pw.print("overheat");
+ break;
+ case BatteryManager.BATTERY_HEALTH_DEAD:
+ pw.print("dead");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+ pw.print("over-voltage");
+ break;
+ case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+ pw.print("failure");
+ break;
+ default:
+ pw.print(oldHealth);
+ break;
+ }
+ }
+ if (oldPlug != rec.batteryPlugType) {
+ oldPlug = rec.batteryPlugType;
+ pw.print(" plug=");
+ switch (oldPlug) {
+ case 0:
+ pw.print("none");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ pw.print("ac");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ pw.print("usb");
+ break;
+ default:
+ pw.print(oldPlug);
+ break;
+ }
+ }
+ if (oldTemp != rec.batteryTemperature) {
+ oldTemp = rec.batteryTemperature;
+ pw.print(" temp=");
+ pw.print(oldTemp);
+ }
+ if (oldVolt != rec.batteryVoltage) {
+ oldVolt = rec.batteryVoltage;
+ pw.print(" volt=");
+ pw.print(oldVolt);
+ }
+ printBitDescriptions(pw, oldState, rec.states,
+ HISTORY_STATE_DESCRIPTIONS);
+ pw.println();
+ }
+ oldState = rec.states;
+ }
+ }
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
@@ -1760,122 +2051,28 @@ public abstract class BatteryStats implements Parcelable {
*/
@SuppressWarnings("unused")
public void dumpLocked(PrintWriter pw) {
+ prepareForDumpLocked();
+
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
final HistoryItem rec = new HistoryItem();
if (startIteratingHistoryLocked()) {
pw.println("Battery History:");
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- int oldState = 0;
- int oldStatus = -1;
- int oldHealth = -1;
- int oldPlug = -1;
- int oldTemp = -1;
- int oldVolt = -1;
+ HistoryPrinter hprinter = new HistoryPrinter();
while (getNextHistoryLocked(rec)) {
- pw.print(" ");
- TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
- pw.print(" ");
- if (rec.cmd == HistoryItem.CMD_START) {
- pw.println(" START");
- } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
- pw.println(" *OVERFLOW*");
- } else {
- if (rec.batteryLevel < 10) pw.print("00");
- else if (rec.batteryLevel < 100) pw.print("0");
- pw.print(rec.batteryLevel);
- pw.print(" ");
- if (rec.states < 0x10) pw.print("0000000");
- else if (rec.states < 0x100) pw.print("000000");
- else if (rec.states < 0x1000) pw.print("00000");
- else if (rec.states < 0x10000) pw.print("0000");
- else if (rec.states < 0x100000) pw.print("000");
- else if (rec.states < 0x1000000) pw.print("00");
- else if (rec.states < 0x10000000) pw.print("0");
- pw.print(Integer.toHexString(rec.states));
- if (oldStatus != rec.batteryStatus) {
- oldStatus = rec.batteryStatus;
- pw.print(" status=");
- switch (oldStatus) {
- case BatteryManager.BATTERY_STATUS_UNKNOWN:
- pw.print("unknown");
- break;
- case BatteryManager.BATTERY_STATUS_CHARGING:
- pw.print("charging");
- break;
- case BatteryManager.BATTERY_STATUS_DISCHARGING:
- pw.print("discharging");
- break;
- case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
- pw.print("not-charging");
- break;
- case BatteryManager.BATTERY_STATUS_FULL:
- pw.print("full");
- break;
- default:
- pw.print(oldStatus);
- break;
- }
- }
- if (oldHealth != rec.batteryHealth) {
- oldHealth = rec.batteryHealth;
- pw.print(" health=");
- switch (oldHealth) {
- case BatteryManager.BATTERY_HEALTH_UNKNOWN:
- pw.print("unknown");
- break;
- case BatteryManager.BATTERY_HEALTH_GOOD:
- pw.print("good");
- break;
- case BatteryManager.BATTERY_HEALTH_OVERHEAT:
- pw.print("overheat");
- break;
- case BatteryManager.BATTERY_HEALTH_DEAD:
- pw.print("dead");
- break;
- case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
- pw.print("over-voltage");
- break;
- case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
- pw.print("failure");
- break;
- default:
- pw.print(oldHealth);
- break;
- }
- }
- if (oldPlug != rec.batteryPlugType) {
- oldPlug = rec.batteryPlugType;
- pw.print(" plug=");
- switch (oldPlug) {
- case 0:
- pw.print("none");
- break;
- case BatteryManager.BATTERY_PLUGGED_AC:
- pw.print("ac");
- break;
- case BatteryManager.BATTERY_PLUGGED_USB:
- pw.print("usb");
- break;
- default:
- pw.print(oldPlug);
- break;
- }
- }
- if (oldTemp != rec.batteryTemperature) {
- oldTemp = rec.batteryTemperature;
- pw.print(" temp=");
- pw.print(oldTemp);
- }
- if (oldVolt != rec.batteryVoltage) {
- oldVolt = rec.batteryVoltage;
- pw.print(" volt=");
- pw.print(oldVolt);
- }
- printBitDescriptions(pw, oldState, rec.states,
- HISTORY_STATE_DESCRIPTIONS);
- pw.println();
- }
- oldState = rec.states;
+ hprinter.printNextItem(pw, rec, now);
+ }
+ finishIteratingHistoryLocked();
+ pw.println("");
+ }
+
+ if (startIteratingOldHistoryLocked()) {
+ pw.println("Old battery History:");
+ HistoryPrinter hprinter = new HistoryPrinter();
+ while (getNextOldHistoryLocked(rec)) {
+ hprinter.printNextItem(pw, rec, now);
}
+ finishIteratingOldHistoryLocked();
pw.println("");
}
@@ -1918,6 +2115,8 @@ public abstract class BatteryStats implements Parcelable {
@SuppressWarnings("unused")
public void dumpCheckinLocked(PrintWriter pw, String[] args, List<ApplicationInfo> apps) {
+ prepareForDumpLocked();
+
boolean isUnpluggedOnly = false;
for (String arg : args) {
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 7dc36f9..c25ebb7 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -16,7 +16,6 @@
package android.os;
-import android.util.Config;
import android.util.Log;
import java.io.FileDescriptor;
@@ -256,6 +255,25 @@ public class Binder implements IBinder {
}
/**
+ * Like {@link #dump(FileDescriptor, String[])}, but ensures the target
+ * executes asynchronously.
+ */
+ public void dumpAsync(final FileDescriptor fd, final String[] args) {
+ final FileOutputStream fout = new FileOutputStream(fd);
+ final PrintWriter pw = new PrintWriter(fout);
+ Thread thr = new Thread("Binder.dumpAsync") {
+ public void run() {
+ try {
+ dump(fd, pw, args);
+ } finally {
+ pw.flush();
+ }
+ }
+ };
+ thr.start();
+ }
+
+ /**
* Print the object's state into the given stream.
*
* @param fd The raw file descriptor that the dump is being sent to.
@@ -272,7 +290,7 @@ public class Binder implements IBinder {
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
- if (Config.LOGV) Log.v("Binder", "Transact: " + code + " to " + this);
+ if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
@@ -364,6 +382,20 @@ final class BinderProxy implements IBinder {
}
}
+ public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeFileDescriptor(fd);
+ data.writeStringArray(args);
+ try {
+ transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
+ reply.readException();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
BinderProxy() {
mSelf = new WeakReference(this);
}
@@ -380,7 +412,7 @@ final class BinderProxy implements IBinder {
private native final void destroy();
private static final void sendDeathNotice(DeathRecipient recipient) {
- if (Config.LOGV) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+ if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
recipient.binderDied();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 3bb0821..24d5369 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -230,6 +230,11 @@ public class Build {
* Newest version of Android, version 3.1.
*/
public static final int HONEYCOMB_MR1 = 12;
+
+ /**
+ * Current version under development.
+ */
+ public static final int ICE_CREAM_SANDWICH = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 87aeccb..ba69246 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -18,7 +18,6 @@ package android.os;
import com.android.internal.util.TypedProperties;
-import android.util.Config;
import android.util.Log;
import java.io.FileDescriptor;
@@ -1031,7 +1030,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* Load the debug properties from the standard files into debugProperties.
*/
static {
- if (Config.DEBUG) {
+ if (false) {
final String TAG = "DebugProperties";
final String[] files = { "/system/debug.prop", "/debug.prop", "/data/debug.prop" };
final TypedProperties tp = new TypedProperties();
@@ -1157,10 +1156,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Reflectively sets static fields of a class based on internal debugging
- * properties. This method is a no-op if android.util.Config.DEBUG is
+ * properties. This method is a no-op if false is
* false.
* <p>
- * <strong>NOTE TO APPLICATION DEVELOPERS</strong>: Config.DEBUG will
+ * <strong>NOTE TO APPLICATION DEVELOPERS</strong>: false will
* always be false in release builds. This API is typically only useful
* for platform developers.
* </p>
@@ -1211,7 +1210,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* the internal debugging property value.
*/
public static void setFieldsOn(Class<?> cl, boolean partial) {
- if (Config.DEBUG) {
+ if (false) {
if (debugProperties != null) {
/* Only look for fields declared directly by the class,
* so we don't mysteriously change static fields in superclasses.
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 8ae8008..8876354 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -157,6 +157,16 @@ public interface IBinder {
public void dump(FileDescriptor fd, String[] args) throws RemoteException;
/**
+ * Like {@link #dump(FileDescriptor, String[])} but always executes
+ * asynchronously. If the object is local, a new thread is created
+ * to perform the dump.
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param args additional arguments to the dump request.
+ */
+ public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
+
+ /**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index ccf642c..3edd692 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -16,7 +16,6 @@
package android.os;
-import android.util.Config;
import android.util.Log;
import android.util.Printer;
import android.util.PrefixPrinter;
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index f82702a..e8148f7 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -28,7 +28,7 @@ import java.io.OutputStream;
* MemoryFile is a wrapper for the Linux ashmem driver.
* MemoryFiles are backed by shared memory, which can be optionally
* set to be purgeable.
- * Purgeable files may have their contents reclaimed by the kernel
+ * Purgeable files may have their contents reclaimed by the kernel
* in low memory conditions (only if allowPurging is set to true).
* After a file is purged, attempts to read or write the file will
* cause an IOException to be thrown.
@@ -126,7 +126,7 @@ public class MemoryFile
close();
}
}
-
+
/**
* Returns the length of the memory file.
*
@@ -190,7 +190,7 @@ public class MemoryFile
* @return number of bytes read.
* @throws IOException if the memory file has been purged or deactivated.
*/
- public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
+ public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
if (isDeactivated()) {
throw new IOException("Can't read from deactivated memory file.");
@@ -330,6 +330,7 @@ public class MemoryFile
@Override
public void write(byte buffer[], int offset, int count) throws IOException {
writeBytes(buffer, offset, mOffset, count);
+ mOffset += count;
}
@Override
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index bb07825..a658fc4 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -17,7 +17,6 @@
package android.os;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -128,7 +127,7 @@ public class MessageQueue {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
- if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
+ if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
} else {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index eca3484..6b35215 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1383,6 +1383,8 @@ public final class Parcel {
private native FileDescriptor internalReadFileDescriptor();
/*package*/ static native FileDescriptor openFileDescriptor(String file,
int mode) throws FileNotFoundException;
+ /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
+ throws IOException;
/*package*/ static native void closeFileDescriptor(FileDescriptor desc)
throws IOException;
/*package*/ static native void clearFileDescriptor(FileDescriptor desc);
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 5bd129f..f7661b6 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,64 +35,64 @@ public class ParcelFileDescriptor implements Parcelable {
//consider ParcelFileDescriptor A(fileDescriptor fd), ParcelFileDescriptor B(A)
//in this particular case fd.close might be invoked twice.
private final ParcelFileDescriptor mParcelDescriptor;
-
+
/**
* For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
* and this file doesn't already exist, then create the file with
* permissions such that any application can read it.
*/
public static final int MODE_WORLD_READABLE = 0x00000001;
-
+
/**
* For use with {@link #open}: if {@link #MODE_CREATE} has been supplied
* and this file doesn't already exist, then create the file with
* permissions such that any application can write it.
*/
public static final int MODE_WORLD_WRITEABLE = 0x00000002;
-
+
/**
* For use with {@link #open}: open the file with read-only access.
*/
public static final int MODE_READ_ONLY = 0x10000000;
-
+
/**
* For use with {@link #open}: open the file with write-only access.
*/
public static final int MODE_WRITE_ONLY = 0x20000000;
-
+
/**
* For use with {@link #open}: open the file with read and write access.
*/
public static final int MODE_READ_WRITE = 0x30000000;
-
+
/**
* For use with {@link #open}: create the file if it doesn't already exist.
*/
public static final int MODE_CREATE = 0x08000000;
-
+
/**
* For use with {@link #open}: erase contents of file when opening.
*/
public static final int MODE_TRUNCATE = 0x04000000;
-
+
/**
* For use with {@link #open}: append to end of file while writing.
*/
public static final int MODE_APPEND = 0x02000000;
-
+
/**
* Create a new ParcelFileDescriptor accessing a given file.
- *
+ *
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
* {@link #MODE_READ_ONLY}, {@link #MODE_WRITE_ONLY}, or
* {@link #MODE_READ_WRITE}; may also be any combination of
* {@link #MODE_CREATE}, {@link #MODE_TRUNCATE},
* {@link #MODE_WORLD_READABLE}, and {@link #MODE_WORLD_WRITEABLE}.
- *
+ *
* @return Returns a new ParcelFileDescriptor pointing to the given
* file.
- *
+ *
* @throws FileNotFoundException Throws FileNotFoundException if the given
* file does not exist or can not be opened with the requested mode.
*/
@@ -106,17 +106,28 @@ public class ParcelFileDescriptor implements Parcelable {
security.checkWrite(path);
}
}
-
+
if ((mode&MODE_READ_WRITE) == 0) {
throw new IllegalArgumentException(
"Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
}
-
+
FileDescriptor fd = Parcel.openFileDescriptor(path, mode);
return fd != null ? new ParcelFileDescriptor(fd) : null;
}
/**
+ * Create a new ParcelFileDescriptor that is a dup of an existing
+ * FileDescriptor. This obeys standard POSIX semantics, where the
+ * new file descriptor shared state such as file position with the
+ * original file descriptor.
+ */
+ public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException {
+ FileDescriptor fd = Parcel.dupFileDescriptor(orig);
+ return fd != null ? new ParcelFileDescriptor(fd) : null;
+ }
+
+ /**
* Create a new ParcelFileDescriptor from the specified Socket.
*
* @param socket The Socket whose FileDescriptor is used to create
@@ -126,13 +137,10 @@ public class ParcelFileDescriptor implements Parcelable {
* specified Socket.
*/
public static ParcelFileDescriptor fromSocket(Socket socket) {
- FileDescriptor fd = getFileDescriptorFromSocket(socket);
+ FileDescriptor fd = socket.getFileDescriptor$();
return fd != null ? new ParcelFileDescriptor(fd) : null;
}
- // Extracts the file descriptor from the specified socket and returns it untouched
- private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
-
/**
* Create two ParcelFileDescriptors structured as a data pipe. The first
* ParcelFileDescriptor in the returned array is the read side; the second
@@ -176,26 +184,26 @@ public class ParcelFileDescriptor implements Parcelable {
/**
* Retrieve the actual FileDescriptor associated with this object.
- *
+ *
* @return Returns the FileDescriptor associated with this object.
*/
public FileDescriptor getFileDescriptor() {
return mFileDescriptor;
}
-
+
/**
* Return the total size of the file representing this fd, as determined
* by stat(). Returns -1 if the fd is not a file.
*/
public native long getStatSize();
-
+
/**
* This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
* and I really don't think we want it to be public.
* @hide
*/
public native long seekTo(long pos);
-
+
/**
* Return the native fd int for this ParcelFileDescriptor. The
* ParcelFileDescriptor still owns the fd, and it still must be closed
@@ -207,9 +215,9 @@ public class ParcelFileDescriptor implements Parcelable {
}
return getFdNative();
}
-
+
private native int getFdNative();
-
+
/**
* Return the native fd int for this ParcelFileDescriptor and detach it
* from the object here. You are now responsible for closing the fd in
@@ -229,11 +237,11 @@ public class ParcelFileDescriptor implements Parcelable {
Parcel.clearFileDescriptor(mFileDescriptor);
return fd;
}
-
+
/**
* Close the ParcelFileDescriptor. This implementation closes the underlying
* OS resources allocated to represent this stream.
- *
+ *
* @throws IOException
* If an error occurs attempting to close this ParcelFileDescriptor.
*/
@@ -250,7 +258,7 @@ public class ParcelFileDescriptor implements Parcelable {
Parcel.closeFileDescriptor(mFileDescriptor);
}
}
-
+
/**
* An InputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
@@ -258,7 +266,7 @@ public class ParcelFileDescriptor implements Parcelable {
*/
public static class AutoCloseInputStream extends FileInputStream {
private final ParcelFileDescriptor mFd;
-
+
public AutoCloseInputStream(ParcelFileDescriptor fd) {
super(fd.getFileDescriptor());
mFd = fd;
@@ -273,7 +281,7 @@ public class ParcelFileDescriptor implements Parcelable {
}
}
}
-
+
/**
* An OutputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
@@ -281,7 +289,7 @@ public class ParcelFileDescriptor implements Parcelable {
*/
public static class AutoCloseOutputStream extends FileOutputStream {
private final ParcelFileDescriptor mFd;
-
+
public AutoCloseOutputStream(ParcelFileDescriptor fd) {
super(fd.getFileDescriptor());
mFd = fd;
@@ -296,12 +304,12 @@ public class ParcelFileDescriptor implements Parcelable {
}
}
}
-
+
@Override
public String toString() {
return "{ParcelFileDescriptor: " + mFileDescriptor + "}";
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -312,13 +320,13 @@ public class ParcelFileDescriptor implements Parcelable {
super.finalize();
}
}
-
+
public ParcelFileDescriptor(ParcelFileDescriptor descriptor) {
super();
mParcelDescriptor = descriptor;
mFileDescriptor = mParcelDescriptor.mFileDescriptor;
}
-
+
/*package */ParcelFileDescriptor(FileDescriptor descriptor) {
super();
if (descriptor == null) {
@@ -327,7 +335,7 @@ public class ParcelFileDescriptor implements Parcelable {
mFileDescriptor = descriptor;
mParcelDescriptor = null;
}
-
+
/* Parcelable interface */
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index efb8415..c830f7a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -97,7 +97,8 @@ import android.util.Log;
* </tbody>
* </table>
*
- *
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
*/
public class PowerManager
{
@@ -130,14 +131,25 @@ public class PowerManager
/**
* Wake lock that ensures that the screen and keyboard are on at
* full brightness.
+ *
+ * <p class="note">Most applications should strongly consider using
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+ * This window flag will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.</p>
*/
public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT
| WAKE_BIT_KEYBOARD_BRIGHT;
/**
+ * @deprecated Most applications should use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+ * of this type of wake lock, as it will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.
+ *
* Wake lock that ensures that the screen is on at full brightness;
* the keyboard backlight will be allowed to go off.
*/
+ @Deprecated
public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;
/**
@@ -188,8 +200,11 @@ public class PowerManager
/**
* Class lets you say that you need to have the device on.
- *
- * <p>Call release when you are done and don't need the lock anymore.
+ * <p>
+ * Call release when you are done and don't need the lock anymore.
+ * <p>
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
*/
public class WakeLock
{
@@ -380,6 +395,11 @@ public class PowerManager
*wl.release();
* }
*
+ * <p class="note">If using this to keep the screen on, you should strongly consider using
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
+ * This window flag will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.</p>
+ *
* @param flags Combination of flag values defining the requested behavior of the WakeLock.
* @param tag Your class name (or other tag) for debugging purposes.
*
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2bfada0..f85df6c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -92,6 +92,12 @@ public class Process {
public static final int SDCARD_RW_GID = 1015;
/**
+ * Defines the UID for the KeyChain service.
+ * @hide
+ */
+ public static final int KEYCHAIN_UID = 1020;
+
+ /**
* Defines the UID/GID for the NFC service process.
* @hide
*/
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index c1dd911..ae605fb 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -70,7 +70,7 @@ public class RecoverySystem {
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
- private static String LAST_LOG_FILENAME = "last_log";
+ private static String LAST_PREFIX = "last_";
// Length limits for reading files.
private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
@@ -415,10 +415,11 @@ public class RecoverySystem {
Log.e(TAG, "Error reading recovery log", e);
}
- // Delete everything in RECOVERY_DIR except LAST_LOG_FILENAME
+ // Delete everything in RECOVERY_DIR except those beginning
+ // with LAST_PREFIX
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
- if (names[i].equals(LAST_LOG_FILENAME)) continue;
+ if (names[i].startsWith(LAST_PREFIX)) continue;
File f = new File(RECOVERY_DIR, names[i]);
if (!f.delete()) {
Log.e(TAG, "Can't delete: " + f);
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 4c83515..27da3c3 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -637,6 +637,22 @@ public interface IMountService extends IInterface {
}
return _result;
}
+
+ public String[] getVolumeList() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readStringArray();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -699,6 +715,8 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_changeEncryptionPassword = IBinder.FIRST_CALL_TRANSACTION + 28;
+ static final int TRANSACTION_getVolumeList = IBinder.FIRST_CALL_TRANSACTION + 29;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1004,6 +1022,13 @@ public interface IMountService extends IInterface {
reply.writeInt(result);
return true;
}
+ case TRANSACTION_getVolumeList: {
+ data.enforceInterface(DESCRIPTOR);
+ String[] result = getVolumeList();
+ reply.writeNoException();
+ reply.writeStringArray(result);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1179,4 +1204,8 @@ public interface IMountService extends IInterface {
*/
public int changeEncryptionPassword(String password) throws RemoteException;
+ /**
+ * Returns list of all mountable volumes.
+ */
+ public String[] getVolumeList() throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 73ac79f..234057b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -527,4 +527,30 @@ public class StorageManager
return null;
}
+
+ /**
+ * Gets the state of a volume via its mountpoint.
+ * @hide
+ */
+ public String getVolumeState(String mountPoint) {
+ try {
+ return mMountService.getVolumeState(mountPoint);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get volume state", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns list of all mountable volumes.
+ * @hide
+ */
+ public String[] getVolumeList() {
+ try {
+ return mMountService.getVolumeList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get volume list", e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java
index 9c4eaf4..58c5c63 100644
--- a/core/java/android/pim/ICalendar.java
+++ b/core/java/android/pim/ICalendar.java
@@ -17,7 +17,6 @@
package android.pim;
import android.util.Log;
-import android.util.Config;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -447,7 +446,7 @@ public class ICalendar {
component = current;
}
} catch (FormatException fe) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Cannot parse " + line, fe);
}
// for now, we ignore the parse error. Google Calendar seems
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index 282417d..fdd0783 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -21,7 +21,6 @@ import android.database.Cursor;
import android.provider.Calendar;
import android.text.TextUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.Log;
import java.util.List;
@@ -197,7 +196,7 @@ public class RecurrenceSet {
(TextUtils.isEmpty(duration))||
((TextUtils.isEmpty(rrule))&&
(TextUtils.isEmpty(rdate)))) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
+ "or RRULE/RDATE: "
+ component.toString());
@@ -211,7 +210,7 @@ public class RecurrenceSet {
long millis = start.toMillis(false /* use isDst */);
values.put(Calendar.Events.DTSTART, millis);
if (millis == -1) {
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "DTSTART is out of range: " + component.toString());
}
return false;
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
index 42d555c..2e8d551 100644
--- a/core/java/android/preference/MultiSelectListPreference.java
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -169,9 +169,9 @@ public class MultiSelectListPreference extends DialogPreference {
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
- mPreferenceChanged |= mNewValues.add(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.add(mEntryValues[which].toString());
} else {
- mPreferenceChanged |= mNewValues.remove(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.remove(mEntryValues[which].toString());
}
}
});
@@ -180,7 +180,7 @@ public class MultiSelectListPreference extends DialogPreference {
}
private boolean[] getSelectedItems() {
- final CharSequence[] entries = mEntries;
+ final CharSequence[] entries = mEntryValues;
final int entryCount = entries.length;
final Set<String> values = mValues;
boolean[] result = new boolean[entryCount];
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 7d37e5b..5e1be21 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -89,6 +89,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
private int mOrder = DEFAULT_ORDER;
private CharSequence mTitle;
+ private int mTitleRes;
private CharSequence mSummary;
/**
* mIconResId is overridden by mIcon, if mIcon is specified.
@@ -214,6 +215,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
break;
case com.android.internal.R.styleable.Preference_title:
+ mTitleRes = a.getResourceId(attr, 0);
mTitle = a.getString(attr);
break;
@@ -582,6 +584,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
*/
public void setTitle(CharSequence title) {
if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
+ mTitleRes = 0;
mTitle = title;
notifyChanged();
}
@@ -595,9 +598,21 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
*/
public void setTitle(int titleResId) {
setTitle(mContext.getString(titleResId));
+ mTitleRes = titleResId;
}
/**
+ * Returns the title resource ID of this Preference. If the title did
+ * not come from a resource, 0 is returned.
+ *
+ * @return The title resource.
+ * @see #setTitle(int)
+ */
+ public int getTitleRes() {
+ return mTitleRes;
+ }
+
+ /**
* Returns the title of this Preference.
*
* @return The title.
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index ad0bc84..15d5898 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -132,13 +132,28 @@ public abstract class PreferenceActivity extends ListActivity implements
/**
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
- * this extra can also be specify to supply a Bundle of arguments to pass
+ * this extra can also be specified to supply a Bundle of arguments to pass
* to that fragment when it is instantiated during the initial creation
* of PreferenceActivity.
*/
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
/**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * this extra can also be specify to supply the title to be shown for
+ * that fragment.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
+
+ /**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * this extra can also be specify to supply the short title to be shown for
+ * that fragment.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
+ = ":android:show_fragment_short_title";
+
+ /**
* When starting this activity, the invoking Intent can contain this extra
* boolean that the header list should not be displayed. This is most often
* used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
@@ -488,7 +503,12 @@ public abstract class PreferenceActivity extends ListActivity implements
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(com.android.internal.R.layout.preference_list_content);
+ if (getResources().getConfiguration().isLayoutSizeAtLeast(
+ Configuration.SCREENLAYOUT_SIZE_LARGE)) {
+ setContentView(com.android.internal.R.layout.preference_list_content_large);
+ } else {
+ setContentView(com.android.internal.R.layout.preference_list_content);
+ }
mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
@@ -496,6 +516,8 @@ public abstract class PreferenceActivity extends ListActivity implements
mSinglePane = hidingHeaders || !onIsMultiPane();
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
+ int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
if (savedInstanceState != null) {
// We are restarting from a previous saved state; used that to
@@ -516,6 +538,12 @@ public abstract class PreferenceActivity extends ListActivity implements
// new fragment mode, but don't need to compute and show
// the headers.
switchToHeader(initialFragment, initialArguments);
+ if (initialTitle != 0) {
+ CharSequence initialTitleStr = getText(initialTitle);
+ CharSequence initialShortTitleStr = initialShortTitle != 0
+ ? getText(initialShortTitle) : null;
+ showBreadCrumbs(initialTitleStr, initialShortTitleStr);
+ }
} else {
// We need to try to build the headers.
@@ -557,7 +585,12 @@ public abstract class PreferenceActivity extends ListActivity implements
} else {
// If there are no headers, we are in the old "just show a screen
// of preferences" mode.
- setContentView(com.android.internal.R.layout.preference_list_content_single);
+ if (getResources().getConfiguration().isLayoutSizeAtLeast(
+ Configuration.SCREENLAYOUT_SIZE_LARGE)) {
+ setContentView(com.android.internal.R.layout.preference_list_content_single_large);
+ } else {
+ setContentView(com.android.internal.R.layout.preference_list_content_single);
+ }
mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
@@ -942,7 +975,8 @@ public abstract class PreferenceActivity extends ListActivity implements
/**
* Called when the user selects an item in the header list. The default
- * implementation will call either {@link #startWithFragment(String, Bundle, Fragment, int)}
+ * implementation will call either
+ * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
* or {@link #switchToHeader(Header)} as appropriate.
*
* @param header The header that was selected.
@@ -951,7 +985,14 @@ public abstract class PreferenceActivity extends ListActivity implements
public void onHeaderClick(Header header, int position) {
if (header.fragment != null) {
if (mSinglePane) {
- startWithFragment(header.fragment, header.fragmentArguments, null, 0);
+ int titleRes = header.breadCrumbTitleRes;
+ int shortTitleRes = header.breadCrumbShortTitleRes;
+ if (titleRes == 0) {
+ titleRes = header.titleRes;
+ shortTitleRes = 0;
+ }
+ startWithFragment(header.fragment, header.fragmentArguments, null, 0,
+ titleRes, shortTitleRes);
} else {
switchToHeader(header);
}
@@ -961,6 +1002,41 @@ public abstract class PreferenceActivity extends ListActivity implements
}
/**
+ * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
+ * in single-pane mode, to build an Intent to launch a new activity showing
+ * the selected fragment. The default implementation constructs an Intent
+ * that re-launches the current activity with the appropriate arguments to
+ * display the fragment.
+ *
+ * @param fragmentName The name of the fragment to display.
+ * @param args Optional arguments to supply to the fragment.
+ * @param titleRes Optional resource ID of title to show for this item.
+ * @param titleRes Optional resource ID of short title to show for this item.
+ * @return Returns an Intent that can be launched to display the given
+ * fragment.
+ */
+ public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
+ int titleRes, int shortTitleRes) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(this, getClass());
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
+ intent.putExtra(EXTRA_NO_HEADERS, true);
+ return intent;
+ }
+
+ /**
+ * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
+ * but uses a 0 titleRes.
+ */
+ public void startWithFragment(String fragmentName, Bundle args,
+ Fragment resultTo, int resultRequestCode) {
+ startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
+ }
+
+ /**
* Start a new instance of this activity, showing only the given
* preference fragment. When launched in this mode, the header list
* will be hidden and the given preference fragment will be instantiated
@@ -968,14 +1044,18 @@ public abstract class PreferenceActivity extends ListActivity implements
*
* @param fragmentName The name of the fragment to display.
* @param args Optional arguments to supply to the fragment.
+ * @param resultTo Option fragment that should receive the result of
+ * the activity launch.
+ * @param resultRequestCode If resultTo is non-null, this is the request
+ * code in which to report the result.
+ * @param titleRes Resource ID of string to display for the title of
+ * this set of preferences.
+ * @param titleRes Resource ID of string to display for the short title of
+ * this set of preferences.
*/
public void startWithFragment(String fragmentName, Bundle args,
- Fragment resultTo, int resultRequestCode) {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(this, getClass());
- intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
- intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
- intent.putExtra(EXTRA_NO_HEADERS, true);
+ Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
+ Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
startActivity(intent);
} else {
@@ -992,16 +1072,16 @@ public abstract class PreferenceActivity extends ListActivity implements
if (mFragmentBreadCrumbs == null) {
View crumbs = findViewById(android.R.id.title);
// For screens with a different kind of title, don't create breadcrumbs.
- if (!(crumbs instanceof FragmentBreadCrumbs)) return;
- mFragmentBreadCrumbs = (FragmentBreadCrumbs) findViewById(android.R.id.title);
+ try {
+ mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
+ } catch (ClassCastException e) {
+ return;
+ }
if (mFragmentBreadCrumbs == null) {
- mFragmentBreadCrumbs = new FragmentBreadCrumbs(this);
- ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setCustomView(mFragmentBreadCrumbs);
+ if (title != null) {
+ setTitle(title);
}
+ return;
}
mFragmentBreadCrumbs.setMaxVisible(2);
mFragmentBreadCrumbs.setActivity(this);
@@ -1169,7 +1249,7 @@ public abstract class PreferenceActivity extends ListActivity implements
public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
CharSequence titleText, Fragment resultTo, int resultRequestCode) {
if (mSinglePane) {
- startWithFragment(fragmentClass, args, resultTo, resultRequestCode);
+ startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
} else {
Fragment f = Fragment.instantiate(this, fragmentClass, args);
if (resultTo != null) {
@@ -1215,7 +1295,8 @@ public abstract class PreferenceActivity extends ListActivity implements
@Override
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
- startPreferencePanel(pref.getFragment(), pref.getExtras(), 0, pref.getTitle(), null, 0);
+ startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
+ pref.getTitle(), null, 0);
return true;
}
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 4e22ba0..7511e14 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -151,8 +152,14 @@ public abstract class PreferenceFragment extends Fragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
- container, false);
+ if (getResources().getConfiguration().isLayoutSizeAtLeast(
+ Configuration.SCREENLAYOUT_SIZE_LARGE)) {
+ return inflater.inflate(com.android.internal.R.layout.preference_list_fragment_large,
+ container, false);
+ } else {
+ return inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
+ container, false);
+ }
}
@Override
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index de71763..309a5ec 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -98,6 +98,8 @@ public final class Calendar {
public static final String SYNC4 = "sync4";
/** Generic column for use by sync adapters. */
public static final String SYNC5 = "sync5";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC6 = "sync6";
}
/**
@@ -135,15 +137,6 @@ public final class Calendar {
public static final String _SYNC_VERSION = "_sync_version";
/**
- * For use by sync adapter at its discretion; not modified by CalendarProvider
- * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
- * schema change.
- * TODO Replace this with something more general in the future.
- * <P>Type: INTEGER (long)</P>
- */
- public static final String _SYNC_DATA = "_sync_local_id";
-
- /**
* Used only in persistent providers, and only during merging.
* <P>Type: INTEGER (long)</P>
*/
@@ -212,7 +205,7 @@ public final class Calendar {
* Is the calendar selected to be displayed?
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String SELECTED = "selected";
+ public static final String VISIBLE = "visible";
/**
* The timezone the calendar's events occurs in
@@ -288,29 +281,32 @@ public final class Calendar {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_TIME);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
- DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_MARK);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC2);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC3);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC4);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC5);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC6);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Calendars.DISPLAY_NAME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, Calendars.COLOR);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELECTED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.LOCATION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TIMEZONE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Calendars.OWNER_ACCOUNT);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
- Calendars.ORGANIZER_CAN_RESPOND);
+ Calendars.CAN_ORGANIZER_RESPOND);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.CAN_MODIFY_TIME_ZONE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.MAX_REMINDERS);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
@@ -432,7 +428,19 @@ public final class Calendar {
* organizer should not be shown by the UI. Defaults to 1
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
+ public static final String CAN_ORGANIZER_RESPOND = "canOrganizerRespond";
+
+ /**
+ * Can the organizer modify the time zone of the event?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CAN_MODIFY_TIME_ZONE = "canModifyTimeZone";
+
+ /**
+ * The maximum number of reminders allowed for an event.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MAX_REMINDERS = "maxReminders";
}
/**
@@ -505,6 +513,15 @@ public final class Calendar {
*/
public interface EventsColumns {
/**
+ * For use by sync adapter at its discretion; not modified by CalendarProvider
+ * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
+ * schema change.
+ * TODO Replace this with something more general in the future.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DATA = "_sync_local_id";
+
+ /**
* The calendar the event belongs to
* <P>Type: INTEGER (foreign key to the Calendars table)</P>
*/
@@ -1011,7 +1028,7 @@ public final class Calendar {
*/
public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
- private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=1";
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end) {
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4f88612..22b4c76 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -144,6 +144,27 @@ public final class ContactsContract {
public static final String LIMIT_PARAM_KEY = "limit";
/**
+ * A query parameter specifing a primary account. This parameter should be used with
+ * {@link #PRIMARY_ACCOUNT_TYPE}. The contacts provider handling a query may rely on
+ * this information to optimize its query results.
+ *
+ * For example, in an email composition screen, its implementation can specify an account when
+ * obtaining possible recipients, letting the provider know which account is selected during
+ * the composition. The provider may use the "primary account" information to optimize
+ * the search result.
+ * @hide
+ */
+ public static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
+
+ /**
+ * A query parameter specifing a primary account. This parameter should be used with
+ * {@link #PRIMARY_ACCOUNT_NAME}. See the doc in {@link #PRIMARY_ACCOUNT_NAME}.
+ * @hide
+ */
+ public static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
+
+
+ /**
* @hide
*/
public static final class Preferences {
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index b59421e..02fd6e4 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -166,7 +166,6 @@ public final class MediaStore {
* If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
* value of EXTRA_OUTPUT.
* @see #EXTRA_OUTPUT
- * @see #EXTRA_VIDEO_QUALITY
*/
public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
@@ -181,6 +180,9 @@ public final class MediaStore {
* written to the standard location for videos, and the Uri of that location will be
* returned in the data field of the Uri.
* @see #EXTRA_OUTPUT
+ * @see #EXTRA_VIDEO_QUALITY
+ * @see #EXTRA_SIZE_LIMIT
+ * @see #EXTRA_DURATION_LIMIT
*/
public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
@@ -344,6 +346,13 @@ public final class MediaStore {
*/
public interface FileColumns extends MediaColumns {
/**
+ * The MTP storage ID of the file
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String STORAGE_ID = "storage_id";
+
+ /**
* The MTP format code of the file
* <P>Type: INTEGER</P>
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b09b44e..570b801 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -41,7 +41,6 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.AndroidException;
-import android.util.Config;
import android.util.Log;
import java.net.URISyntaxException;
@@ -569,7 +568,7 @@ public final class Settings {
public static final String AUTHORITY = "settings";
private static final String TAG = "Settings";
- private static final boolean LOCAL_LOGV = Config.LOGV || false;
+ private static final boolean LOCAL_LOGV = false || false;
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
@@ -1103,6 +1102,18 @@ public final class Settings {
public static final int END_BUTTON_BEHAVIOR_DEFAULT = END_BUTTON_BEHAVIOR_SLEEP;
/**
+ * Is advanced settings mode turned on. 0 == no, 1 == yes
+ * @hide
+ */
+ public static final String ADVANCED_SETTINGS = "advanced_settings";
+
+ /**
+ * ADVANCED_SETTINGS default value.
+ * @hide
+ */
+ public static final int ADVANCED_SETTINGS_DEFAULT = 0;
+
+ /**
* Whether Airplane Mode is on.
*/
public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
@@ -1494,6 +1505,13 @@ public final class Settings {
public static final Uri DEFAULT_ALARM_ALERT_URI = getUriFor(ALARM_ALERT);
/**
+ * Persistent store for the system default media button event receiver.
+ *
+ * @hide
+ */
+ public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+
+ /**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
@@ -3303,12 +3321,20 @@ public final class Settings {
public static final String WIFI_IDLE_MS = "wifi_idle_ms";
/**
- * The interval in milliseconds to issue scans when the driver is
- * started. This is necessary to allow wifi to connect to an
- * access point when the driver is suspended.
+ * The interval in milliseconds to issue wake up scans when wifi needs
+ * to connect. This is necessary to connect to an access point when
+ * device is on the move and the screen is off.
+ * @hide
+ */
+ public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS =
+ "wifi_framework_scan_interval_ms";
+
+ /**
+ * The interval in milliseconds to scan as used by the wifi supplicant
* @hide
*/
- public static final String WIFI_SCAN_INTERVAL_MS = "wifi_scan_interval_ms";
+ public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS =
+ "wifi_supplicant_scan_interval_ms";
/**
* The interval in milliseconds at which to check packet counts on the
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d2d2557..91a72a5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -28,7 +28,6 @@ import android.net.Uri;
import android.os.Environment;
import android.telephony.SmsMessage;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.util.Patterns;
@@ -46,7 +45,7 @@ import java.util.regex.Pattern;
public final class Telephony {
private static final String TAG = "Telephony";
private static final boolean DEBUG = true;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
// Constructor
public Telephony() {
@@ -90,12 +89,18 @@ public final class Telephony {
public static final String PERSON_ID = "person";
/**
- * The date the message was sent
+ * The date the message was received
* <P>Type: INTEGER (long)</P>
*/
public static final String DATE = "date";
/**
+ * The date the message was sent
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
* Has the message been read
* <P>Type: INTEGER (boolean)</P>
*/
@@ -650,12 +655,18 @@ public final class Telephony {
public static final int MESSAGE_BOX_OUTBOX = 4;
/**
- * The date the message was sent.
+ * The date the message was received.
* <P>Type: INTEGER (long)</P>
*/
public static final String DATE = "date";
/**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
* The box which the message belong to, for example, MESSAGE_BOX_INBOX.
* <P>Type: INTEGER</P>
*/
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 132c346..ca2212c 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -83,19 +83,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
onBluetoothDisable();
break;
}
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
- BluetoothDevice.ERROR);
- switch(bondState) {
- case BluetoothDevice.BOND_BONDED:
- if (getPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
- setPriority(device, BluetoothA2dp.PRIORITY_ON);
- }
- break;
- case BluetoothDevice.BOND_NONE:
- setPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
- break;
- }
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
synchronized (this) {
if (mAudioDevices.containsKey(device)) {
@@ -158,7 +145,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
mAdapter = BluetoothAdapter.getDefaultAdapter();
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
diff --git a/core/java/android/server/BluetoothAdapterProperties.java b/core/java/android/server/BluetoothAdapterProperties.java
index ae8104b..9723f60 100644
--- a/core/java/android/server/BluetoothAdapterProperties.java
+++ b/core/java/android/server/BluetoothAdapterProperties.java
@@ -76,14 +76,13 @@ class BluetoothAdapterProperties {
for (int i = 0; i < properties.length; i++) {
String name = properties[i];
String newValue = null;
- int len;
if (name == null) {
Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
continue;
}
if (name.equals("Devices") || name.equals("UUIDs")) {
StringBuilder str = new StringBuilder();
- len = Integer.valueOf(properties[++i]);
+ int len = Integer.valueOf(properties[++i]);
for (int j = 0; j < len; j++) {
str.append(properties[++i]);
str.append(",");
diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java
index 2304a70..a36cd24 100644
--- a/core/java/android/server/BluetoothBondState.java
+++ b/core/java/android/server/BluetoothBondState.java
@@ -18,6 +18,9 @@ package android.server;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothHeadset;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
@@ -68,6 +71,8 @@ class BluetoothBondState {
private final Context mContext;
private final BluetoothService mService;
private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
+ private BluetoothA2dp mA2dpProxy;
+ private BluetoothHeadset mHeadsetProxy;
BluetoothBondState(Context context, BluetoothService service) {
mContext = context;
@@ -126,14 +131,15 @@ class BluetoothBondState {
if (state == BluetoothDevice.BOND_BONDED) {
mService.addProfileState(address);
+ } else if (state == BluetoothDevice.BOND_BONDING) {
+ if (mA2dpProxy == null || mHeadsetProxy == null) {
+ getProfileProxy();
+ }
} else if (state == BluetoothDevice.BOND_NONE) {
mService.removeProfileState(address);
}
- // HID is handled by BluetoothService, other profiles
- // will be handled by their respective services.
- mBluetoothInputProfileHandler.setInitialInputDevicePriority(
- mService.getRemoteDevice(address), state);
+ setProfilePriorities(address, state);
if (DBG) {
Log.d(TAG, address + " bond state " + oldState + " -> " + state
@@ -261,6 +267,52 @@ class BluetoothBondState {
mPinAttempt.put(address, new Integer(newAttempt));
}
+ private void getProfileProxy() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (mA2dpProxy == null) {
+ bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
+ BluetoothProfile.A2DP);
+ }
+
+ if (mHeadsetProxy == null) {
+ bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
+ }
+
+ private void closeProfileProxy() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (mA2dpProxy != null) {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy);
+ }
+
+ if (mHeadsetProxy != null) {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy);
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dpProxy = (BluetoothA2dp) proxy;
+ } else if (profile == BluetoothProfile.HEADSET) {
+ mHeadsetProxy = (BluetoothHeadset) proxy;
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (profile == BluetoothProfile.A2DP) {
+ mA2dpProxy = null;
+ } else if (profile == BluetoothProfile.HEADSET) {
+ mHeadsetProxy = null;
+ }
+ }
+ };
+
private void copyAutoPairingData() {
FileInputStream in = null;
FileOutputStream out = null;
@@ -365,4 +417,30 @@ class BluetoothBondState {
}
}
}
+
+ // Set service priority of Hid, A2DP and Headset profiles depending on
+ // the bond state change
+ private void setProfilePriorities(String address, int state) {
+ BluetoothDevice remoteDevice = mService.getRemoteDevice(address);
+ // HID is handled by BluetoothService
+ mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state);
+
+ // Set service priority of A2DP and Headset
+ // We used to do the priority change in the 2 services after the broadcast
+ // intent reach them. But that left a small time gap that could reject
+ // incoming connection due to undefined priorities.
+ if (state == BluetoothDevice.BOND_BONDED) {
+ if (mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
+ mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
+ }
+
+ if (mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
+ mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
+ }
+ } else if (state == BluetoothDevice.BOND_NONE) {
+ mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
+ mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
+ }
+ }
+
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index 5eb71d7..bdb3ba9 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -70,7 +70,8 @@ public interface RecognitionListener {
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
- * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+ * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter. A float array of
+ * confidence values might also be given in {@link SpeechRecognizer#CONFIDENCE_SCORES}.
*/
void onResults(Bundle results);
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 02c324c..fd709f2 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -46,7 +46,7 @@ public class RecognizerIntent {
}
/**
- * Starts an activity that will prompt the user for speech and sends it through a
+ * Starts an activity that will prompt the user for speech and send it through a
* speech recognizer. The results will be returned via activity results (in
* {@link Activity#onActivityResult}, if you start the intent using
* {@link Activity#startActivityForResult(Intent, int)}), or forwarded via a PendingIntent
@@ -81,8 +81,8 @@ public class RecognizerIntent {
public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
/**
- * Starts an activity that will prompt the user for speech, sends it through a
- * speech recognizer, and invokes and either displays a web search result or triggers
+ * Starts an activity that will prompt the user for speech, send it through a
+ * speech recognizer, and either display a web search result or trigger
* another type of action based on the user's speech.
*
* <p>If you want to avoid triggering any type of action besides web search, you can use
@@ -100,11 +100,13 @@ public class RecognizerIntent {
* <li>{@link #EXTRA_MAX_RESULTS}
* <li>{@link #EXTRA_PARTIAL_RESULTS}
* <li>{@link #EXTRA_WEB_SEARCH_ONLY}
+ * <li>{@link #EXTRA_ORIGIN}
* </ul>
*
* <p> Result extras (returned in the result, not to be specified in the request):
* <ul>
* <li>{@link #EXTRA_RESULTS}
+ * <li>{@link #EXTRA_CONFIDENCE_SCORES} (optional)
* </ul>
*
* <p>NOTE: There may not be any applications installed to handle this action, so you should
@@ -181,6 +183,13 @@ public class RecognizerIntent {
* {@link java.util.Locale#getDefault()}.
*/
public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
+
+ /**
+ * Optional value which can be used to indicate the referer url of a page in which
+ * speech was requested. For example, a web browser may choose to provide this for
+ * uses of speech on a given page.
+ */
+ public static final String EXTRA_ORIGIN = "android.speech.extra.ORIGIN";
/**
* Optional limit on the maximum number of results to return. If omitted the recognizer
@@ -232,13 +241,31 @@ public class RecognizerIntent {
/**
* An ArrayList&lt;String&gt; of the recognition results when performing
- * {@link #ACTION_RECOGNIZE_SPEECH}. Returned in the results; not to be specified in the
- * recognition request. Only present when {@link Activity#RESULT_OK} is returned in
- * an activity result. In a PendingIntent, the lack of this extra indicates failure.
+ * {@link #ACTION_RECOGNIZE_SPEECH}. Generally this list should be ordered in
+ * descending order of speech recognizer confidence. (See {@link #EXTRA_CONFIDENCE_SCORES}).
+ * Returned in the results; not to be specified in the recognition request. Only present
+ * when {@link Activity#RESULT_OK} is returned in an activity result. In a PendingIntent,
+ * the lack of this extra indicates failure.
*/
public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
/**
+ * A float array of confidence scores of the recognition results when performing
+ * {@link #ACTION_RECOGNIZE_SPEECH}. The array should be the same size as the ArrayList
+ * returned in {@link #EXTRA_RESULTS}, and should contain values ranging from 0.0 to 1.0,
+ * or -1 to represent an unavailable confidence score.
+ * <p>
+ * Confidence values close to 1.0 indicate high confidence (the speech recognizer is
+ * confident that the recognition result is correct), while values close to 0.0 indicate
+ * low confidence.
+ * <p>
+ * Returned in the results; not to be specified in the recognition request. This extra is
+ * optional and might not be provided. Only present when {@link Activity#RESULT_OK} is
+ * returned in an activity result.
+ */
+ public static final String EXTRA_CONFIDENCE_SCORES = "android.speech.extra.CONFIDENCE_SCORES";
+
+ /**
* Returns the broadcast intent to fire with
* {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}
* to receive details from the package that implements voice search.
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index cd73ba8..8fee41d 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -50,12 +50,26 @@ public class SpeechRecognizer {
private static final String TAG = "SpeechRecognizer";
/**
- * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
+ * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
* {@link RecognitionListener#onResults(Bundle)} and
* {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
* recognition results, where the first element is the most likely candidate.
*/
public static final String RESULTS_RECOGNITION = "results_recognition";
+
+ /**
+ * Key used to retrieve a float array from the {@link Bundle} passed to the
+ * {@link RecognitionListener#onResults(Bundle)} and
+ * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be
+ * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain
+ * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score.
+ * <p>
+ * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident
+ * that the recognition result is correct), while values close to 0.0 indicate low confidence.
+ * <p>
+ * This value is optional and might not be provided.
+ */
+ public static final String CONFIDENCE_SCORES = "confidence_scores";
/** Network operation timed out. */
public static final int ERROR_NETWORK_TIMEOUT = 1;
diff --git a/core/java/android/speech/srec/Recognizer.java b/core/java/android/speech/srec/Recognizer.java
index a03a36a..8a2bc7d 100644
--- a/core/java/android/speech/srec/Recognizer.java
+++ b/core/java/android/speech/srec/Recognizer.java
@@ -22,7 +22,6 @@
package android.speech.srec;
-import android.util.Config;
import android.util.Log;
import java.io.File;
diff --git a/core/java/android/speech/tts/BlockingMediaPlayer.java b/core/java/android/speech/tts/BlockingMediaPlayer.java
new file mode 100644
index 0000000..3cf60dd
--- /dev/null
+++ b/core/java/android/speech/tts/BlockingMediaPlayer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * A media player that allows blocking to wait for it to finish.
+ */
+class BlockingMediaPlayer {
+
+ private static final String TAG = "BlockMediaPlayer";
+
+ private static final String MEDIA_PLAYER_THREAD_NAME = "TTS-MediaPlayer";
+
+ private final Context mContext;
+ private final Uri mUri;
+ private final int mStreamType;
+ private final ConditionVariable mDone;
+ // Only accessed on the Handler thread
+ private MediaPlayer mPlayer;
+ private volatile boolean mFinished;
+
+ /**
+ * Creates a new blocking media player.
+ * Creating a blocking media player is a cheap operation.
+ *
+ * @param context
+ * @param uri
+ * @param streamType
+ */
+ public BlockingMediaPlayer(Context context, Uri uri, int streamType) {
+ mContext = context;
+ mUri = uri;
+ mStreamType = streamType;
+ mDone = new ConditionVariable();
+
+ }
+
+ /**
+ * Starts playback and waits for it to finish.
+ * Can be called from any thread.
+ *
+ * @return {@code true} if the playback finished normally, {@code false} if the playback
+ * failed or {@link #stop} was called before the playback finished.
+ */
+ public boolean startAndWait() {
+ HandlerThread thread = new HandlerThread(MEDIA_PLAYER_THREAD_NAME);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ mFinished = false;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ startPlaying();
+ }
+ });
+ mDone.block();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ // No new messages should get posted to the handler thread after this
+ Looper.myLooper().quit();
+ }
+ });
+ return mFinished;
+ }
+
+ /**
+ * Stops playback. Can be called multiple times.
+ * Can be called from any thread.
+ */
+ public void stop() {
+ mDone.open();
+ }
+
+ /**
+ * Starts playback.
+ * Called on the handler thread.
+ */
+ private void startPlaying() {
+ mPlayer = MediaPlayer.create(mContext, mUri);
+ if (mPlayer == null) {
+ Log.w(TAG, "Failed to play " + mUri);
+ mDone.open();
+ return;
+ }
+ try {
+ mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ Log.w(TAG, "Audio playback error: " + what + ", " + extra);
+ mDone.open();
+ return true;
+ }
+ });
+ mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ mFinished = true;
+ mDone.open();
+ }
+ });
+ mPlayer.setAudioStreamType(mStreamType);
+ mPlayer.start();
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "MediaPlayer failed", ex);
+ mDone.open();
+ }
+ }
+
+ /**
+ * Stops playback and release the media player.
+ * Called on the handler thread.
+ */
+ private void finish() {
+ try {
+ mPlayer.stop();
+ } catch (IllegalStateException ex) {
+ // Do nothing, the player is already stopped
+ }
+ mPlayer.release();
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java
new file mode 100644
index 0000000..7efc264
--- /dev/null
+++ b/core/java/android/speech/tts/FileSynthesisRequest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.media.AudioFormat;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Speech synthesis request that writes the audio to a WAV file.
+ */
+class FileSynthesisRequest extends SynthesisRequest {
+
+ private static final String TAG = "FileSynthesisRequest";
+ private static final boolean DBG = false;
+
+ private static final int MAX_AUDIO_BUFFER_SIZE = 8192;
+
+ private static final int WAV_HEADER_LENGTH = 44;
+ private static final short WAV_FORMAT_PCM = 0x0001;
+
+ private final Object mStateLock = new Object();
+ private final File mFileName;
+ private int mSampleRateInHz;
+ private int mAudioFormat;
+ private int mChannelCount;
+ private RandomAccessFile mFile;
+ private boolean mStopped = false;
+ private boolean mDone = false;
+
+ FileSynthesisRequest(String text, File fileName) {
+ super(text);
+ mFileName = fileName;
+ }
+
+ @Override
+ void stop() {
+ synchronized (mStateLock) {
+ mStopped = true;
+ cleanUp();
+ }
+ }
+
+ /**
+ * Must be called while holding the monitor on {@link #mStateLock}.
+ */
+ private void cleanUp() {
+ closeFile();
+ if (mFile != null) {
+ mFileName.delete();
+ }
+ }
+
+ /**
+ * Must be called while holding the monitor on {@link #mStateLock}.
+ */
+ private void closeFile() {
+ try {
+ if (mFile != null) {
+ mFile.close();
+ mFile = null;
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+ }
+ }
+
+ @Override
+ public int getMaxBufferSize() {
+ return MAX_AUDIO_BUFFER_SIZE;
+ }
+
+ @Override
+ boolean isDone() {
+ return mDone;
+ }
+
+ @Override
+ public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+ if (DBG) {
+ Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
+ + "," + channelCount + ")");
+ }
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile != null) {
+ cleanUp();
+ throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
+ }
+ mSampleRateInHz = sampleRateInHz;
+ mAudioFormat = audioFormat;
+ mChannelCount = channelCount;
+ try {
+ mFile = new RandomAccessFile(mFileName, "rw");
+ // Reserve space for WAV header
+ mFile.write(new byte[WAV_HEADER_LENGTH]);
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public int audioAvailable(byte[] buffer, int offset, int length) {
+ if (DBG) {
+ Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset
+ + "," + length + ")");
+ }
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile == null) {
+ Log.e(TAG, "File not open");
+ return TextToSpeech.ERROR;
+ }
+ try {
+ mFile.write(buffer, offset, length);
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public int done() {
+ if (DBG) Log.d(TAG, "FileSynthesisRequest.done()");
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mFile == null) {
+ Log.e(TAG, "File not open");
+ return TextToSpeech.ERROR;
+ }
+ try {
+ // Write WAV header at start of file
+ mFile.seek(0);
+ int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
+ mFile.write(
+ makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
+ closeFile();
+ mDone = true;
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ }
+ }
+
+ @Override
+ public void error() {
+ if (DBG) Log.d(TAG, "FileSynthesisRequest.error()");
+ synchronized (mStateLock) {
+ cleanUp();
+ }
+ }
+
+ @Override
+ public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+ byte[] buffer, int offset, int length) {
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ }
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(mFileName);
+ out.write(makeWavHeader(sampleRateInHz, audioFormat, channelCount, length));
+ out.write(buffer, offset, length);
+ mDone = true;
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ mFileName.delete();
+ return TextToSpeech.ERROR;
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
+ }
+ }
+ }
+
+ private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
+ int dataLength) {
+ // TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT?
+ int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+ int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount;
+ short blockAlign = (short) (sampleSizeInBytes * channelCount);
+ short bitsPerSample = (short) (sampleSizeInBytes * 8);
+
+ byte[] headerBuf = new byte[WAV_HEADER_LENGTH];
+ ByteBuffer header = ByteBuffer.wrap(headerBuf);
+ header.order(ByteOrder.LITTLE_ENDIAN);
+
+ header.put(new byte[]{ 'R', 'I', 'F', 'F' });
+ header.putInt(dataLength + WAV_HEADER_LENGTH - 8); // RIFF chunk size
+ header.put(new byte[]{ 'W', 'A', 'V', 'E' });
+ header.put(new byte[]{ 'f', 'm', 't', ' ' });
+ header.putInt(16); // size of fmt chunk
+ header.putShort(WAV_FORMAT_PCM);
+ header.putShort((short) channelCount);
+ header.putInt(sampleRateInHz);
+ header.putInt(byteRate);
+ header.putShort(blockAlign);
+ header.putShort(bitsPerSample);
+ header.put(new byte[]{ 'd', 'a', 't', 'a' });
+ header.putInt(dataLength);
+
+ return headerBuf;
+ }
+
+}
diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index c9898eb..40902ae 100755
--- a/core/java/android/speech/tts/ITtsCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.speech.tts;
/**
- * AIDL for the callback from the TTS Service
- * ITtsCallback.java is autogenerated from this.
+ * Interface for callbacks from TextToSpeechService
*
* {@hide}
*/
-oneway interface ITtsCallback {
+oneway interface ITextToSpeechCallback {
void utteranceCompleted(String utteranceId);
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
new file mode 100644
index 0000000..ff3fa11
--- /dev/null
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech.tts;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.speech.tts.ITextToSpeechCallback;
+
+/**
+ * Interface for TextToSpeech to talk to TextToSpeechService.
+ *
+ * {@hide}
+ */
+interface ITextToSpeechService {
+
+ /**
+ * Tells the engine to synthesize some speech and play it back.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param text The text to synthesize.
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int speak(in String callingApp, in String text, in int queueMode, in Bundle params);
+
+ /**
+ * Tells the engine to synthesize some speech and write it to a file.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param text The text to synthesize.
+ * @param filename The file to write the synthesized audio to.
+ * @param param Request parameters.
+ */
+ int synthesizeToFile(in String callingApp, in String text,
+ in String filename, in Bundle params);
+
+ /**
+ * Plays an existing audio resource.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param audioUri URI for the audio resource (a file or android.resource URI)
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int playAudio(in String callingApp, in Uri audioUri, in int queueMode, in Bundle params);
+
+ /**
+ * Plays silence.
+ *
+ * @param callingApp The package name of the calling app. Used to connect requests
+ * callbacks and to clear requests when the calling app is stopping.
+ * @param duration Number of milliseconds of silence to play.
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param param Request parameters.
+ */
+ int playSilence(in String callingApp, in long duration, in int queueMode, in Bundle params);
+
+ /**
+ * Checks whether the service is currently playing some audio.
+ */
+ boolean isSpeaking();
+
+ /**
+ * Interrupts the current utterance (if from the given app) and removes any utterances
+ * in the queue that are from the given app.
+ *
+ * @param callingApp Package name of the app whose utterances
+ * should be interrupted and cleared.
+ */
+ int stop(in String callingApp);
+
+ /**
+ * Returns the language, country and variant currently being used by the TTS engine.
+ *
+ * Can be called from multiple threads.
+ *
+ * @return A 3-element array, containing language (ISO 3-letter code),
+ * country (ISO 3-letter code) and variant used by the engine.
+ * The country and variant may be {@code ""}. If country is empty, then variant must
+ * be empty too.
+ */
+ String[] getLanguage();
+
+ /**
+ * Checks whether the engine supports a given language.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ int isLanguageAvailable(in String lang, in String country, in String variant);
+
+ /**
+ * Notifies the engine that it should load a speech synthesis language.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ int loadLanguage(in String lang, in String country, in String variant);
+
+ /**
+ * Sets the callback that will be notified when playback of utterance from the
+ * given app are completed.
+ *
+ * @param callingApp Package name for the app whose utterance the callback will handle.
+ * @param cb The callback.
+ */
+ void setCallback(in String callingApp, ITextToSpeechCallback cb);
+
+}
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
deleted file mode 100755
index c1051c4..0000000
--- a/core/java/android/speech/tts/ITts.aidl
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.speech.tts;
-
-import android.speech.tts.ITtsCallback;
-
-import android.content.Intent;
-
-/**
- * AIDL for the TTS Service
- * ITts.java is autogenerated from this.
- *
- * {@hide}
- */
-interface ITts {
- int setSpeechRate(in String callingApp, in int speechRate);
-
- int setPitch(in String callingApp, in int pitch);
-
- int speak(in String callingApp, in String text, in int queueMode, in String[] params);
-
- boolean isSpeaking();
-
- int stop(in String callingApp);
-
- void addSpeech(in String callingApp, in String text, in String packageName, in int resId);
-
- void addSpeechFile(in String callingApp, in String text, in String filename);
-
- String[] getLanguage();
-
- int isLanguageAvailable(in String language, in String country, in String variant, in String[] params);
-
- int setLanguage(in String callingApp, in String language, in String country, in String variant);
-
- boolean synthesizeToFile(in String callingApp, in String text, in String[] params, in String outputDirectory);
-
- int playEarcon(in String callingApp, in String earcon, in int queueMode, in String[] params);
-
- void addEarcon(in String callingApp, in String earcon, in String packageName, in int resId);
-
- void addEarconFile(in String callingApp, in String earcon, in String filename);
-
- int registerCallback(in String callingApp, ITtsCallback cb);
-
- int unregisterCallback(in String callingApp, ITtsCallback cb);
-
- int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
-
- int setEngineByPackageName(in String enginePackageName);
-
- String getDefaultEngine();
-
- boolean areDefaultsEnforced();
-}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
new file mode 100644
index 0000000..dc5ff70
--- /dev/null
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+/**
+ * Speech synthesis request that plays the audio as it is received.
+ */
+class PlaybackSynthesisRequest extends SynthesisRequest {
+
+ private static final String TAG = "PlaybackSynthesisRequest";
+ private static final boolean DBG = false;
+
+ private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
+
+ /**
+ * Audio stream type. Must be one of the STREAM_ contants defined in
+ * {@link android.media.AudioManager}.
+ */
+ private final int mStreamType;
+
+ /**
+ * Volume, in the range [0.0f, 1.0f]. The default value is
+ * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
+ */
+ private final float mVolume;
+
+ /**
+ * Left/right position of the audio, in the range [-1.0f, 1.0f].
+ * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
+ */
+ private final float mPan;
+
+ private final Object mStateLock = new Object();
+ private AudioTrack mAudioTrack = null;
+ private boolean mStopped = false;
+ private boolean mDone = false;
+
+ PlaybackSynthesisRequest(String text, int streamType, float volume, float pan) {
+ super(text);
+ mStreamType = streamType;
+ mVolume = volume;
+ mPan = pan;
+ }
+
+ @Override
+ void stop() {
+ if (DBG) Log.d(TAG, "stop()");
+ synchronized (mStateLock) {
+ mStopped = true;
+ cleanUp();
+ }
+ }
+
+ private void cleanUp() {
+ if (DBG) Log.d(TAG, "cleanUp()");
+ if (mAudioTrack != null) {
+ mAudioTrack.flush();
+ mAudioTrack.stop();
+ mAudioTrack.release();
+ mAudioTrack = null;
+ }
+ }
+
+ @Override
+ public int getMaxBufferSize() {
+ // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
+ // a safe buffer size to pass in.
+ return MIN_AUDIO_BUFFER_SIZE;
+ }
+
+ @Override
+ boolean isDone() {
+ return mDone;
+ }
+
+ // TODO: add a thread that writes to the AudioTrack?
+ @Override
+ public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+ if (DBG) {
+ Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat
+ + "," + channelCount + ")");
+ }
+
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mAudioTrack != null) {
+ Log.e(TAG, "start() called twice");
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+
+ mAudioTrack = createStreamingAudioTrack(sampleRateInHz, audioFormat, channelCount);
+ if (mAudioTrack == null) {
+ return TextToSpeech.ERROR;
+ }
+ }
+
+ return TextToSpeech.SUCCESS;
+ }
+
+ private void setupVolume(AudioTrack audioTrack, float volume, float pan) {
+ float vol = clip(volume, 0.0f, 1.0f);
+ float panning = clip(pan, -1.0f, 1.0f);
+ float volLeft = vol;
+ float volRight = vol;
+ if (panning > 0.0f) {
+ volLeft *= (1.0f - panning);
+ } else if (panning < 0.0f) {
+ volRight *= (1.0f + panning);
+ }
+ if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight);
+ if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) {
+ Log.e(TAG, "Failed to set volume");
+ }
+ }
+
+ private float clip(float value, float min, float max) {
+ return value > max ? max : (value < min ? min : value);
+ }
+
+ @Override
+ public int audioAvailable(byte[] buffer, int offset, int length) {
+ if (DBG) {
+ Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
+ + offset + "," + length + ")");
+ }
+ if (length > getMaxBufferSize()) {
+ throw new IllegalArgumentException("buffer is too large (" + length + " bytes)");
+ }
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mAudioTrack == null) {
+ Log.e(TAG, "audioAvailable(): Not started");
+ return TextToSpeech.ERROR;
+ }
+ int playState = mAudioTrack.getPlayState();
+ if (playState == AudioTrack.PLAYSTATE_STOPPED) {
+ if (DBG) Log.d(TAG, "AudioTrack stopped, restarting");
+ mAudioTrack.play();
+ }
+ // TODO: loop until all data is written?
+ if (DBG) Log.d(TAG, "AudioTrack.write()");
+ int count = mAudioTrack.write(buffer, offset, length);
+ if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count);
+ if (count < 0) {
+ Log.e(TAG, "Writing to AudioTrack failed: " + count);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ } else {
+ return TextToSpeech.SUCCESS;
+ }
+ }
+ }
+
+ @Override
+ public int done() {
+ if (DBG) Log.d(TAG, "done()");
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mAudioTrack == null) {
+ Log.e(TAG, "done(): Not started");
+ return TextToSpeech.ERROR;
+ }
+ mDone = true;
+ cleanUp();
+ }
+ return TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ public void error() {
+ if (DBG) Log.d(TAG, "error()");
+ synchronized (mStateLock) {
+ cleanUp();
+ }
+ }
+
+ @Override
+ public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
+ byte[] buffer, int offset, int length) {
+ if (DBG) {
+ Log.d(TAG, "completeAudioAvailable(" + sampleRateInHz + "," + audioFormat
+ + "," + channelCount + "byte[" + buffer.length + "],"
+ + offset + "," + length + ")");
+ }
+
+ synchronized (mStateLock) {
+ if (mStopped) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return TextToSpeech.ERROR;
+ }
+ if (mAudioTrack != null) {
+ Log.e(TAG, "start() called before completeAudioAvailable()");
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+
+ int channelConfig = getChannelConfig(channelCount);
+ if (channelConfig < 0) {
+ Log.e(TAG, "Unsupported number of channels :" + channelCount);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ int bytesPerFrame = getBytesPerFrame(audioFormat);
+ if (bytesPerFrame < 0) {
+ Log.e(TAG, "Unsupported audio format :" + audioFormat);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+
+ mAudioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
+ audioFormat, buffer.length, AudioTrack.MODE_STATIC);
+ if (mAudioTrack == null) {
+ return TextToSpeech.ERROR;
+ }
+
+ try {
+ mAudioTrack.write(buffer, offset, length);
+ setupVolume(mAudioTrack, mVolume, mPan);
+ mAudioTrack.play();
+ blockUntilDone(mAudioTrack, bytesPerFrame, length);
+ mDone = true;
+ if (DBG) Log.d(TAG, "Wrote data to audio track succesfully : " + length);
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "Playback error", ex);
+ return TextToSpeech.ERROR;
+ } finally {
+ cleanUp();
+ }
+ }
+
+ return TextToSpeech.SUCCESS;
+ }
+
+ private void blockUntilDone(AudioTrack audioTrack, int bytesPerFrame, int length) {
+ int lengthInFrames = length / bytesPerFrame;
+ int currentPosition = 0;
+ while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) {
+ long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) /
+ audioTrack.getSampleRate();
+ if (DBG) Log.d(TAG, "About to sleep for : " + estimatedTimeMs + " ms," +
+ " Playback position : " + currentPosition);
+ try {
+ Thread.sleep(estimatedTimeMs);
+ } catch (InterruptedException ie) {
+ break;
+ }
+ }
+ }
+
+ private int getBytesPerFrame(int audioFormat) {
+ if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
+ return 1;
+ } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+ return 2;
+ }
+
+ return -1;
+ }
+
+ private int getChannelConfig(int channelCount) {
+ if (channelCount == 1) {
+ return AudioFormat.CHANNEL_OUT_MONO;
+ } else if (channelCount == 2){
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+
+ return -1;
+ }
+
+ private AudioTrack createStreamingAudioTrack(int sampleRateInHz, int audioFormat,
+ int channelCount) {
+ int channelConfig = getChannelConfig(channelCount);
+
+ if (channelConfig < 0) {
+ Log.e(TAG, "Unsupported number of channels : " + channelCount);
+ return null;
+ }
+
+ int minBufferSizeInBytes
+ = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
+ int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
+ AudioTrack audioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
+ audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
+ if (audioTrack == null) {
+ return null;
+ }
+
+ if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
+ audioTrack.release();
+ return null;
+ }
+ setupVolume(audioTrack, mVolume, mPan);
+ return audioTrack;
+ }
+}
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
new file mode 100644
index 0000000..515218b
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+/**
+ * A request for speech synthesis given to a TTS engine for processing.
+ *
+ * The engine can provide streaming audio by calling
+ * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally
+ * {@link #done}.
+ *
+ * Alternatively, the engine can provide all the audio at once, by using
+ * {@link #completeAudioAvailable}.
+ *
+ * @hide Pending approval
+ */
+public abstract class SynthesisRequest {
+
+ private final String mText;
+ private String mLanguage;
+ private String mCountry;
+ private String mVariant;
+ private int mSpeechRate;
+ private int mPitch;
+
+ public SynthesisRequest(String text) {
+ mText = text;
+ }
+
+ /**
+ * Sets the locale for the request.
+ */
+ void setLanguage(String language, String country, String variant) {
+ mLanguage = language;
+ mCountry = country;
+ mVariant = variant;
+ }
+
+ /**
+ * Sets the speech rate.
+ */
+ void setSpeechRate(int speechRate) {
+ mSpeechRate = speechRate;
+ }
+
+ /**
+ * Sets the pitch.
+ */
+ void setPitch(int pitch) {
+ mPitch = pitch;
+ }
+
+ /**
+ * Gets the text which should be synthesized.
+ */
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Gets the ISO 3-letter language code for the language to use.
+ */
+ public String getLanguage() {
+ return mLanguage;
+ }
+
+ /**
+ * Gets the ISO 3-letter country code for the language to use.
+ */
+ public String getCountry() {
+ return mCountry;
+ }
+
+ /**
+ * Gets the language variant to use.
+ */
+ public String getVariant() {
+ return mVariant;
+ }
+
+ /**
+ * Gets the speech rate to use. {@link TextToSpeech.Engine#DEFAULT_RATE} (100)
+ * is the normal rate.
+ */
+ public int getSpeechRate() {
+ return mSpeechRate;
+ }
+
+ /**
+ * Gets the pitch to use. {@link TextToSpeech.Engine#DEFAULT_PITCH} (100)
+ * is the normal pitch.
+ */
+ public int getPitch() {
+ return mPitch;
+ }
+
+ /**
+ * Gets the maximum number of bytes that the TTS engine can pass in a single call of
+ * {@link #audioAvailable}. This does not apply to {@link #completeAudioAvailable}.
+ */
+ public abstract int getMaxBufferSize();
+
+ /**
+ * Checks whether the synthesis request completed successfully.
+ */
+ abstract boolean isDone();
+
+ /**
+ * Aborts the speech request.
+ *
+ * Can be called from multiple threads.
+ */
+ abstract void stop();
+
+ /**
+ * The service should call this when it starts to synthesize audio for this
+ * request.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @param sampleRateInHz Sample rate in HZ of the generated audio.
+ * @param audioFormat Audio format of the generated audio. Must be one of
+ * the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+ * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public abstract int start(int sampleRateInHz, int audioFormat, int channelCount);
+
+ /**
+ * The service should call this method when synthesized audio is ready for consumption.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+ * so the caller is free to modify it after this method returns.
+ * @param offset The offset into {@code buffer} where the audio data starts.
+ * @param length The number of bytes of audio data in {@code buffer}. This must be
+ * less than or equal to the return value of {@link #getMaxBufferSize}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public abstract int audioAvailable(byte[] buffer, int offset, int length);
+
+ /**
+ * The service should call this method when all the synthesized audio for a request has
+ * been passed to {@link #audioAvailable}.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ *
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public abstract int done();
+
+ /**
+ * The service should call this method if the speech synthesis fails.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ */
+ public abstract void error();
+
+ /**
+ * The service can call this method instead of using {@link #start}, {@link #audioAvailable}
+ * and {@link #done} if all the audio data is available in a single buffer.
+ *
+ * @param sampleRateInHz Sample rate in HZ of the generated audio.
+ * @param audioFormat Audio format of the generated audio. Must be one of
+ * the ENCODING_ constants defined in {@link android.media.AudioFormat}.
+ * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
+ * @param buffer The generated audio data. This method will not hold on to {@code buffer},
+ * so the caller is free to modify it after this method returns.
+ * @param offset The offset into {@code buffer} where the audio data starts.
+ * @param length The number of bytes of audio data in {@code buffer}.
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public abstract int completeAudioAvailable(int sampleRateInHz, int audioFormat,
+ int channelCount, byte[] buffer, int offset, int length);
+} \ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 186af70..e247df8 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Google Inc.
+ * Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -15,22 +15,31 @@
*/
package android.speech.tts;
-import android.speech.tts.ITts;
-import android.speech.tts.ITtsCallback;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
*
@@ -44,14 +53,16 @@ import java.util.Locale;
*/
public class TextToSpeech {
+ private static final String TAG = "TextToSpeech";
+
/**
* Denotes a successful operation.
*/
- public static final int SUCCESS = 0;
+ public static final int SUCCESS = 0;
/**
* Denotes a generic operation failure.
*/
- public static final int ERROR = -1;
+ public static final int ERROR = -1;
/**
* Queue mode where all entries in the playback queue (media to be played
@@ -63,20 +74,17 @@ public class TextToSpeech {
*/
public static final int QUEUE_ADD = 1;
-
/**
* Denotes the language is available exactly as specified by the locale.
*/
public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
-
/**
* Denotes the language is available for the language and country specified
* by the locale, but not the variant.
*/
public static final int LANG_COUNTRY_AVAILABLE = 1;
-
/**
* Denotes the language is available for the language by the locale,
* but not the country and variant.
@@ -93,7 +101,6 @@ public class TextToSpeech {
*/
public static final int LANG_NOT_SUPPORTED = -2;
-
/**
* Broadcast Action: The TextToSpeech synthesizer has completed processing
* of all the text in the speech queue.
@@ -102,7 +109,6 @@ public class TextToSpeech {
public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
"android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
-
/**
* Interface definition of a callback to be invoked indicating the completion of the
* TextToSpeech engine initialization.
@@ -110,103 +116,117 @@ public class TextToSpeech {
public interface OnInitListener {
/**
* Called to signal the completion of the TextToSpeech engine initialization.
+ *
* @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
public void onInit(int status);
}
/**
- * Interface definition of a callback to be invoked indicating the TextToSpeech engine has
- * completed synthesizing an utterance with an utterance ID set.
- *
+ * Listener that will be called when the TTS service has
+ * completed synthesizing an utterance. This is only called if the utterance
+ * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
*/
public interface OnUtteranceCompletedListener {
/**
- * Called to signal the completion of the synthesis of the utterance that was identified
- * with the string parameter. This identifier is the one originally passed in the
- * parameter hashmap of the synthesis request in
- * {@link TextToSpeech#speak(String, int, HashMap)} or
- * {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the
- * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key.
+ * Called when an utterance has been synthesized.
+ *
* @param utteranceId the identifier of the utterance.
*/
public void onUtteranceCompleted(String utteranceId);
}
-
/**
- * Internal constants for the TextToSpeech functionality
- *
+ * Constants and parameter names for controlling text-to-speech.
*/
public class Engine {
- // default values for a TTS engine when settings are not found in the provider
+
/**
- * {@hide}
+ * Default speech rate.
+ * @hide
*/
- public static final int DEFAULT_RATE = 100; // 1x
+ public static final int DEFAULT_RATE = 100;
+
/**
- * {@hide}
+ * Default pitch.
+ * @hide
*/
- public static final int DEFAULT_PITCH = 100;// 1x
+ public static final int DEFAULT_PITCH = 100;
+
/**
- * {@hide}
+ * Default volume.
+ * @hide
*/
public static final float DEFAULT_VOLUME = 1.0f;
+
/**
- * {@hide}
- */
- protected static final String DEFAULT_VOLUME_STRING = "1.0";
- /**
- * {@hide}
+ * Default pan (centered).
+ * @hide
*/
public static final float DEFAULT_PAN = 0.0f;
- /**
- * {@hide}
- */
- protected static final String DEFAULT_PAN_STRING = "0.0";
/**
- * {@hide}
+ * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
+ * @hide
*/
public static final int USE_DEFAULTS = 0; // false
+
/**
- * {@hide}
+ * Package name of the default TTS engine.
+ *
+ * TODO: This should come from a system property
+ *
+ * @hide
*/
- public static final String DEFAULT_SYNTH = "com.svox.pico";
+ public static final String DEFAULT_ENGINE = "com.svox.pico";
- // default values for rendering
/**
* Default audio stream used when playing synthesized speech.
*/
public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
- // return codes for a TTS engine's check data activity
/**
* Indicates success when checking the installation status of the resources used by the
* TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_PASS = 1;
+
/**
* Indicates failure when checking the installation status of the resources used by the
* TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_FAIL = 0;
+
/**
* Indicates erroneous data when checking the installation status of the resources used by
* the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
+
/**
* Indicates missing resources when checking the installation status of the resources used
* by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
+
/**
* Indicates missing storage volume when checking the installation status of the resources
* used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
*/
public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
+ /**
+ * Intent for starting a TTS service. Services that handle this intent must
+ * extend {@link TextToSpeechService}. Normal applications should not use this intent
+ * directly, instead they should talk to the TTS service using the the methods in this
+ * class.
+ *
+ * @hide Pending API council approval
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String INTENT_ACTION_TTS_SERVICE =
+ "android.intent.action.TTS_SERVICE";
+
// intents to ask engine to install data or check its data
/**
* Activity Action: Triggers the platform TextToSpeech engine to
@@ -229,6 +249,7 @@ public class TextToSpeech {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TTS_DATA_INSTALLED =
"android.speech.tts.engine.TTS_DATA_INSTALLED";
+
/**
* Activity Action: Starts the activity from the platform TextToSpeech
* engine to verify the proper installation and availability of the
@@ -256,23 +277,36 @@ public class TextToSpeech {
public static final String ACTION_CHECK_TTS_DATA =
"android.speech.tts.engine.CHECK_TTS_DATA";
+ /**
+ * Activity intent for getting some sample text to use for demonstrating TTS.
+ *
+ * @hide This intent was used by engines written against the old API.
+ * Not sure if it should be exposed.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_GET_SAMPLE_TEXT =
+ "android.speech.tts.engine.GET_SAMPLE_TEXT";
+
// extras for a TTS engine's check data activity
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine specifies the path to its resources.
*/
public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine specifies the file names of its resources under the
* resource path.
*/
public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine specifies the locale associated with each resource file.
*/
public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine returns an ArrayList<String> of all the available voices.
@@ -280,6 +314,7 @@ public class TextToSpeech {
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
*/
public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
+
/**
* Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
* the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
@@ -287,6 +322,7 @@ public class TextToSpeech {
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
*/
public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
+
/**
* Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
* caller indicates to the TextToSpeech engine which specific sets of voice data to
@@ -309,134 +345,87 @@ public class TextToSpeech {
// keys for the parameters passed with speak commands. Hidden keys are used internally
// to maintain engine state for each TextToSpeech instance.
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_RATE = "rate";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_LANGUAGE = "language";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_COUNTRY = "country";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_VARIANT = "variant";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_ENGINE = "engine";
+
/**
- * {@hide}
+ * @hide
*/
public static final String KEY_PARAM_PITCH = "pitch";
+
/**
* Parameter key to specify the audio stream type to be used when speaking text
- * or playing back a file.
+ * or playing back a file. The value should be one of the STREAM_ constants
+ * defined in {@link AudioManager}.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_STREAM = "streamType";
+
/**
* Parameter key to identify an utterance in the
* {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
* spoken, a file has been played back or a silence duration has elapsed.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
* @see TextToSpeech#synthesizeToFile(String, HashMap, String)
*/
public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
+
/**
* Parameter key to specify the speech volume relative to the current stream type
* volume used when speaking text. Volume is specified as a float ranging from 0 to 1
* where 0 is silence, and 1 is the maximum volume (the default behavior).
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_VOLUME = "volume";
+
/**
* Parameter key to specify how the speech is panned from left to right when speaking text.
* Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
* 0 to center (the default behavior), and +1 to hard-right.
+ *
* @see TextToSpeech#speak(String, int, HashMap)
* @see TextToSpeech#playEarcon(String, int, HashMap)
*/
public static final String KEY_PARAM_PAN = "pan";
- // key positions in the array of cached parameters
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_RATE = 0;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_LANGUAGE = 2;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_COUNTRY = 4;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_VARIANT = 6;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_STREAM = 8;
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_ENGINE = 12;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_PITCH = 14;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_VOLUME = 16;
-
- /**
- * {@hide}
- */
- protected static final int PARAM_POSITION_PAN = 18;
-
-
- /**
- * {@hide}
- * Total number of cached speech parameters.
- * This number should be equal to (max param position/2) + 1.
- */
- protected static final int NB_CACHED_PARAMS = 10;
}
- /**
- * Connection needed for the TTS.
- */
- private ServiceConnection mServiceConnection;
-
- private ITts mITts = null;
- private ITtsCallback mITtscallback = null;
- private Context mContext = null;
- private String mPackageName = "";
- private OnInitListener mInitListener = null;
- private boolean mStarted = false;
+ private final Context mContext;
+ private Connection mServiceConnection;
+ private OnInitListener mInitListener;
private final Object mStartLock = new Object();
- /**
- * Used to store the cached parameters sent along with each synthesis request to the
- * TTS service.
- */
- private String[] mCachedParams;
+
+ private String mRequestedEngine;
+ private final Map<String, Uri> mEarcons;
+ private final Map<String, Uri> mUtterances;
+ private final Bundle mParams = new Bundle();
/**
* The constructor for the TextToSpeech class.
@@ -449,84 +438,98 @@ public class TextToSpeech {
* TextToSpeech engine has initialized.
*/
public TextToSpeech(Context context, OnInitListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * @hide pending approval
+ */
+ public TextToSpeech(Context context, OnInitListener listener, String engine) {
mContext = context;
- mPackageName = mContext.getPackageName();
mInitListener = listener;
+ mRequestedEngine = engine;
- mCachedParams = new String[2*Engine.NB_CACHED_PARAMS]; // store key and value
- mCachedParams[Engine.PARAM_POSITION_RATE] = Engine.KEY_PARAM_RATE;
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE] = Engine.KEY_PARAM_LANGUAGE;
- mCachedParams[Engine.PARAM_POSITION_COUNTRY] = Engine.KEY_PARAM_COUNTRY;
- mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT;
- mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM;
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID;
- mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE;
- mCachedParams[Engine.PARAM_POSITION_PITCH] = Engine.KEY_PARAM_PITCH;
- mCachedParams[Engine.PARAM_POSITION_VOLUME] = Engine.KEY_PARAM_VOLUME;
- mCachedParams[Engine.PARAM_POSITION_PAN] = Engine.KEY_PARAM_PAN;
-
- // Leave all defaults that are shown in Settings uninitialized/at the default
- // so that the values set in Settings will take effect if the application does
- // not try to change these settings itself.
- mCachedParams[Engine.PARAM_POSITION_RATE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
- String.valueOf(Engine.DEFAULT_STREAM);
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = "";
- mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = "100";
- mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
- mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
+ mEarcons = new HashMap<String, Uri>();
+ mUtterances = new HashMap<String, Uri>();
initTts();
}
+ private String getPackageName() {
+ return mContext.getPackageName();
+ }
- private void initTts() {
- mStarted = false;
-
- // Initialize the TTS, run the callback after the binding is successful
- mServiceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized(mStartLock) {
- mITts = ITts.Stub.asInterface(service);
- mStarted = true;
- // Cache the default engine and current language
- setEngineByPackageName(getDefaultEngine());
- setLanguage(getLanguage());
- if (mInitListener != null) {
- // TODO manage failures and missing resources
- mInitListener.onInit(SUCCESS);
- }
- }
+ private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
+ return runAction(action, errorResult, method, false);
+ }
+
+ private <R> R runAction(Action<R> action, R errorResult, String method) {
+ return runAction(action, errorResult, method, true);
+ }
+
+ private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ synchronized (mStartLock) {
+ if (mServiceConnection == null) {
+ Log.w(TAG, method + " failed: not bound to TTS engine");
+ return errorResult;
}
+ return mServiceConnection.runAction(action, errorResult, method, reconnect);
+ }
+ }
- public void onServiceDisconnected(ComponentName name) {
- synchronized(mStartLock) {
- mITts = null;
- mInitListener = null;
- mStarted = false;
- }
+ private int initTts() {
+ String defaultEngine = getDefaultEngine();
+ String engine = defaultEngine;
+ if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine)
+ && isEngineEnabled(engine)) {
+ engine = mRequestedEngine;
+ }
+
+ // Try requested engine
+ if (connectToEngine(engine)) {
+ return SUCCESS;
+ }
+
+ // Fall back to user's default engine if different from the already tested one
+ if (!engine.equals(defaultEngine)) {
+ if (connectToEngine(defaultEngine)) {
+ return SUCCESS;
+ }
+ }
+
+ // Fall back to the hardcoded default if different from the two above
+ if (!defaultEngine.equals(Engine.DEFAULT_ENGINE)
+ && !engine.equals(Engine.DEFAULT_ENGINE)) {
+ if (connectToEngine(Engine.DEFAULT_ENGINE)) {
+ return SUCCESS;
}
- };
+ }
+
+ return ERROR;
+ }
- Intent intent = new Intent("android.intent.action.START_TTS_SERVICE");
- intent.addCategory("android.intent.category.TTS");
- boolean bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ private boolean connectToEngine(String engine) {
+ Connection connection = new Connection();
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
if (!bound) {
- Log.e("TextToSpeech.java", "initTts() failed to bind to service");
- if (mInitListener != null) {
- mInitListener.onInit(ERROR);
- }
+ Log.e(TAG, "Failed to bind to " + engine);
+ dispatchOnInit(ERROR);
+ return false;
} else {
- // initialization listener will be called inside ServiceConnection
- Log.i("TextToSpeech.java", "initTts() successfully bound to service");
+ return true;
}
- // TODO handle plugin failures
}
+ private void dispatchOnInit(int result) {
+ synchronized (mStartLock) {
+ if (mInitListener != null) {
+ mInitListener.onInit(result);
+ mInitListener = null;
+ }
+ }
+ }
/**
* Releases the resources used by the TextToSpeech engine.
@@ -534,15 +537,17 @@ public class TextToSpeech {
* so the TextToSpeech engine can be cleanly stopped.
*/
public void shutdown() {
- try {
- mContext.unbindService(mServiceConnection);
- } catch (IllegalArgumentException e) {
- // Do nothing and fail silently since an error here indicates that
- // binding never succeeded in the first place.
- }
+ runActionNoReconnect(new Action<Void>() {
+ @Override
+ public Void run(ITextToSpeechService service) throws RemoteException {
+ service.setCallback(getPackageName(), null);
+ service.stop(getPackageName());
+ mServiceConnection.disconnect();
+ return null;
+ }
+ }, null, "shutdown");
}
-
/**
* Adds a mapping between a string of text and a sound resource in a
* package. After a call to this method, subsequent calls to
@@ -571,37 +576,12 @@ public class TextToSpeech {
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
public int addSpeech(String text, String packagename, int resourceId) {
- synchronized(mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addSpeech(mPackageName, text, packagename, resourceId);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ synchronized (mStartLock) {
+ mUtterances.put(text, makeResourceUri(packagename, resourceId));
+ return SUCCESS;
}
}
-
/**
* Adds a mapping between a string of text and a sound file. Using this, it
* is possible to add custom pronounciations for a string of text.
@@ -619,32 +599,8 @@ public class TextToSpeech {
*/
public int addSpeech(String text, String filename) {
synchronized (mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addSpeechFile(mPackageName, text, filename);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ mUtterances.put(text, Uri.parse(filename));
+ return SUCCESS;
}
}
@@ -676,36 +632,11 @@ public class TextToSpeech {
*/
public int addEarcon(String earcon, String packagename, int resourceId) {
synchronized(mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addEarcon(mPackageName, earcon, packagename, resourceId);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
+ return SUCCESS;
}
}
-
/**
* Adds a mapping between a string of text and a sound file.
* Use this to add custom earcons.
@@ -722,403 +653,199 @@ public class TextToSpeech {
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
public int addEarcon(String earcon, String filename) {
- synchronized (mStartLock) {
- if (!mStarted) {
- return ERROR;
- }
- try {
- mITts.addEarconFile(mPackageName, earcon, filename);
- return SUCCESS;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return ERROR;
+ synchronized(mStartLock) {
+ mEarcons.put(earcon, Uri.parse(filename));
+ return SUCCESS;
}
}
+ private Uri makeResourceUri(String packageName, int resourceId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .encodedAuthority(packageName)
+ .appendEncodedPath(String.valueOf(resourceId))
+ .build();
+ }
/**
* Speaks the string using the specified queuing strategy and speech
* parameters.
*
- * @param text
- * The string of text to be spoken.
- * @param queueMode
- * The queuing strategy to use.
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
- * {@link Engine#KEY_PARAM_STREAM} or
- * {@link Engine#KEY_PARAM_UTTERANCE_ID}.
+ * @param text The string of text to be spoken.
+ * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
+ * {@link Engine#KEY_PARAM_STREAM},
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID},
+ * {@link Engine#KEY_PARAM_VOLUME},
+ * {@link Engine#KEY_PARAM_PAN}.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int speak(String text, int queueMode, HashMap<String,String> params)
- {
- synchronized (mStartLock) {
- int result = ERROR;
- Log.i("TextToSpeech.java - speak", "speak text of length " + text.length());
- if (!mStarted) {
- Log.e("TextToSpeech.java - speak", "service isn't started");
- return result;
- }
- try {
- if ((params != null) && (!params.isEmpty())) {
- setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
- setCachedParam(params, Engine.KEY_PARAM_VOLUME, Engine.PARAM_POSITION_VOLUME);
- setCachedParam(params, Engine.KEY_PARAM_PAN, Engine.PARAM_POSITION_PAN);
+ public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ Uri utteranceUri = mUtterances.get(text);
+ if (utteranceUri != null) {
+ return service.playAudio(getPackageName(), utteranceUri, queueMode,
+ getParams(params));
+ } else {
+ return service.speak(getPackageName(), text, queueMode, getParams(params));
}
- result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - speak", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
}
- }
+ }, ERROR, "speak");
}
-
/**
* Plays the earcon using the specified queueing mode and parameters.
+ * The earcon must already have been added with {@link #addEarcon(String, String)} or
+ * {@link #addEarcon(String, String, int)}.
*
- * @param earcon
- * The earcon that should be played
- * @param queueMode
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
- * {@link Engine#KEY_PARAM_STREAM} or
+ * @param earcon The earcon that should be played
+ * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
+ * {@link Engine#KEY_PARAM_STREAM},
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int playEarcon(String earcon, int queueMode,
- HashMap<String,String> params) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- if ((params != null) && (!params.isEmpty())) {
- String extra = params.get(Engine.KEY_PARAM_STREAM);
- if (extra != null) {
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra;
- }
- setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM);
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
+ public int playEarcon(final String earcon, final int queueMode,
+ final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ Uri earconUri = mEarcons.get(earcon);
+ if (earconUri == null) {
+ return ERROR;
}
- result = mITts.playEarcon(mPackageName, earcon, queueMode, null);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playEarcon", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
+ return service.playAudio(getPackageName(), earconUri, queueMode,
+ getParams(params));
}
- }
+ }, ERROR, "playEarcon");
}
/**
* Plays silence for the specified amount of time using the specified
* queue mode.
*
- * @param durationInMs
- * A long that indicates how long the silence should last.
- * @param queueMode
- * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
+ * @param durationInMs The duration of the silence.
+ * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
+ public int playSilence(final long durationInMs, final int queueMode,
+ final HashMap<String, String> params) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.playSilence(getPackageName(), durationInMs, queueMode,
+ getParams(params));
}
- try {
- if ((params != null) && (!params.isEmpty())) {
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- }
- result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - playSilence", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
- }
- }
+ }, ERROR, "playSilence");
}
-
/**
- * Returns whether or not the TextToSpeech engine is busy speaking.
+ * Checks whether the TTS engine is busy speaking.
*
- * @return Whether or not the TextToSpeech engine is busy speaking.
+ * @return {@code true} if the TTS engine is speaking.
*/
public boolean isSpeaking() {
- synchronized (mStartLock) {
- if (!mStarted) {
- return false;
+ return runAction(new Action<Boolean>() {
+ @Override
+ public Boolean run(ITextToSpeechService service) throws RemoteException {
+ return service.isSpeaking();
}
- try {
- return mITts.isSpeaking();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isSpeaking", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- }
- return false;
- }
+ }, false, "isSpeaking");
}
-
/**
* Interrupts the current utterance (whether played or rendered to file) and discards other
* utterances in the queue.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int stop() {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.stop(getPackageName());
}
- try {
- result = mITts.stop(mPackageName);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - stop", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
- }
- }
+ }, ERROR, "stop");
}
-
/**
- * Sets the speech rate for the TextToSpeech engine.
+ * Sets the speech rate.
*
* This has no effect on any pre-recorded speech.
*
- * @param speechRate
- * The speech rate for the TextToSpeech engine. 1 is the normal speed,
- * lower values slow down the speech (0.5 is half the normal speech rate),
- * greater values accelerate it (2 is twice the normal speech rate).
+ * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
+ * lower values slow down the speech ({@code 0.5} is half the normal speech rate),
+ * greater values accelerate it ({@code 2.0} is twice the normal speech rate).
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int setSpeechRate(float speechRate) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- if (speechRate > 0) {
- int rate = (int)(speechRate*100);
- mCachedParams[Engine.PARAM_POSITION_RATE + 1] = String.valueOf(rate);
- // the rate is not set here, instead it is cached so it will be associated
- // with all upcoming utterances.
- if (speechRate > 0.0f) {
- result = SUCCESS;
- } else {
- result = ERROR;
- }
+ if (speechRate > 0.0f) {
+ int intRate = (int)(speechRate * 100);
+ if (intRate > 0) {
+ synchronized (mStartLock) {
+ mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
}
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setSpeechRate", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setSpeechRate", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ return SUCCESS;
}
}
+ return ERROR;
}
-
/**
* Sets the speech pitch for the TextToSpeech engine.
*
* This has no effect on any pre-recorded speech.
*
- * @param pitch
- * The pitch for the TextToSpeech engine. 1 is the normal pitch,
+ * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
* lower values lower the tone of the synthesized voice,
* greater values increase it.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int setPitch(float pitch) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- // the pitch is not set here, instead it is cached so it will be associated
- // with all upcoming utterances.
- if (pitch > 0) {
- int p = (int)(pitch*100);
- mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = String.valueOf(p);
- result = SUCCESS;
+ if (pitch > 0.0f) {
+ int intPitch = (int)(pitch * 100);
+ if (intPitch > 0) {
+ synchronized (mStartLock) {
+ mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
}
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setPitch", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setPitch", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ return SUCCESS;
}
}
+ return ERROR;
}
-
/**
- * Sets the language for the TextToSpeech engine.
- * The TextToSpeech engine will try to use the closest match to the specified
+ * Sets the text-to-speech language.
+ * The TTS engine will try to use the closest match to the specified
* language as represented by the Locale, but there is no guarantee that the exact same Locale
* will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
* before choosing the language to use for the next utterances.
*
- * @param loc
- * The locale describing the language to be used.
+ * @param loc The locale describing the language to be used.
*
- * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+ * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
* {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
- public int setLanguage(Locale loc) {
- synchronized (mStartLock) {
- int result = LANG_NOT_SUPPORTED;
- if (!mStarted) {
- return result;
- }
- if (loc == null) {
- return result;
- }
- try {
+ public int setLanguage(final Locale loc) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ if (loc == null) {
+ return LANG_NOT_SUPPORTED;
+ }
String language = loc.getISO3Language();
String country = loc.getISO3Country();
String variant = loc.getVariant();
@@ -1126,386 +853,317 @@ public class TextToSpeech {
// the available parts.
// Note that the language is not actually set here, instead it is cached so it
// will be associated with all upcoming utterances.
- result = mITts.isLanguageAvailable(language, country, variant, mCachedParams);
+ int result = service.loadLanguage(language, country, variant);
if (result >= LANG_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = language;
- if (result >= LANG_COUNTRY_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = country;
- } else {
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = "";
- }
- if (result >= LANG_COUNTRY_VAR_AVAILABLE){
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = variant;
- } else {
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = "";
+ if (result < LANG_COUNTRY_VAR_AVAILABLE) {
+ variant = "";
+ if (result < LANG_COUNTRY_AVAILABLE) {
+ country = "";
+ }
}
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
}
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setLanguage", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
return result;
}
- }
+ }, LANG_NOT_SUPPORTED, "setLanguage");
}
-
/**
* Returns a Locale instance describing the language currently being used by the TextToSpeech
* engine.
+ *
* @return language, country (if any) and variant (if any) used by the engine stored in a Locale
- * instance, or null is the TextToSpeech engine has failed.
+ * instance, or {@code null} on error.
*/
public Locale getLanguage() {
- synchronized (mStartLock) {
- if (!mStarted) {
- return null;
- }
- try {
- // Only do a call to the native synth if there is nothing in the cached params
- if (mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1].length() < 1){
- String[] locStrings = mITts.getLanguage();
- if ((locStrings != null) && (locStrings.length == 3)) {
- return new Locale(locStrings[0], locStrings[1], locStrings[2]);
- } else {
- return null;
- }
- } else {
- return new Locale(mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1]);
+ return runAction(new Action<Locale>() {
+ @Override
+ public Locale run(ITextToSpeechService service) throws RemoteException {
+ String[] locStrings = service.getLanguage();
+ if (locStrings != null && locStrings.length == 3) {
+ return new Locale(locStrings[0], locStrings[1], locStrings[2]);
}
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - getLanguage", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
+ return null;
}
- return null;
- }
+ }, null, "getLanguage");
}
/**
* Checks if the specified language as represented by the Locale is available and supported.
*
- * @param loc
- * The Locale describing the language to be used.
+ * @param loc The Locale describing the language to be used.
*
- * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
+ * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
* {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
* {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
*/
- public int isLanguageAvailable(Locale loc) {
- synchronized (mStartLock) {
- int result = LANG_NOT_SUPPORTED;
- if (!mStarted) {
- return result;
- }
- try {
- result = mITts.isLanguageAvailable(loc.getISO3Language(),
- loc.getISO3Country(), loc.getVariant(), mCachedParams);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - isLanguageAvailable", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ public int isLanguageAvailable(final Locale loc) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.isLanguageAvailable(loc.getISO3Language(),
+ loc.getISO3Country(), loc.getVariant());
}
- }
+ }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
}
-
/**
* Synthesizes the given text to a file using the specified parameters.
*
- * @param text
- * The String of text that should be synthesized
- * @param params
- * The list of parameters to be used. Can be null if no parameters are given.
- * They are specified using a (key, value) pair, where the key can be
+ * @param text Thetext that should be synthesized
+ * @param params Parameters for the request. Can be null.
+ * Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
- * @param filename
- * The string that gives the full output filename; it should be
+ * @param filename Absolute file filename to write the generated audio data to.It should be
* something like "/sdcard/myappsounds/mysound.wav".
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int synthesizeToFile(String text, HashMap<String,String> params,
- String filename) {
- Log.i("TextToSpeech.java", "synthesizeToFile()");
- synchronized (mStartLock) {
- int result = ERROR;
- Log.i("TextToSpeech.java - synthesizeToFile", "synthesizeToFile text of length "
- + text.length());
- if (!mStarted) {
- Log.e("TextToSpeech.java - synthesizeToFile", "service isn't started");
- return result;
- }
- try {
- if ((params != null) && (!params.isEmpty())) {
- // no need to read the stream type here
- setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID,
- Engine.PARAM_POSITION_UTTERANCE_ID);
- setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE);
- }
- result = mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename) ?
- SUCCESS : ERROR;
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - synthesizeToFile", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- resetCachedParams();
- return result;
+ public int synthesizeToFile(final String text, final HashMap<String, String> params,
+ final String filename) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ return service.synthesizeToFile(getPackageName(), text, filename,
+ getParams(params));
}
+ }, ERROR, "synthesizeToFile");
+ }
+
+ private Bundle getParams(HashMap<String, String> params) {
+ if (params != null && !params.isEmpty()) {
+ Bundle bundle = new Bundle(mParams);
+ copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
+ copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
+ copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
+ copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
+ return bundle;
+ } else {
+ return mParams;
}
}
+ private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String value = params.get(key);
+ if (value != null) {
+ bundle.putString(key, value);
+ }
+ }
- /**
- * Convenience method to reset the cached parameters to the current default values
- * if they are not persistent between calls to the service.
- */
- private void resetCachedParams() {
- mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
- String.valueOf(Engine.DEFAULT_STREAM);
- mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID+ 1] = "";
- mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING;
- mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING;
+ private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String valueString = params.get(key);
+ if (!TextUtils.isEmpty(valueString)) {
+ try {
+ int value = Integer.parseInt(valueString);
+ bundle.putInt(key, value);
+ } catch (NumberFormatException ex) {
+ // don't set the value in the bundle
+ }
+ }
}
- /**
- * Convenience method to save a parameter in the cached parameter array, at the given index,
- * for a property saved in the given hashmap.
- */
- private void setCachedParam(HashMap<String,String> params, String key, int keyIndex) {
- String extra = params.get(key);
- if (extra != null) {
- mCachedParams[keyIndex+1] = extra;
+ private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
+ String valueString = params.get(key);
+ if (!TextUtils.isEmpty(valueString)) {
+ try {
+ float value = Float.parseFloat(valueString);
+ bundle.putFloat(key, value);
+ } catch (NumberFormatException ex) {
+ // don't set the value in the bundle
+ }
}
}
/**
- * Sets the OnUtteranceCompletedListener that will fire when an utterance completes.
+ * Sets the listener that will be notified when synthesis of an utterance completes.
*
- * @param listener
- * The OnUtteranceCompletedListener
+ * @param listener The listener to use.
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
- public int setOnUtteranceCompletedListener(
- final OnUtteranceCompletedListener listener) {
- synchronized (mStartLock) {
- int result = ERROR;
- if (!mStarted) {
- return result;
- }
- mITtscallback = new ITtsCallback.Stub() {
- public void utteranceCompleted(String utteranceId) throws RemoteException {
- if (listener != null) {
- listener.onUtteranceCompleted(utteranceId);
+ public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
+ return runAction(new Action<Integer>() {
+ @Override
+ public Integer run(ITextToSpeechService service) throws RemoteException {
+ ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() {
+ public void utteranceCompleted(String utteranceId) {
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
}
- }
- };
- try {
- result = mITts.registerCallback(mPackageName, mITtscallback);
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - registerCallback", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ };
+ service.setCallback(getPackageName(), callback);
+ return SUCCESS;
}
- }
+ }, ERROR, "setOnUtteranceCompletedListener");
}
/**
- * Sets the speech synthesis engine to be used by its packagename.
+ * Sets the TTS engine to use.
*
- * @param enginePackageName
- * The packagename for the synthesis engine (ie, "com.svox.pico")
+ * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
*
- * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS}.
*/
+ // TODO: add @Deprecated{This method does not tell the caller when the new engine
+ // has been initialized. You should create a new TextToSpeech object with the new
+ // engine instead.}
public int setEngineByPackageName(String enginePackageName) {
- synchronized (mStartLock) {
- int result = TextToSpeech.ERROR;
- if (!mStarted) {
- return result;
- }
- try {
- result = mITts.setEngineByPackageName(enginePackageName);
- if (result == TextToSpeech.SUCCESS){
- mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = enginePackageName;
- }
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return result;
+ mRequestedEngine = enginePackageName;
+ return initTts();
+ }
+
+ /**
+ * Gets the package name of the default speech synthesis engine.
+ *
+ * @return Package name of the TTS engine that the user has chosen as their default.
+ */
+ public String getDefaultEngine() {
+ String engine = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.TTS_DEFAULT_SYNTH);
+ return engine != null ? engine : Engine.DEFAULT_ENGINE;
+ }
+
+ /**
+ * Checks whether the user's settings should override settings requested by the calling
+ * application.
+ */
+ public boolean areDefaultsEnforced() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1;
+ }
+
+ private boolean isEngineEnabled(String engine) {
+ if (Engine.DEFAULT_ENGINE.equals(engine)) {
+ return true;
+ }
+ for (String enabled : getEnabledEngines()) {
+ if (engine.equals(enabled)) {
+ return true;
}
}
+ return false;
}
+ private String[] getEnabledEngines() {
+ String str = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.TTS_ENABLED_PLUGINS);
+ if (TextUtils.isEmpty(str)) {
+ return new String[0];
+ }
+ return str.split(" ");
+ }
/**
- * Gets the packagename of the default speech synthesis engine.
+ * Gets a list of all installed TTS engines.
*
- * @return Packagename of the TTS engine that the user has chosen as their default.
+ * @return A list of engine info objects. The list can be empty, but will never by {@code null}.
+ *
+ * @hide Pending approval
*/
- public String getDefaultEngine() {
- synchronized (mStartLock) {
- String engineName = "";
- if (!mStarted) {
- return engineName;
+ public List<EngineInfo> getEngines() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ List<ResolveInfo> resolveInfos =
+ pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolveInfos == null) return Collections.emptyList();
+ List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo service = resolveInfo.serviceInfo;
+ if (service != null) {
+ EngineInfo engine = new EngineInfo();
+ // Using just the package name isn't great, since it disallows having
+ // multiple engines in the same package, but that's what the existing API does.
+ engine.name = service.packageName;
+ CharSequence label = service.loadLabel(pm);
+ engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
+ engine.icon = service.getIconResource();
+ engines.add(engine);
+ }
+ }
+ return engines;
+ }
+
+ private class Connection implements ServiceConnection {
+ private ITextToSpeechService mService;
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Connected to " + name);
+ synchronized(mStartLock) {
+ if (mServiceConnection != null) {
+ // Disconnect any previous service connection
+ mServiceConnection.disconnect();
+ }
+ mServiceConnection = this;
+ mService = ITextToSpeechService.Stub.asInterface(service);
+ dispatchOnInit(SUCCESS);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized(mStartLock) {
+ mService = null;
+ // If this is the active connection, clear it
+ if (mServiceConnection == this) {
+ mServiceConnection = null;
+ }
}
+ }
+
+ public void disconnect() {
+ mContext.unbindService(this);
+ }
+
+ public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
try {
- engineName = mITts.getDefaultEngine();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setEngineByPackageName", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return engineName;
+ synchronized (mStartLock) {
+ if (mService == null) {
+ Log.w(TAG, method + " failed: not connected to TTS engine");
+ return errorResult;
+ }
+ return action.run(mService);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, method + " failed", ex);
+ if (reconnect) {
+ disconnect();
+ initTts();
+ }
+ return errorResult;
}
}
}
+ private interface Action<R> {
+ R run(ITextToSpeechService service) throws RemoteException;
+ }
/**
- * Returns whether or not the user is forcing their defaults to override the
- * Text-To-Speech settings set by applications.
+ * Information about an installed text-to-speech engine.
*
- * @return Whether or not defaults are enforced.
+ * @see TextToSpeech#getEngines
+ * @hide Pending approval
*/
- public boolean areDefaultsEnforced() {
- synchronized (mStartLock) {
- boolean defaultsEnforced = false;
- if (!mStarted) {
- return defaultsEnforced;
- }
- try {
- defaultsEnforced = mITts.areDefaultsEnforced();
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (NullPointerException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "NullPointerException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } catch (IllegalStateException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - areDefaultsEnforced", "IllegalStateException");
- e.printStackTrace();
- mStarted = false;
- initTts();
- } finally {
- return defaultsEnforced;
- }
+ public static class EngineInfo {
+ /**
+ * Engine package name..
+ */
+ public String name;
+ /**
+ * Localized label for the engine.
+ */
+ public String label;
+ /**
+ * Icon for the engine.
+ */
+ public int icon;
+
+ @Override
+ public String toString() {
+ return "EngineInfo{name=" + name + "}";
}
+
}
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
new file mode 100644
index 0000000..da97fb4
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech.Engine;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+
+
+/**
+ * Abstract base class for TTS engine implementations.
+ *
+ * @hide Pending approval
+ */
+public abstract class TextToSpeechService extends Service {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "TextToSpeechService";
+
+ private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+ private static final String SYNTH_THREAD_NAME = "SynthThread";
+
+ private SynthHandler mSynthHandler;
+
+ private CallbackMap mCallbacks;
+
+ @Override
+ public void onCreate() {
+ if (DBG) Log.d(TAG, "onCreate()");
+ super.onCreate();
+
+ SynthThread synthThread = new SynthThread();
+ synthThread.start();
+ mSynthHandler = new SynthHandler(synthThread.getLooper());
+
+ mCallbacks = new CallbackMap();
+
+ // Load default language
+ onLoadLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DBG) Log.d(TAG, "onDestroy()");
+
+ // Tell the synthesizer to stop
+ mSynthHandler.quit();
+
+ // Unregister all callbacks.
+ mCallbacks.kill();
+
+ super.onDestroy();
+ }
+
+ /**
+ * Checks whether the engine supports a given language.
+ *
+ * Can be called on multiple threads.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
+
+ /**
+ * Returns the language, country and variant currently being used by the TTS engine.
+ *
+ * Can be called on multiple threads.
+ *
+ * @return A 3-element array, containing language (ISO 3-letter code),
+ * country (ISO 3-letter code) and variant used by the engine.
+ * The country and variant may be {@code ""}. If country is empty, then variant must
+ * be empty too.
+ * @see Locale#getISO3Language()
+ * @see Locale#getISO3Country()
+ * @see Locale#getVariant()
+ */
+ protected abstract String[] onGetLanguage();
+
+ /**
+ * Notifies the engine that it should load a speech synthesis language. There is no guarantee
+ * that this method is always called before the language is used for synthesis. It is merely
+ * a hint to the engine that it will probably get some synthesis requests for this language
+ * at some point in the future.
+ *
+ * Can be called on multiple threads.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return Code indicating the support status for the locale.
+ * One of {@link TextToSpeech#LANG_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
+ * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
+ * {@link TextToSpeech#LANG_MISSING_DATA}
+ * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
+ */
+ protected abstract int onLoadLanguage(String lang, String country, String variant);
+
+ /**
+ * Notifies the service that it should stop any in-progress speech synthesis.
+ * This method can be called even if no speech synthesis is currently in progress.
+ *
+ * Can be called on multiple threads, but not on the synthesis thread.
+ */
+ protected abstract void onStop();
+
+ /**
+ * Tells the service to synthesize speech from the given text. This method should
+ * block until the synthesis is finished.
+ *
+ * Called on the synthesis thread.
+ *
+ * @param request The synthesis request. The method should use the methods in the request
+ * object to communicate the results of the synthesis.
+ */
+ protected abstract void onSynthesizeText(SynthesisRequest request);
+
+ private boolean areDefaultsEnforced() {
+ return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS,
+ TextToSpeech.Engine.USE_DEFAULTS) == 1;
+ }
+
+ private int getDefaultSpeechRate() {
+ return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
+ }
+
+ private String getDefaultLanguage() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_LANG,
+ Locale.getDefault().getISO3Language());
+ }
+
+ private String getDefaultCountry() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_COUNTRY,
+ Locale.getDefault().getISO3Country());
+ }
+
+ private String getDefaultVariant() {
+ return getSecureSettingString(Settings.Secure.TTS_DEFAULT_VARIANT,
+ Locale.getDefault().getVariant());
+ }
+
+ private int getSecureSettingInt(String name, int defaultValue) {
+ return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
+ }
+
+ private String getSecureSettingString(String name, String defaultValue) {
+ String value = Settings.Secure.getString(getContentResolver(), name);
+ return value != null ? value : defaultValue;
+ }
+
+ /**
+ * Synthesizer thread. This thread is used to run {@link SynthHandler}.
+ */
+ private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
+
+ private boolean mFirstIdle = true;
+
+ public SynthThread() {
+ super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_AUDIO);
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ getLooper().getQueue().addIdleHandler(this);
+ }
+
+ @Override
+ public boolean queueIdle() {
+ if (mFirstIdle) {
+ mFirstIdle = false;
+ } else {
+ broadcastTtsQueueProcessingCompleted();
+ }
+ return true;
+ }
+
+ private void broadcastTtsQueueProcessingCompleted() {
+ Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
+ if (DBG) Log.d(TAG, "Broadcasting: " + i);
+ sendBroadcast(i);
+ }
+ }
+
+ private class SynthHandler extends Handler {
+
+ private SpeechItem mCurrentSpeechItem = null;
+
+ public SynthHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void dispatchUtteranceCompleted(SpeechItem item) {
+ String utteranceId = item.getUtteranceId();
+ if (!TextUtils.isEmpty(utteranceId)) {
+ mCallbacks.dispatchUtteranceCompleted(item.getCallingApp(), utteranceId);
+ }
+ }
+
+ private synchronized SpeechItem getCurrentSpeechItem() {
+ return mCurrentSpeechItem;
+ }
+
+ private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
+ SpeechItem old = mCurrentSpeechItem;
+ mCurrentSpeechItem = speechItem;
+ return old;
+ }
+
+ public boolean isSpeaking() {
+ return getCurrentSpeechItem() != null;
+ }
+
+ public void quit() {
+ // Don't process any more speech items
+ getLooper().quit();
+ // Stop the current speech item
+ SpeechItem current = setCurrentSpeechItem(null);
+ if (current != null) {
+ current.stop();
+ }
+ }
+
+ /**
+ * Adds a speech item to the queue.
+ *
+ * Called on a service binder thread.
+ */
+ public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
+ if (!speechItem.isValid()) {
+ return TextToSpeech.ERROR;
+ }
+ // TODO: The old code also supported the undocumented queueMode == 2,
+ // which clears out all pending items from the calling app, as well as all
+ // non-file items from other apps.
+ if (queueMode == TextToSpeech.QUEUE_FLUSH) {
+ stop(speechItem.getCallingApp());
+ }
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ setCurrentSpeechItem(speechItem);
+ if (speechItem.play() == TextToSpeech.SUCCESS) {
+ dispatchUtteranceCompleted(speechItem);
+ }
+ setCurrentSpeechItem(null);
+ }
+ };
+ Message msg = Message.obtain(this, runnable);
+ // The obj is used to remove all callbacks from the given app in stop(String).
+ msg.obj = speechItem.getCallingApp();
+ if (sendMessage(msg)) {
+ return TextToSpeech.SUCCESS;
+ } else {
+ Log.w(TAG, "SynthThread has quit");
+ return TextToSpeech.ERROR;
+ }
+ }
+
+ /**
+ * Stops all speech output and removes any utterances still in the queue for
+ * the calling app.
+ *
+ * Called on a service binder thread.
+ */
+ public int stop(String callingApp) {
+ if (TextUtils.isEmpty(callingApp)) {
+ return TextToSpeech.ERROR;
+ }
+ removeCallbacksAndMessages(callingApp);
+ SpeechItem current = setCurrentSpeechItem(null);
+ if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
+ current.stop();
+ }
+ return TextToSpeech.SUCCESS;
+ }
+ }
+
+ /**
+ * An item in the synth thread queue.
+ */
+ private static abstract class SpeechItem {
+ private final String mCallingApp;
+ private final Bundle mParams;
+ private boolean mStarted = false;
+ private boolean mStopped = false;
+
+ public SpeechItem(String callingApp, Bundle params) {
+ mCallingApp = callingApp;
+ mParams = params;
+ }
+
+ public String getCallingApp() {
+ return mCallingApp;
+ }
+
+ /**
+ * Checker whether the item is valid. If this method returns false, the item should not
+ * be played.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Plays the speech item. Blocks until playback is finished.
+ * Must not be called more than once.
+ *
+ * Only called on the synthesis thread.
+ *
+ * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ */
+ public int play() {
+ synchronized (this) {
+ if (mStarted) {
+ throw new IllegalStateException("play() called twice");
+ }
+ mStarted = true;
+ }
+ return playImpl();
+ }
+
+ /**
+ * Stops the speech item.
+ * Must not be called more than once.
+ *
+ * Can be called on multiple threads, but not on the synthesis thread.
+ */
+ public void stop() {
+ synchronized (this) {
+ if (mStopped) {
+ throw new IllegalStateException("stop() called twice");
+ }
+ mStopped = true;
+ }
+ stopImpl();
+ }
+
+ protected abstract int playImpl();
+
+ protected abstract void stopImpl();
+
+ public int getStreamType() {
+ return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
+ }
+
+ public float getVolume() {
+ return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
+ }
+
+ public float getPan() {
+ return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
+ }
+
+ public String getUtteranceId() {
+ return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
+ }
+
+ protected String getStringParam(String key, String defaultValue) {
+ return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
+ }
+
+ protected int getIntParam(String key, int defaultValue) {
+ return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
+ }
+
+ protected float getFloatParam(String key, float defaultValue) {
+ return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
+ }
+ }
+
+ private class SynthesisSpeechItem extends SpeechItem {
+ private final String mText;
+ private SynthesisRequest mSynthesisRequest;
+
+ public SynthesisSpeechItem(String callingApp, Bundle params, String text) {
+ super(callingApp, params);
+ mText = text;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ @Override
+ public boolean isValid() {
+ if (TextUtils.isEmpty(mText)) {
+ Log.w(TAG, "Got empty text");
+ return false;
+ }
+ if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
+ Log.w(TAG, "Text too long: " + mText.length() + " chars");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ SynthesisRequest synthesisRequest;
+ synchronized (this) {
+ mSynthesisRequest = createSynthesisRequest();
+ synthesisRequest = mSynthesisRequest;
+ }
+ setRequestParams(synthesisRequest);
+ TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+ return synthesisRequest.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
+ }
+
+ protected SynthesisRequest createSynthesisRequest() {
+ return new PlaybackSynthesisRequest(mText, getStreamType(), getVolume(), getPan());
+ }
+
+ private void setRequestParams(SynthesisRequest request) {
+ if (areDefaultsEnforced()) {
+ request.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant());
+ request.setSpeechRate(getDefaultSpeechRate());
+ } else {
+ request.setLanguage(getLanguage(), getCountry(), getVariant());
+ request.setSpeechRate(getSpeechRate());
+ }
+ request.setPitch(getPitch());
+ }
+
+ @Override
+ protected void stopImpl() {
+ SynthesisRequest synthesisRequest;
+ synchronized (this) {
+ synthesisRequest = mSynthesisRequest;
+ }
+ synthesisRequest.stop();
+ TextToSpeechService.this.onStop();
+ }
+
+ public String getLanguage() {
+ return getStringParam(Engine.KEY_PARAM_LANGUAGE, getDefaultLanguage());
+ }
+
+ private boolean hasLanguage() {
+ return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
+ }
+
+ private String getCountry() {
+ if (!hasLanguage()) return getDefaultCountry();
+ return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
+ }
+
+ private String getVariant() {
+ if (!hasLanguage()) return getDefaultVariant();
+ return getStringParam(Engine.KEY_PARAM_VARIANT, "");
+ }
+
+ private int getSpeechRate() {
+ return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
+ }
+
+ private int getPitch() {
+ return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
+ }
+ }
+
+ private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
+ private final File mFile;
+
+ public SynthesisToFileSpeechItem(String callingApp, Bundle params, String text,
+ File file) {
+ super(callingApp, params, text);
+ mFile = file;
+ }
+
+ @Override
+ public boolean isValid() {
+ if (!super.isValid()) {
+ return false;
+ }
+ return checkFile(mFile);
+ }
+
+ @Override
+ protected SynthesisRequest createSynthesisRequest() {
+ return new FileSynthesisRequest(getText(), mFile);
+ }
+
+ /**
+ * Checks that the given file can be used for synthesis output.
+ */
+ private boolean checkFile(File file) {
+ try {
+ if (file.exists()) {
+ Log.v(TAG, "File " + file + " exists, deleting.");
+ if (!file.delete()) {
+ Log.e(TAG, "Failed to delete " + file);
+ return false;
+ }
+ }
+ if (!file.createNewFile()) {
+ Log.e(TAG, "Can't create file " + file);
+ return false;
+ }
+ if (!file.delete()) {
+ Log.e(TAG, "Failed to delete " + file);
+ return false;
+ }
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Can't use " + file + " due to exception " + e);
+ return false;
+ }
+ }
+ }
+
+ private class AudioSpeechItem extends SpeechItem {
+
+ private final BlockingMediaPlayer mPlayer;
+
+ public AudioSpeechItem(String callingApp, Bundle params, Uri uri) {
+ super(callingApp, params);
+ mPlayer = new BlockingMediaPlayer(TextToSpeechService.this, uri, getStreamType());
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ return mPlayer.startAndWait() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
+ }
+
+ @Override
+ protected void stopImpl() {
+ mPlayer.stop();
+ }
+ }
+
+ private class SilenceSpeechItem extends SpeechItem {
+ private final long mDuration;
+ private final ConditionVariable mDone;
+
+ public SilenceSpeechItem(String callingApp, Bundle params, long duration) {
+ super(callingApp, params);
+ mDuration = duration;
+ mDone = new ConditionVariable();
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ boolean aborted = mDone.block(mDuration);
+ return aborted ? TextToSpeech.ERROR : TextToSpeech.SUCCESS;
+ }
+
+ @Override
+ protected void stopImpl() {
+ mDone.open();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
+ return mBinder;
+ }
+ return null;
+ }
+
+ /**
+ * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
+ * called called from several different threads.
+ */
+ private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
+
+ public int speak(String callingApp, String text, int queueMode, Bundle params) {
+ SpeechItem item = new SynthesisSpeechItem(callingApp, params, text);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public int synthesizeToFile(String callingApp, String text, String filename,
+ Bundle params) {
+ File file = new File(filename);
+ SpeechItem item = new SynthesisToFileSpeechItem(callingApp, params, text, file);
+ return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+ }
+
+ public int playAudio(String callingApp, Uri audioUri, int queueMode, Bundle params) {
+ SpeechItem item = new AudioSpeechItem(callingApp, params, audioUri);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public int playSilence(String callingApp, long duration, int queueMode, Bundle params) {
+ SpeechItem item = new SilenceSpeechItem(callingApp, params, duration);
+ return mSynthHandler.enqueueSpeechItem(queueMode, item);
+ }
+
+ public boolean isSpeaking() {
+ return mSynthHandler.isSpeaking();
+ }
+
+ public int stop(String callingApp) {
+ return mSynthHandler.stop(callingApp);
+ }
+
+ public String[] getLanguage() {
+ return onGetLanguage();
+ }
+
+ public int isLanguageAvailable(String lang, String country, String variant) {
+ return onIsLanguageAvailable(lang, country, variant);
+ }
+
+ public int loadLanguage(String lang, String country, String variant) {
+ return onLoadLanguage(lang, country, variant);
+ }
+
+ public void setCallback(String packageName, ITextToSpeechCallback cb) {
+ mCallbacks.setCallback(packageName, cb);
+ }
+ };
+
+ private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
+
+ private final HashMap<String, ITextToSpeechCallback> mAppToCallback
+ = new HashMap<String, ITextToSpeechCallback>();
+
+ public void setCallback(String packageName, ITextToSpeechCallback cb) {
+ synchronized (mAppToCallback) {
+ ITextToSpeechCallback old;
+ if (cb != null) {
+ register(cb, packageName);
+ old = mAppToCallback.put(packageName, cb);
+ } else {
+ old = mAppToCallback.remove(packageName);
+ }
+ if (old != null && old != cb) {
+ unregister(old);
+ }
+ }
+ }
+
+ public void dispatchUtteranceCompleted(String packageName, String utteranceId) {
+ ITextToSpeechCallback cb;
+ synchronized (mAppToCallback) {
+ cb = mAppToCallback.get(packageName);
+ }
+ if (cb == null) return;
+ try {
+ cb.utteranceCompleted(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed: " + e);
+ }
+ }
+
+ @Override
+ public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
+ String packageName = (String) cookie;
+ synchronized (mAppToCallback) {
+ mAppToCallback.remove(packageName);
+ }
+ mSynthHandler.stop(packageName);
+ }
+
+ @Override
+ public void kill() {
+ synchronized (mAppToCallback) {
+ mAppToCallback.clear();
+ super.kill();
+ }
+ }
+
+ }
+
+}
diff --git a/core/java/android/text/CharSequenceIterator.java b/core/java/android/text/CharSequenceIterator.java
new file mode 100644
index 0000000..4b8ac10
--- /dev/null
+++ b/core/java/android/text/CharSequenceIterator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import java.text.CharacterIterator;
+
+/** {@hide} */
+public class CharSequenceIterator implements CharacterIterator {
+ private final CharSequence mValue;
+
+ private final int mLength;
+ private int mIndex;
+
+ public CharSequenceIterator(CharSequence value) {
+ mValue = value;
+ mLength = value.length();
+ mIndex = 0;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public char current() {
+ if (mIndex == mLength) {
+ return DONE;
+ }
+ return mValue.charAt(mIndex);
+ }
+
+ /** {@inheritDoc} */
+ public int getBeginIndex() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public int getEndIndex() {
+ return mLength;
+ }
+
+ /** {@inheritDoc} */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /** {@inheritDoc} */
+ public char first() {
+ return setIndex(0);
+ }
+
+ /** {@inheritDoc} */
+ public char last() {
+ return setIndex(mLength - 1);
+ }
+
+ /** {@inheritDoc} */
+ public char next() {
+ if (mIndex == mLength) {
+ return DONE;
+ }
+ return setIndex(mIndex + 1);
+ }
+
+ /** {@inheritDoc} */
+ public char previous() {
+ if (mIndex == 0) {
+ return DONE;
+ }
+ return setIndex(mIndex - 1);
+ }
+
+ /** {@inheritDoc} */
+ public char setIndex(int index) {
+ if ((index < 0) || (index > mLength)) {
+ throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]");
+ }
+ mIndex = index;
+ return current();
+ }
+}
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index d426d12..831ccc5 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -58,6 +58,13 @@ extends CharSequence
int flags, float[] advances, int advancesIndex, Paint paint);
/**
+ * Just like {@link Paint#getTextRunAdvances}.
+ * @hide
+ */
+ float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+ int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
+
+ /**
* Just like {@link Paint#getTextRunCursor}.
* @hide
*/
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 13cb5e6..679e2cc 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -16,6 +16,8 @@
package android.text;
+import java.text.BreakIterator;
+
/**
* Utility class for manipulating cursors and selections in CharSequences.
@@ -38,7 +40,7 @@ public class Selection {
else
return -1;
}
-
+
/**
* Return the offset of the selection edge or cursor, or -1 if
* there is no selection or cursor.
@@ -57,7 +59,7 @@ public class Selection {
// private static int pin(int value, int min, int max) {
// return value < min ? 0 : (value > max ? max : value);
// }
-
+
/**
* Set the selection anchor to <code>start</code> and the selection edge
* to <code>stop</code>.
@@ -69,7 +71,7 @@ public class Selection {
int ostart = getSelectionStart(text);
int oend = getSelectionEnd(text);
-
+
if (ostart != start || oend != stop) {
text.setSpan(SELECTION_START, start, start,
Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
@@ -357,6 +359,42 @@ public class Selection {
return true;
}
+ /** {@hide} */
+ public static interface PositionIterator {
+ public static final int DONE = BreakIterator.DONE;
+
+ public int preceding(int position);
+ public int following(int position);
+ }
+
+ /** {@hide} */
+ public static boolean moveToPreceding(
+ Spannable text, PositionIterator iter, boolean extendSelection) {
+ final int offset = iter.preceding(getSelectionEnd(text));
+ if (offset != PositionIterator.DONE) {
+ if (extendSelection) {
+ extendSelection(text, offset);
+ } else {
+ setSelection(text, offset);
+ }
+ }
+ return true;
+ }
+
+ /** {@hide} */
+ public static boolean moveToFollowing(
+ Spannable text, PositionIterator iter, boolean extendSelection) {
+ final int offset = iter.following(getSelectionEnd(text));
+ if (offset != PositionIterator.DONE) {
+ if (extendSelection) {
+ extendSelection(text, offset);
+ } else {
+ setSelection(text, offset);
+ }
+ }
+ return true;
+ }
+
private static int findEdge(Spannable text, Layout layout, int dir) {
int pt = getSelectionEnd(text);
int line = layout.getLineForOffset(pt);
@@ -419,7 +457,7 @@ public class Selection {
private static final class START implements NoCopySpan { }
private static final class END implements NoCopySpan { }
-
+
/*
* Public constants
*/
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index ea5cdfe..6b2d8e4 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1170,6 +1170,35 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
}
/**
+ * Don't call this yourself -- exists for Paint to use internally.
+ * {@hide}
+ */
+ public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+ float[] advances, int advancesPos, Paint p, int reserved) {
+
+ float ret;
+
+ int contextLen = contextEnd - contextStart;
+ int len = end - start;
+
+ if (end <= mGapStart) {
+ ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+ flags, advances, advancesPos, reserved);
+ } else if (start >= mGapStart) {
+ ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+ contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
+ } else {
+ char[] buf = TextUtils.obtain(contextLen);
+ getChars(contextStart, contextEnd, buf, 0);
+ ret = p.getTextRunAdvances(buf, start - contextStart, len,
+ 0, contextLen, flags, advances, advancesPos, reserved);
+ TextUtils.recycle(buf);
+ }
+
+ return ret;
+ }
+
+ /**
* Returns the next cursor position in the run. This avoids placing the cursor between
* surrogates, between characters that form conjuncts, between base characters and combining
* marks, or within a reordering cluster.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index a826a97..9e48eff 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -235,6 +235,8 @@ public class StaticLayout extends Layout {
} else {
MetricAffectingSpan[] spans =
spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
measured.addStyleRun(paint, spans, spanLen, fm);
}
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 90279d1..1b7f2f3 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -127,12 +127,12 @@ class TextLine {
boolean hasReplacement = false;
if (text instanceof Spanned) {
mSpanned = (Spanned) text;
- hasReplacement = mSpanned.getSpans(start, limit,
- ReplacementSpan.class).length > 0;
+ ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
+ hasReplacement = spans.length > 0;
}
- mCharsValid = hasReplacement || hasTabs ||
- directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
if (mCharsValid) {
if (mChars == null || mChars.length < mLen) {
@@ -147,10 +147,11 @@ class TextLine {
// zero-width characters.
char[] chars = mChars;
for (int i = start, inext; i < limit; i = inext) {
- inext = mSpanned.nextSpanTransition(i, limit,
- ReplacementSpan.class);
- if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
- .length > 0) { // transition into a span
+ inext = mSpanned.nextSpanTransition(i, limit, ReplacementSpan.class);
+ ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
+ if (spans.length > 0) {
+ // transition into a span
chars[i - start] = '\ufffc';
for (int j = i - start + 1, e = inext - start; j < e; ++j) {
chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
@@ -197,7 +198,6 @@ class TextLine {
boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
int segstart = runStart;
- char[] chars = mChars;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
int codept = 0;
Bitmap bm = null;
@@ -629,6 +629,7 @@ class TextLine {
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
mStart + spanLimit, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
@@ -835,6 +836,7 @@ class TextLine {
mlimit = inext < measureLimit ? inext : measureLimit;
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
mStart + mlimit, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
if (spans.length > 0) {
ReplacementSpan replacement = null;
@@ -868,6 +870,7 @@ class TextLine {
CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
mStart + jnext, CharacterStyle.class);
+ spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
wp.set(mPaint);
for (int k = 0; k < spans.length; k++) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index d5010c6..ac5db62 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -37,6 +37,7 @@ import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
@@ -44,6 +45,7 @@ import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;
+import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.regex.Pattern;
@@ -54,7 +56,7 @@ public class TextUtils {
public static void getChars(CharSequence s, int start, int end,
char[] dest, int destoff) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
((String) s).getChars(start, end, dest, destoff);
@@ -75,7 +77,7 @@ public class TextUtils {
}
public static int indexOf(CharSequence s, char ch, int start) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
return ((String) s).indexOf(ch, start);
@@ -84,7 +86,7 @@ public class TextUtils {
}
public static int indexOf(CharSequence s, char ch, int start, int end) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (s instanceof GetChars || c == StringBuffer.class ||
c == StringBuilder.class || c == String.class) {
@@ -125,7 +127,7 @@ public class TextUtils {
}
public static int lastIndexOf(CharSequence s, char ch, int last) {
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (c == String.class)
return ((String) s).lastIndexOf(ch, last);
@@ -142,7 +144,7 @@ public class TextUtils {
int end = last + 1;
- Class c = s.getClass();
+ Class<? extends CharSequence> c = s.getClass();
if (s instanceof GetChars || c == StringBuffer.class ||
c == StringBuilder.class || c == String.class) {
@@ -499,6 +501,7 @@ public class TextUtils {
return new String(buf);
}
+ @Override
public String toString() {
return subSequence(0, length()).toString();
}
@@ -563,6 +566,8 @@ public class TextUtils {
public static final int TEXT_APPEARANCE_SPAN = 17;
/** @hide */
public static final int ANNOTATION = 18;
+ /** @hide */
+ public static final int SUGGESTION_SPAN = 19;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -621,7 +626,7 @@ public class TextUtils {
* Read and return a new CharSequence, possibly with styles,
* from the parcel.
*/
- public CharSequence createFromParcel(Parcel p) {
+ public CharSequence createFromParcel(Parcel p) {
int kind = p.readInt();
if (kind == 1)
@@ -708,6 +713,10 @@ public class TextUtils {
readSpan(p, sp, new Annotation(p));
break;
+ case SUGGESTION_SPAN:
+ readSpan(p, sp, new SuggestionSpan(p));
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
@@ -760,7 +769,7 @@ public class TextUtils {
if (where >= 0)
tb.setSpan(sources[i], where, where + sources[i].length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
for (int i = 0; i < sources.length; i++) {
@@ -1114,7 +1123,6 @@ public class TextUtils {
int remaining = commaCount + 1;
int ok = 0;
- int okRemaining = remaining;
String okFormat = "";
int w = 0;
@@ -1146,7 +1154,6 @@ public class TextUtils {
if (w + moreWid <= avail) {
ok = i + 1;
- okRemaining = remaining;
okFormat = format;
}
}
@@ -1179,6 +1186,7 @@ public class TextUtils {
MetricAffectingSpan.class);
MetricAffectingSpan[] spans = sp.getSpans(
spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
}
}
@@ -1537,6 +1545,56 @@ public class TextUtils {
return false;
}
+ /**
+ * Removes empty spans from the <code>spans</code> array.
+ *
+ * When parsing a Spanned using {@link Spanned#nextSpanTransition(int, int, Class)}, empty spans
+ * will (correctly) create span transitions, and calling getSpans on a slice of text bounded by
+ * one of these transitions will (correctly) include the empty overlapping span.
+ *
+ * However, these empty spans should not be taken into account when layouting or rendering the
+ * string and this method provides a way to filter getSpans' results accordingly.
+ *
+ * @param spans A list of spans retrieved using {@link Spanned#getSpans(int, int, Class)} from
+ * the <code>spanned</code>
+ * @param spanned The Spanned from which spans were extracted
+ * @return A subset of spans where empty spans ({@link Spanned#getSpanStart(Object)} ==
+ * {@link Spanned#getSpanEnd(Object)} have been removed. The initial order is preserved
+ * @hide
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T[] removeEmptySpans(T[] spans, Spanned spanned, Class<T> klass) {
+ T[] copy = null;
+ int count = 0;
+
+ for (int i = 0; i < spans.length; i++) {
+ final T span = spans[i];
+ final int start = spanned.getSpanStart(span);
+ final int end = spanned.getSpanEnd(span);
+
+ if (start == end) {
+ if (copy == null) {
+ copy = (T[]) Array.newInstance(klass, spans.length - 1);
+ System.arraycopy(spans, 0, copy, 0, i);
+ count = i;
+ }
+ } else {
+ if (copy != null) {
+ copy[count] = span;
+ count++;
+ }
+ }
+ }
+
+ if (copy != null) {
+ T[] result = (T[]) Array.newInstance(klass, count);
+ System.arraycopy(copy, 0, result, 0, count);
+ return result;
+ } else {
+ return spans;
+ }
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
}
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index a61ff13..d432dee 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -193,6 +193,20 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
}
}
+ /** {@hide} */
+ @Override
+ protected boolean leftWord(TextView widget, Spannable buffer) {
+ mWordIterator.setCharSequence(buffer);
+ return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
+ }
+
+ /** {@hide} */
+ @Override
+ protected boolean rightWord(TextView widget, Spannable buffer) {
+ mWordIterator.setCharSequence(buffer);
+ return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
+ }
+
@Override
protected boolean home(TextView widget, Spannable buffer) {
return lineStart(widget, buffer);
@@ -205,7 +219,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
- int initialScrollX = -1, initialScrollY = -1;
+ int initialScrollX = -1;
+ int initialScrollY = -1;
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP) {
@@ -220,7 +235,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
boolean cap = isSelecting(buffer);
if (cap) {
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
-
+
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
@@ -308,6 +323,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return sInstance;
}
+ private WordIterator mWordIterator = new WordIterator();
private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 94c6ed0..f554b90 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -164,6 +164,9 @@ public class BaseMovementMethod implements MovementMethod {
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return left(widget, buffer);
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return leftWord(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return lineStart(widget, buffer);
}
@@ -173,6 +176,9 @@ public class BaseMovementMethod implements MovementMethod {
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return right(widget, buffer);
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return rightWord(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return lineEnd(widget, buffer);
}
@@ -217,12 +223,18 @@ public class BaseMovementMethod implements MovementMethod {
case KeyEvent.KEYCODE_MOVE_HOME:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return home(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return top(widget, buffer);
}
break;
case KeyEvent.KEYCODE_MOVE_END:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
return end(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return bottom(widget, buffer);
}
break;
}
@@ -349,6 +361,16 @@ public class BaseMovementMethod implements MovementMethod {
return false;
}
+ /** {@hide} */
+ protected boolean leftWord(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /** {@hide} */
+ protected boolean rightWord(TextView widget, Spannable buffer) {
+ return false;
+ }
+
/**
* Performs a home movement action.
* Moves the cursor or scrolls to the start of the line or to the top of the
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
new file mode 100644
index 0000000..b250414
--- /dev/null
+++ b/core/java/android/text/method/WordIterator.java
@@ -0,0 +1,220 @@
+
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import android.text.CharSequenceIterator;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spanned;
+import android.text.TextWatcher;
+
+import java.text.BreakIterator;
+import java.text.CharacterIterator;
+import java.util.Locale;
+
+/**
+ * Walks through cursor positions at word boundaries. Internally uses
+ * {@link BreakIterator#getWordInstance()}, and caches {@link CharSequence}
+ * for performance reasons.
+ *
+ * Also provides methods to determine word boundaries.
+ * {@hide}
+ */
+public class WordIterator implements Selection.PositionIterator {
+ private CharSequence mCurrent;
+ private boolean mCurrentDirty = false;
+
+ private BreakIterator mIterator;
+
+ /**
+ * Constructs a WordIterator using the default locale.
+ */
+ public WordIterator() {
+ this(Locale.getDefault());
+ }
+
+ /**
+ * Constructs a new WordIterator for the specified locale.
+ * @param locale The locale to be used when analysing the text.
+ */
+ public WordIterator(Locale locale) {
+ mIterator = BreakIterator.getWordInstance(locale);
+ }
+
+ private final TextWatcher mWatcher = new TextWatcher() {
+ /** {@inheritDoc} */
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mCurrentDirty = true;
+ }
+
+ /** {@inheritDoc} */
+ public void afterTextChanged(Editable s) {
+ // ignored
+ }
+ };
+
+ public void setCharSequence(CharSequence incoming) {
+ // When incoming is different object, move listeners to new sequence
+ // and mark as dirty so we reload contents.
+ if (mCurrent != incoming) {
+ if (mCurrent instanceof Editable) {
+ ((Editable) mCurrent).removeSpan(mWatcher);
+ }
+
+ if (incoming instanceof Editable) {
+ ((Editable) incoming).setSpan(
+ mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ mCurrent = incoming;
+ mCurrentDirty = true;
+ }
+
+ if (mCurrentDirty) {
+ final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
+ mIterator.setText(charIterator);
+
+ mCurrentDirty = false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int preceding(int offset) {
+ do {
+ offset = mIterator.preceding(offset);
+ if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) {
+ break;
+ }
+ } while (true);
+
+ return offset;
+ }
+
+ /** {@inheritDoc} */
+ public int following(int offset) {
+ do {
+ offset = mIterator.following(offset);
+ if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) {
+ break;
+ }
+ } while (true);
+
+ return offset;
+ }
+
+ /** If <code>offset</code> is within a word, returns the index of the first character of that
+ * word, otherwise returns BreakIterator.DONE.
+ *
+ * The offsets that are considered to be part of a word are the indexes of its characters,
+ * <i>as well as</i> the index of its last character plus one.
+ * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
+ *
+ * Valid range for offset is [0..textLength] (note the inclusive upper bound).
+ * The returned value is within [0..offset] or BreakIterator.DONE.
+ *
+ * @throws IllegalArgumentException is offset is not valid.
+ */
+ public int getBeginning(int offset) {
+ checkOffsetIsValid(offset);
+
+ if (isOnLetterOrDigit(offset)) {
+ if (mIterator.isBoundary(offset)) {
+ return offset;
+ } else {
+ return mIterator.preceding(offset);
+ }
+ } else {
+ if (isAfterLetterOrDigit(offset)) {
+ return mIterator.preceding(offset);
+ }
+ }
+ return BreakIterator.DONE;
+ }
+
+ /** If <code>offset</code> is within a word, returns the index of the last character of that
+ * word plus one, otherwise returns BreakIterator.DONE.
+ *
+ * The offsets that are considered to be part of a word are the indexes of its characters,
+ * <i>as well as</i> the index of its last character plus one.
+ * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
+ *
+ * Valid range for offset is [0..textLength] (note the inclusive upper bound).
+ * The returned value is within [offset..textLength] or BreakIterator.DONE.
+ *
+ * @throws IllegalArgumentException is offset is not valid.
+ */
+ public int getEnd(int offset) {
+ checkOffsetIsValid(offset);
+
+ if (isAfterLetterOrDigit(offset)) {
+ if (mIterator.isBoundary(offset)) {
+ return offset;
+ } else {
+ return mIterator.following(offset);
+ }
+ } else {
+ if (isOnLetterOrDigit(offset)) {
+ return mIterator.following(offset);
+ }
+ }
+ return BreakIterator.DONE;
+ }
+
+ private boolean isAfterLetterOrDigit(int offset) {
+ if (offset - 1 >= 0) {
+ final char previousChar = mCurrent.charAt(offset - 1);
+ if (Character.isLetterOrDigit(previousChar)) return true;
+ if (offset - 2 >= 0) {
+ final char previousPreviousChar = mCurrent.charAt(offset - 2);
+ if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
+ final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar);
+ return Character.isLetterOrDigit(codePoint);
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isOnLetterOrDigit(int offset) {
+ final int length = mCurrent.length();
+ if (offset < length) {
+ final char currentChar = mCurrent.charAt(offset);
+ if (Character.isLetterOrDigit(currentChar)) return true;
+ if (offset + 1 < length) {
+ final char nextChar = mCurrent.charAt(offset + 1);
+ if (Character.isSurrogatePair(currentChar, nextChar)) {
+ final int codePoint = Character.toCodePoint(currentChar, nextChar);
+ return Character.isLetterOrDigit(codePoint);
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkOffsetIsValid(int offset) {
+ if (offset < 0 || offset > mCurrent.length()) {
+ final String message = "Valid range is [0, " + mCurrent.length() + "]";
+ throw new IllegalArgumentException(message);
+ }
+ }
+}
diff --git a/core/java/android/text/style/SuggestionSpan.aidl b/core/java/android/text/style/SuggestionSpan.aidl
new file mode 100644
index 0000000..3c56cfe
--- /dev/null
+++ b/core/java/android/text/style/SuggestionSpan.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.style;
+
+parcelable SuggestionSpan;
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
new file mode 100644
index 0000000..7083641
--- /dev/null
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.style;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Holds suggestion candidates of words under this span.
+ */
+public class SuggestionSpan implements ParcelableSpan {
+
+ /**
+ * Flag for indicating that the input is verbatim. TextView refers to this flag to determine
+ * how it displays a word with SuggestionSpan.
+ */
+ public static final int FLAG_VERBATIM = 0x0001;
+
+ private static final int SUGGESTIONS_MAX_SIZE = 5;
+
+ /*
+ * TODO: Needs to check the validity and add a feature that TextView will change
+ * the current IME to the other IME which is specified in SuggestionSpan.
+ * An IME needs to set the span by specifying the target IME and Subtype of SuggestionSpan.
+ * And the current IME might want to specify any IME as the target IME including other IMEs.
+ */
+
+ private final int mFlags;
+ private final String[] mSuggestions;
+ private final String mLocaleString;
+ private final String mOriginalString;
+ /*
+ * TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
+ * and InputMethodSubtype.
+ */
+
+ /**
+ * @param context Context for the application
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ */
+ public SuggestionSpan(Context context, String[] suggestions, int flags) {
+ this(context, null, suggestions, flags, null);
+ }
+
+ /**
+ * @param locale Locale of the suggestions
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ */
+ public SuggestionSpan(Locale locale, String[] suggestions, int flags) {
+ this(null, locale, suggestions, flags, null);
+ }
+
+ /**
+ * @param context Context for the application
+ * @param locale locale Locale of the suggestions
+ * @param suggestions Suggestions for the string under the span
+ * @param flags Additional flags indicating how this span is handled in TextView
+ * @param originalString originalString for suggestions
+ */
+ public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
+ String originalString) {
+ final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
+ mSuggestions = Arrays.copyOf(suggestions, N);
+ mFlags = flags;
+ if (context != null && locale == null) {
+ mLocaleString = context.getResources().getConfiguration().locale.toString();
+ } else {
+ mLocaleString = locale.toString();
+ }
+ mOriginalString = originalString;
+ }
+
+ public SuggestionSpan(Parcel src) {
+ mSuggestions = src.readStringArray();
+ mFlags = src.readInt();
+ mLocaleString = src.readString();
+ mOriginalString = src.readString();
+ }
+
+ /**
+ * @return suggestions
+ */
+ public String[] getSuggestions() {
+ return Arrays.copyOf(mSuggestions, mSuggestions.length);
+ }
+
+ /**
+ * @return locale of suggestions
+ */
+ public String getLocale() {
+ return mLocaleString;
+ }
+
+ /**
+ * @return original string of suggestions
+ */
+ public String getOriginalString() {
+ return mOriginalString;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringArray(mSuggestions);
+ dest.writeInt(mFlags);
+ dest.writeString(mLocaleString);
+ dest.writeString(mOriginalString);
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return TextUtils.SUGGESTION_SPAN;
+ }
+
+ public static final Parcelable.Creator<SuggestionSpan> CREATOR =
+ new Parcelable.Creator<SuggestionSpan>() {
+ @Override
+ public SuggestionSpan createFromParcel(Parcel source) {
+ return new SuggestionSpan(source);
+ }
+
+ @Override
+ public SuggestionSpan[] newArray(int size) {
+ return new SuggestionSpan[size];
+ }
+ };
+}
diff --git a/core/java/android/util/Config.java b/core/java/android/util/Config.java
deleted file mode 100644
index becb882..0000000
--- a/core/java/android/util/Config.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * Build configuration. The constants in this class vary depending
- * on release vs. debug build.
- * {@more}
- */
-public final class Config {
- /** @hide */ public Config() {}
-
- /**
- * If this is a debug build, this field will be true.
- */
- public static final boolean DEBUG = ConfigBuildFlags.DEBUG;
-
- /*
- * Deprecated fields
- * TODO: Remove platform references to these and @hide them.
- */
-
- /**
- * @deprecated Use {@link #DEBUG} instead.
- */
- @Deprecated
- public static final boolean RELEASE = !DEBUG;
-
- /**
- * @deprecated Always false.
- */
- @Deprecated
- public static final boolean PROFILE = false;
-
- /**
- * @deprecated Always false.
- */
- @Deprecated
- public static final boolean LOGV = false;
-
- /**
- * @deprecated Always true.
- */
- @Deprecated
- public static final boolean LOGD = true;
-}
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
index 8f44895..132b595 100644
--- a/core/java/android/util/JsonReader.java
+++ b/core/java/android/util/JsonReader.java
@@ -16,12 +16,13 @@
package android.util;
+import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
-import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
+import libcore.internal.StringPool;
/**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
@@ -86,7 +87,11 @@ import java.util.List;
*
* public List<Message> readJsonStream(InputStream in) throws IOException {
* JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
- * return readMessagesArray(reader);
+ * try {
+ * return readMessagesArray(reader);
+ * } finally {
+ * reader.close();
+ * }
* }
*
* public List<Message> readMessagesArray(JsonReader reader) throws IOException {
@@ -173,6 +178,8 @@ public final class JsonReader implements Closeable {
private static final String TRUE = "true";
private static final String FALSE = "false";
+ private final StringPool stringPool = new StringPool();
+
/** The input JSON. */
private final Reader in;
@@ -832,7 +839,7 @@ public final class JsonReader implements Closeable {
if (skipping) {
return "skipped!";
} else if (builder == null) {
- return new String(buffer, start, pos - start - 1);
+ return stringPool.get(buffer, start, pos - start - 1);
} else {
builder.append(buffer, start, pos - start - 1);
return builder.toString();
@@ -930,7 +937,7 @@ public final class JsonReader implements Closeable {
} else if (skipping) {
result = "skipped!";
} else if (builder == null) {
- result = new String(buffer, pos, i);
+ result = stringPool.get(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
result = builder.toString();
@@ -964,7 +971,7 @@ public final class JsonReader implements Closeable {
if (pos + 4 > limit && !fillBuffer(4)) {
throw syntaxError("Unterminated escape sequence");
}
- String hex = new String(buffer, pos, 4);
+ String hex = stringPool.get(buffer, pos, 4);
pos += 4;
return (char) Integer.parseInt(hex, 16);
@@ -1036,7 +1043,7 @@ public final class JsonReader implements Closeable {
value = FALSE;
return JsonToken.BOOLEAN;
} else {
- value = new String(buffer, valuePos, valueLength);
+ value = stringPool.get(buffer, valuePos, valueLength);
return decodeNumber(buffer, valuePos, valueLength);
}
}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 834dac3..5540000 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -304,7 +304,8 @@ public class LruCache<K, V> {
}
/**
- * Returns the number of times {@link #get} returned a value.
+ * Returns the number of times {@link #get} returned a value that was
+ * already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9042505..93299eb 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,7 @@ package android.util;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import org.apache.harmony.luni.internal.util.ZoneInfoDB;
+import libcore.util.ZoneInfoDB;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index b0c33e5..041e8a8 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -16,23 +16,21 @@
package android.util;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import org.apache.harmony.xml.ExpatReader;
+import org.kxml2.io.KXmlParser;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.harmony.xml.ExpatPullParser;
-import org.apache.harmony.xml.ExpatReader;
+import org.xmlpull.v1.XmlSerializer;
/**
* XML utility methods.
@@ -46,7 +44,7 @@ public class Xml {
* @see <a href="http://xmlpull.org/v1/doc/features.html#relaxed">
* specification</a>
*/
- public static String FEATURE_RELAXED = ExpatPullParser.FEATURE_RELAXED;
+ public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";
/**
* Parses the given xml string and fires events on the given SAX handler.
@@ -57,8 +55,7 @@ public class Xml {
XMLReader reader = new ExpatReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(new StringReader(xml)));
- }
- catch (IOException e) {
+ } catch (IOException e) {
throw new AssertionError(e);
}
}
@@ -88,16 +85,17 @@ public class Xml {
}
/**
- * Creates a new pull parser with namespace support.
- *
- * <p><b>Note:</b> This is actually slower than the SAX parser, and it's not
- * fully implemented. If you need a fast, mostly implemented pull parser,
- * use this. If you need a complete implementation, use KXML.
+ * Returns a new pull parser with namespace support.
*/
public static XmlPullParser newPullParser() {
- ExpatPullParser parser = new ExpatPullParser();
- parser.setNamespaceProcessingEnabled(true);
- return parser;
+ try {
+ KXmlParser parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
}
/**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 126f409..8e839c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,10 +16,15 @@
package android.view;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
import android.util.DisplayMetrics;
+import android.util.Slog;
-public class Display
-{
+public class Display {
/**
* Specify the default Display
*/
@@ -35,10 +40,10 @@ public class Display
Display(int display) {
// initalize the statics when this class is first instansiated. This is
// done here instead of in the static block because Zygote
- synchronized (mStaticInit) {
- if (!mInitialized) {
+ synchronized (sStaticInit) {
+ if (!sInitialized) {
nativeClassInit();
- mInitialized = true;
+ sInitialized = true;
}
}
mDisplay = display;
@@ -60,29 +65,92 @@ public class Display
native static int getDisplayCount();
/**
- * Returns the raw width of the display, in pixels. Note that this
+ * Returns the raw size of the display, in pixels. Note that this
* should <em>not</em> generally be used for computing layouts, since
* a device will typically have screen decoration (such as a status bar)
* along the edges of the display that reduce the amount of application
* space available from the raw size returned here. This value is
* adjusted for you based on the current rotation of the display.
*/
- native public int getWidth();
+ public void getSize(Point outSize) {
+ try {
+ IWindowManager wm = getWindowManager();
+ if (wm != null) {
+ wm.getDisplaySize(outSize);
+ } else {
+ // This is just for boot-strapping, initializing the
+ // system process before the window manager is up.
+ outSize.y = getRealHeight();
+ }
+ } catch (RemoteException e) {
+ Slog.w("Display", "Unable to get display size", e);
+ }
+ }
/**
- * Returns the raw height of the display, in pixels. Note that this
- * should <em>not</em> generally be used for computing layouts, since
- * a device will typically have screen decoration (such as a status bar)
- * along the edges of the display that reduce the amount of application
- * space available from the raw size returned here. This value is
- * adjusted for you based on the current rotation of the display.
+ * This is just easier for some parts of the framework.
*/
- native public int getHeight();
+ public void getRectSize(Rect outSize) {
+ synchronized (mTmpPoint) {
+ getSize(mTmpPoint);
+ outSize.set(0, 0, mTmpPoint.x, mTmpPoint.y);
+ }
+ }
- /** @hide special for when we are faking the screen size. */
+ /**
+ * Return the maximum screen size dimension that will happen. This is
+ * mostly for wallpapers.
+ * @hide
+ */
+ public int getMaximumSizeDimension() {
+ try {
+ IWindowManager wm = getWindowManager();
+ return wm.getMaximumSizeDimension();
+ } catch (RemoteException e) {
+ Slog.w("Display", "Unable to get display maximum size dimension", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #getSize(Point)} instead.
+ */
+ @Deprecated
+ public int getWidth() {
+ synchronized (mTmpPoint) {
+ long now = SystemClock.uptimeMillis();
+ if (now > (mLastGetTime+20)) {
+ getSize(mTmpPoint);
+ mLastGetTime = now;
+ }
+ return mTmpPoint.x;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #getSize(Point)} instead.
+ */
+ @Deprecated
+ public int getHeight() {
+ synchronized (mTmpPoint) {
+ long now = SystemClock.uptimeMillis();
+ if (now > (mLastGetTime+20)) {
+ getSize(mTmpPoint);
+ mLastGetTime = now;
+ }
+ return mTmpPoint.y;
+ }
+ }
+
+ /** @hide Returns the actual screen size, not including any decor. */
native public int getRealWidth();
- /** @hide special for when we are faking the screen size. */
+ /** @hide Returns the actual screen size, not including any decor. */
native public int getRealHeight();
+
+ /** @hide special for when we are faking the screen size. */
+ native public int getRawWidth();
+ /** @hide special for when we are faking the screen size. */
+ native public int getRawHeight();
/**
* Returns the rotation of the screen from its "natural" orientation.
@@ -132,8 +200,27 @@ public class Display
* @param outMetrics
*/
public void getMetrics(DisplayMetrics outMetrics) {
- outMetrics.widthPixels = getWidth();
- outMetrics.heightPixels = getHeight();
+ synchronized (mTmpPoint) {
+ getSize(mTmpPoint);
+ outMetrics.widthPixels = mTmpPoint.x;
+ outMetrics.heightPixels = mTmpPoint.y;
+ }
+ getNonSizeMetrics(outMetrics);
+ }
+
+ /**
+ * Initialize a DisplayMetrics object from this display's data.
+ *
+ * @param outMetrics
+ * @hide
+ */
+ public void getRealMetrics(DisplayMetrics outMetrics) {
+ outMetrics.widthPixels = getRealWidth();
+ outMetrics.heightPixels = getRealHeight();
+ getNonSizeMetrics(outMetrics);
+ }
+
+ private void getNonSizeMetrics(DisplayMetrics outMetrics) {
outMetrics.density = mDensity;
outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f);
outMetrics.scaledDensity= outMetrics.density;
@@ -141,6 +228,16 @@ public class Display
outMetrics.ydpi = mDpiY;
}
+ static IWindowManager getWindowManager() {
+ synchronized (sStaticInit) {
+ if (sWindowManager == null) {
+ sWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ }
+ return sWindowManager;
+ }
+ }
+
/*
* We use a class initializer to allow the native code to cache some
* field offsets.
@@ -157,8 +254,12 @@ public class Display
private float mDpiX;
private float mDpiY;
- private static final Object mStaticInit = new Object();
- private static boolean mInitialized = false;
+ private final Point mTmpPoint = new Point();
+ private float mLastGetTime;
+
+ private static final Object sStaticInit = new Object();
+ private static boolean sInitialized = false;
+ private static IWindowManager sWindowManager;
/**
* Returns a display object which uses the metric's width/height instead.
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 14f2e9d..b8c5c2a 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -39,6 +39,12 @@ import android.text.TextUtils;
* An implementation of Canvas on top of OpenGL ES 2.0.
*/
class GLES20Canvas extends HardwareCanvas {
+ // Must match modifiers used in the JNI layer
+ private static final int MODIFIER_NONE = 0;
+ private static final int MODIFIER_SHADOW = 1;
+ private static final int MODIFIER_SHADER = 2;
+ private static final int MODIFIER_COLOR_FILTER = 4;
+
private final boolean mOpaque;
private int mRenderer;
@@ -259,10 +265,10 @@ class GLES20Canvas extends HardwareCanvas {
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
final GLES20Layer glLayer = (GLES20Layer) layer;
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
@@ -455,10 +461,10 @@ class GLES20Canvas extends HardwareCanvas {
public int saveLayer(float left, float top, float right, float bottom, Paint paint,
int saveFlags) {
if (left < right && top < bottom) {
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
int count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
return count;
}
return save(saveFlags);
@@ -527,10 +533,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle,
useCenter, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawArc(int renderer, float left, float top,
@@ -545,11 +551,11 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
// Shaders are ignored when drawing patches
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks,
@@ -558,10 +564,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(
@@ -570,11 +576,11 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
matrix.native_instance, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff,
@@ -583,7 +589,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
int left, top, right, bottom;
@@ -600,17 +606,17 @@ class GLES20Canvas extends HardwareCanvas {
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
@Override
public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, src.left, src.top, src.right,
src.bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer,
@@ -621,13 +627,13 @@ class GLES20Canvas extends HardwareCanvas {
public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
int width, int height, boolean hasAlpha, Paint paint) {
// Shaders are ignored when drawing bitmaps
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmap(mRenderer, b.mNativeBitmap, b.mBuffer, x, y, nativePaint);
b.recycle();
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
}
@Override
@@ -655,11 +661,11 @@ class GLES20Canvas extends HardwareCanvas {
colors = null;
colorOffset = 0;
- boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
final int nativePaint = paint == null ? 0 : paint.mNativePaint;
nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight,
verts, vertOffset, colors, colorOffset, nativePaint);
- if (hasColorFilter) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer,
@@ -668,9 +674,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawCircle(int renderer, float cx, float cy,
@@ -702,9 +708,9 @@ class GLES20Canvas extends HardwareCanvas {
if ((offset | count) < 0 || offset + count > pts.length) {
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawLines(int renderer, float[] points,
@@ -717,9 +723,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawOval(RectF oval, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawOval(int renderer, float left, float top,
@@ -734,7 +740,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPath(Path path, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
if (path.isSimplePath) {
if (path.rects != null) {
nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
@@ -742,7 +748,7 @@ class GLES20Canvas extends HardwareCanvas {
} else {
nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
}
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawPath(int renderer, int path, int paint);
@@ -767,19 +773,24 @@ class GLES20Canvas extends HardwareCanvas {
public void drawPoint(float x, float y, Paint paint) {
mPoint[0] = x;
mPoint[1] = y;
- drawPoints(mPoint, 0, 1, paint);
+ drawPoints(mPoint, 0, 2, paint);
}
@Override
- public void drawPoints(float[] pts, int offset, int count, Paint paint) {
- // TODO: Implement
+ public void drawPoints(float[] pts, Paint paint) {
+ drawPoints(pts, 0, pts.length, paint);
}
@Override
- public void drawPoints(float[] pts, Paint paint) {
- drawPoints(pts, 0, pts.length / 2, paint);
+ public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+ int modifiers = setupModifiers(paint);
+ nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
+ private static native void nDrawPoints(int renderer, float[] points,
+ int offset, int count, int paint);
+
@Override
public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
// TODO: Implement
@@ -792,9 +803,9 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawRect(int renderer, float left, float top,
@@ -817,10 +828,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
rx, ry, paint.mNativePaint);
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
private static native void nDrawRoundRect(int renderer, float left, float top,
@@ -832,11 +843,11 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -845,7 +856,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
@@ -862,7 +873,7 @@ class GLES20Canvas extends HardwareCanvas {
TemporaryBuffer.recycle(buf);
}
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -872,11 +883,11 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -885,12 +896,12 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawText(String text, float x, float y, Paint paint) {
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -915,12 +926,12 @@ class GLES20Canvas extends HardwareCanvas {
throw new IllegalArgumentException("Unknown direction: " + dir);
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
paint.mNativePaint);
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -934,7 +945,7 @@ class GLES20Canvas extends HardwareCanvas {
throw new IndexOutOfBoundsException();
}
- boolean hasModifier = setupModifiers(paint);
+ int modifiers = setupModifiers(paint);
try {
int flags = dir == 0 ? 0 : 1;
if (text instanceof String || text instanceof SpannedString ||
@@ -954,7 +965,7 @@ class GLES20Canvas extends HardwareCanvas {
TemporaryBuffer.recycle(buf);
}
} finally {
- if (hasModifier) nResetModifiers(mRenderer);
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
@@ -968,43 +979,57 @@ class GLES20Canvas extends HardwareCanvas {
// TODO: Implement
}
- private boolean setupModifiers(Paint paint) {
- boolean hasModifier = false;
+ private int setupModifiers(Bitmap b, Paint paint) {
+ if (b.getConfig() == Bitmap.Config.ALPHA_8) {
+ return setupModifiers(paint);
+ }
+
+ final ColorFilter filter = paint.getColorFilter();
+ if (filter != null) {
+ nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+ return MODIFIER_COLOR_FILTER;
+ }
+
+ return MODIFIER_NONE;
+ }
+
+ private int setupModifiers(Paint paint) {
+ int modifiers = MODIFIER_NONE;
if (paint.hasShadow) {
nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
paint.shadowColor);
- hasModifier = true;
+ modifiers |= MODIFIER_SHADOW;
}
final Shader shader = paint.getShader();
if (shader != null) {
nSetupShader(mRenderer, shader.native_shader);
- hasModifier = true;
+ modifiers |= MODIFIER_SHADER;
}
final ColorFilter filter = paint.getColorFilter();
if (filter != null) {
nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- hasModifier = true;
+ modifiers |= MODIFIER_COLOR_FILTER;
}
- return hasModifier;
+ return modifiers;
}
- private boolean setupColorFilter(Paint paint) {
+ private int setupColorFilter(Paint paint) {
final ColorFilter filter = paint.getColorFilter();
if (filter != null) {
nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- return true;
+ return MODIFIER_COLOR_FILTER;
}
- return false;
+ return MODIFIER_NONE;
}
-
+
private static native void nSetupShader(int renderer, int shader);
private static native void nSetupColorFilter(int renderer, int colorFilter);
private static native void nSetupShadow(int renderer, float radius,
float dx, float dy, int color);
- private static native void nResetModifiers(int renderer);
+ private static native void nResetModifiers(int renderer, int modifiers);
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index c1e1049..1ccc66f 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -245,6 +245,13 @@ public class GestureDetector {
*/
private VelocityTracker mVelocityTracker;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
private class GestureHandler extends Handler {
GestureHandler() {
super();
@@ -443,6 +450,10 @@ public class GestureDetector {
* else false.
*/
public boolean onTouchEvent(MotionEvent ev) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
+ }
+
final int action = ev.getAction();
final float y = ev.getY();
final float x = ev.getX();
@@ -579,8 +590,14 @@ public class GestureDetector {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
+
case MotionEvent.ACTION_CANCEL:
cancel();
+ break;
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 8584bf2..66f37f2 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -20,7 +20,7 @@ package android.view;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.os.SystemClock;
+import android.os.*;
import android.util.EventLog;
import android.util.Log;
@@ -256,6 +256,7 @@ public abstract class HardwareRenderer {
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
+ // These values are not exposed in our EGL APIs
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private static final int EGL_SURFACE_TYPE = 0x3033;
private static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
@@ -290,7 +291,7 @@ public abstract class HardwareRenderer {
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
- final String dirtyProperty = System.getProperty(RENDER_DIRTY_REGIONS_PROPERTY, "true");
+ final String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
//noinspection PointlessBooleanExpression,ConstantConditions
mDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
}
@@ -644,8 +645,21 @@ public abstract class HardwareRenderer {
}
attachInfo.mIgnoreDirtyState = false;
-
+
+ final long swapBuffersStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ swapBuffersStartTime = System.nanoTime();
+ }
+
sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(LOG_TAG, "Latency: Spent "
+ + ((now - swapBuffersStartTime) * 0.000001f)
+ + "ms waiting for eglSwapBuffers()");
+ }
+
checkEglErrors();
}
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index dd8242a..0be02a6 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -21,6 +21,7 @@ import com.android.internal.view.IInputMethodClient;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.view.IApplicationToken;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
@@ -52,6 +53,9 @@ interface IWindowManager
in IInputContext inputContext);
boolean inputMethodClientHasFocus(IInputMethodClient client);
+ void getDisplaySize(out Point size);
+ int getMaximumSizeDimension();
+
// These can only be called when injecting events to your own window,
// or by holding the INJECT_EVENTS permission. These methods may block
// until pending input events are finished being dispatched even when 'sync' is false.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 98d4eb9..8cb68f9 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -290,7 +290,7 @@ public final class InputDevice implements Parcelable {
* @return The input device or null if not found.
*/
public static InputDevice getDevice(int id) {
- IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ IWindowManager wm = Display.getWindowManager();
try {
return wm.getInputDevice(id);
} catch (RemoteException ex) {
@@ -304,7 +304,7 @@ public final class InputDevice implements Parcelable {
* @return The input device ids.
*/
public static int[] getDeviceIds() {
- IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ IWindowManager wm = Display.getWindowManager();
try {
return wm.getInputDeviceIds();
} catch (RemoteException ex) {
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index f6aeb39..01ddcc9 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -67,6 +67,52 @@ public abstract class InputEvent implements Parcelable {
*/
public abstract void setSource(int source);
+ /**
+ * Copies the event.
+ *
+ * @return A deep copy of the event.
+ * @hide
+ */
+ public abstract InputEvent copy();
+
+ /**
+ * Recycles the event.
+ * This method should only be used by the system since applications do not
+ * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent}
+ * objects are fine. See {@link KeyEvent#recycle()} for details.
+ * @hide
+ */
+ public abstract void recycle();
+
+ /**
+ * Gets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @return True if this event is tainted.
+ * @hide
+ */
+ public abstract boolean isTainted();
+
+ /**
+ * Sets a private flag that indicates when the system has detected that this input event
+ * may be inconsistent with respect to the sequence of previously delivered input events,
+ * such as when a key up event is sent but the key was not down or when a pointer
+ * move event is sent but the pointer is not down.
+ *
+ * @param tainted True if this event is tainted.
+ * @hide
+ */
+ public abstract void setTainted(boolean tainted);
+
+ /**
+ * Returns the time (in ns) when this specific event was generated.
+ * The value is in nanosecond precision but it may not have nanosecond accuracy.
+ * @hide
+ */
+ public abstract long getEventTimeNano();
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
new file mode 100644
index 0000000..e14b975
--- /dev/null
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * Checks whether a sequence of input events is self-consistent.
+ * Logs a description of each problem detected.
+ * <p>
+ * When a problem is detected, the event is tainted. This mechanism prevents the same
+ * error from being reported multiple times.
+ * </p>
+ *
+ * @hide
+ */
+public final class InputEventConsistencyVerifier {
+ private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
+
+ // The number of recent events to log when a problem is detected.
+ // Can be set to 0 to disable logging recent events but the runtime overhead of
+ // this feature is negligible on current hardware.
+ private static final int RECENT_EVENTS_TO_LOG = 5;
+
+ // The object to which the verifier is attached.
+ private final Object mCaller;
+
+ // Consistency verifier flags.
+ private final int mFlags;
+
+ // Tag for logging which a client can set to help distinguish the output
+ // from different verifiers since several can be active at the same time.
+ // If not provided defaults to the simple class name.
+ private final String mLogTag;
+
+ // The most recently checked event and the nesting level at which it was checked.
+ // This is only set when the verifier is called from a nesting level greater than 0
+ // so that the verifier can detect when it has been asked to verify the same event twice.
+ // It does not make sense to examine the contents of the last event since it may have
+ // been recycled.
+ private InputEvent mLastEvent;
+ private int mLastNestingLevel;
+
+ // Copy of the most recent events.
+ private InputEvent[] mRecentEvents;
+ private boolean[] mRecentEventsUnhandled;
+ private int mMostRecentEventIndex;
+
+ // Current event and its type.
+ private InputEvent mCurrentEvent;
+ private String mCurrentEventType;
+
+ // Linked list of key state objects.
+ private KeyState mKeyStateList;
+
+ // Current state of the trackball.
+ private boolean mTrackballDown;
+ private boolean mTrackballUnhandled;
+
+ // Bitfield of pointer ids that are currently down.
+ // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ private int mTouchEventStreamPointers;
+
+ // The device id and source of the current stream of touch events.
+ private int mTouchEventStreamDeviceId = -1;
+ private int mTouchEventStreamSource;
+
+ // Set to true when we discover that the touch event stream is inconsistent.
+ // Reset on down or cancel.
+ private boolean mTouchEventStreamIsTainted;
+
+ // Set to true if the touch event stream is partially unhandled.
+ private boolean mTouchEventStreamUnhandled;
+
+ // Set to true if we received hover enter.
+ private boolean mHoverEntered;
+
+ // The current violation message.
+ private StringBuilder mViolationMessage;
+
+ /**
+ * Indicates that the verifier is intended to act on raw device input event streams.
+ * Disables certain checks for invariants that are established by the input dispatcher
+ * itself as it delivers input events, such as key repeating behavior.
+ */
+ public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags) {
+ this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
+ }
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ * @param logTag Tag for logging. If null defaults to the short class name.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
+ this.mCaller = caller;
+ this.mFlags = flags;
+ this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
+ }
+
+ /**
+ * Determines whether the instrumentation should be enabled.
+ * @return True if it should be enabled.
+ */
+ public static boolean isInstrumentationEnabled() {
+ return IS_ENG_BUILD;
+ }
+
+ /**
+ * Resets the state of the input event consistency verifier.
+ */
+ public void reset() {
+ mLastEvent = null;
+ mLastNestingLevel = 0;
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mHoverEntered = false;
+
+ while (mKeyStateList != null) {
+ final KeyState state = mKeyStateList;
+ mKeyStateList = state.next;
+ state.recycle();
+ }
+ }
+
+ /**
+ * Checks an arbitrary input event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onInputEvent(InputEvent event, int nestingLevel) {
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ onKeyEvent(keyEvent, nestingLevel);
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ onTouchEvent(motionEvent, nestingLevel);
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ onTrackballEvent(motionEvent, nestingLevel);
+ } else {
+ onGenericMotionEvent(motionEvent, nestingLevel);
+ }
+ }
+ }
+
+ /**
+ * Checks a key event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onKeyEvent(KeyEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "KeyEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+ final int keyCode = event.getKeyCode();
+ switch (action) {
+ case KeyEvent.ACTION_DOWN: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ // If the key is already down, ensure it is a repeat.
+ // We don't perform this check when processing raw device input
+ // because the input dispatcher itself is responsible for setting
+ // the key repeat count before it delivers input events.
+ if (state.unhandled) {
+ state.unhandled = false;
+ } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+ && event.getRepeatCount() == 0) {
+ problem("ACTION_DOWN but key is already down and this event "
+ + "is not a key repeat.");
+ }
+ } else {
+ addKeyState(deviceId, source, keyCode);
+ }
+ break;
+ }
+ case KeyEvent.ACTION_UP: {
+ KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
+ if (state == null) {
+ problem("ACTION_UP but key was not down.");
+ } else {
+ state.recycle();
+ }
+ break;
+ }
+ case KeyEvent.ACTION_MULTIPLE:
+ break;
+ default:
+ problem("Invalid action " + KeyEvent.actionToString(action)
+ + " for key event.");
+ break;
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a trackball event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTrackballEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "TrackballEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTrackballDown && !mTrackballUnhandled) {
+ problem("ACTION_DOWN but trackball is already down.");
+ } else {
+ mTrackballDown = true;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!mTrackballDown) {
+ problem("ACTION_UP but trackball is not down.");
+ } else {
+ mTrackballDown = false;
+ mTrackballUnhandled = false;
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for trackball event.");
+ break;
+ }
+
+ if (mTrackballDown && event.getPressure() <= 0) {
+ problem("Trackball is down but pressure is not greater than 0.");
+ } else if (!mTrackballDown && event.getPressure() != 0) {
+ problem("Trackball is up but pressure is not equal to 0.");
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_TRACKBALL.");
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a touch event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onTouchEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "TouchEvent")) {
+ return;
+ }
+
+ final int action = event.getAction();
+ final boolean newStream = action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_CANCEL;
+ if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
+ if (newStream) {
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
+ } else {
+ finishEvent(mTouchEventStreamIsTainted);
+ return;
+ }
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+
+ if (!newStream && mTouchEventStreamDeviceId != -1
+ && (mTouchEventStreamDeviceId != deviceId
+ || mTouchEventStreamSource != source)) {
+ problem("Touch event stream contains events from multiple sources: "
+ + "previous device id " + mTouchEventStreamDeviceId
+ + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
+ + ", new device id " + deviceId
+ + ", new source " + Integer.toHexString(source));
+ }
+ mTouchEventStreamDeviceId = deviceId;
+ mTouchEventStreamSource = source;
+
+ final int pointerCount = event.getPointerCount();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_DOWN but pointers are already down. "
+ + "Probably missing ACTION_UP from previous gesture.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 1 << event.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_UP:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ final int expectedPointerCount =
+ Integer.bitCount(mTouchEventStreamPointers);
+ if (pointerCount != expectedPointerCount) {
+ problem("ACTION_MOVE contained " + pointerCount
+ + " pointers but there are currently "
+ + expectedPointerCount + " pointers down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ mTouchEventStreamPointers = 0;
+ mTouchEventStreamIsTainted = false;
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ if (mTouchEventStreamPointers != 0) {
+ problem("ACTION_OUTSIDE but pointers are still down.");
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ mTouchEventStreamIsTainted = false;
+ break;
+ default: {
+ final int actionMasked = event.getActionMasked();
+ final int actionIndex = event.getActionIndex();
+ if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mTouchEventStreamPointers == 0) {
+ problem("ACTION_POINTER_DOWN but no other pointers were down.");
+ mTouchEventStreamIsTainted = true;
+ }
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_DOWN index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) != 0) {
+ problem("ACTION_POINTER_DOWN specified pointer id " + id
+ + " which is already down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers |= idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ if (actionIndex < 0 || actionIndex >= pointerCount) {
+ problem("ACTION_POINTER_UP index is " + actionIndex
+ + " but the pointer count is " + pointerCount + ".");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ final int id = event.getPointerId(actionIndex);
+ final int idBit = 1 << id;
+ if ((mTouchEventStreamPointers & idBit) == 0) {
+ problem("ACTION_POINTER_UP specified pointer id " + id
+ + " which is not currently down.");
+ mTouchEventStreamIsTainted = true;
+ } else {
+ mTouchEventStreamPointers &= ~idBit;
+ }
+ }
+ ensureHistorySizeIsZeroForThisAction(event);
+ } else {
+ problem("Invalid action " + MotionEvent.actionToString(action)
+ + " for touch event.");
+ }
+ break;
+ }
+ }
+ } else {
+ problem("Source was not SOURCE_CLASS_POINTER.");
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Checks a generic motion event.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
+ if (!startEvent(event, nestingLevel, "GenericMotionEvent")) {
+ return;
+ }
+
+ try {
+ ensureMetaStateIsNormalized(event.getMetaState());
+
+ final int action = event.getAction();
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ ensurePointerCountIsOneForThisAction(event);
+ mHoverEntered = true;
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ ensurePointerCountIsOneForThisAction(event);
+ if (!mHoverEntered) {
+ problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
+ }
+ mHoverEntered = false;
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ ensureHistorySizeIsZeroForThisAction(event);
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action for generic pointer event.");
+ break;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ ensurePointerCountIsOneForThisAction(event);
+ break;
+ default:
+ problem("Invalid action for generic joystick event.");
+ break;
+ }
+ }
+ } finally {
+ finishEvent(false);
+ }
+ }
+
+ /**
+ * Notifies the verifier that a given event was unhandled and the rest of the
+ * trace for the event should be ignored.
+ * This method should only be called if the event was previously checked by
+ * the consistency verifier using {@link #onInputEvent} and other methods.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+ if (nestingLevel != mLastNestingLevel) {
+ return;
+ }
+
+ if (mRecentEventsUnhandled != null) {
+ mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+ }
+
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ final int deviceId = keyEvent.getDeviceId();
+ final int source = keyEvent.getSource();
+ final int keyCode = keyEvent.getKeyCode();
+ final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ state.unhandled = true;
+ }
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ mTouchEventStreamUnhandled = true;
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (mTrackballDown) {
+ mTrackballUnhandled = true;
+ }
+ }
+ }
+ }
+
+ private void ensureMetaStateIsNormalized(int metaState) {
+ final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
+ if (normalizedMetaState != metaState) {
+ problem(String.format("Metastate not normalized. Was 0x%08x but expected 0x%08x.",
+ metaState, normalizedMetaState));
+ }
+ }
+
+ private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ if (pointerCount != 1) {
+ problem("Pointer count is " + pointerCount + " but it should always be 1 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
+ final int historySize = event.getHistorySize();
+ if (historySize != 0) {
+ problem("History size is " + historySize + " but it should always be 0 for "
+ + MotionEvent.actionToString(event.getAction()));
+ }
+ }
+
+ private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
+ // Ignore the event if it is already tainted.
+ if (event.isTainted()) {
+ return false;
+ }
+
+ // Ignore the event if we already checked it at a higher nesting level.
+ if (event == mLastEvent && nestingLevel < mLastNestingLevel) {
+ return false;
+ }
+
+ if (nestingLevel > 0) {
+ mLastEvent = event;
+ mLastNestingLevel = nestingLevel;
+ } else {
+ mLastEvent = null;
+ mLastNestingLevel = 0;
+ }
+
+ mCurrentEvent = event;
+ mCurrentEventType = eventType;
+ return true;
+ }
+
+ private void finishEvent(boolean tainted) {
+ if (mViolationMessage != null && mViolationMessage.length() != 0) {
+ mViolationMessage.append("\n in ").append(mCaller);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
+
+ if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
+ mViolationMessage.append("\n -- recent events --");
+ for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
+ final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
+ % RECENT_EVENTS_TO_LOG;
+ final InputEvent event = mRecentEvents[index];
+ if (event == null) {
+ break;
+ }
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
+ }
+ }
+
+ Log.d(mLogTag, mViolationMessage.toString());
+ mViolationMessage.setLength(0);
+ tainted = true;
+ }
+
+ if (tainted) {
+ // Taint the event so that we do not generate additional violations from it
+ // further downstream.
+ mCurrentEvent.setTainted(true);
+ }
+
+ if (RECENT_EVENTS_TO_LOG != 0) {
+ if (mRecentEvents == null) {
+ mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+ mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
+ }
+ final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
+ mMostRecentEventIndex = index;
+ if (mRecentEvents[index] != null) {
+ mRecentEvents[index].recycle();
+ }
+ mRecentEvents[index] = mCurrentEvent.copy();
+ mRecentEventsUnhandled[index] = false;
+ }
+
+ mCurrentEvent = null;
+ mCurrentEventType = null;
+ }
+
+ private static void appendEvent(StringBuilder message, int index,
+ InputEvent event, boolean unhandled) {
+ message.append(index).append(": sent at ").append(event.getEventTimeNano());
+ message.append(", ");
+ if (unhandled) {
+ message.append("(unhandled) ");
+ }
+ message.append(event);
+ }
+
+ private void problem(String message) {
+ if (mViolationMessage == null) {
+ mViolationMessage = new StringBuilder();
+ }
+ if (mViolationMessage.length() == 0) {
+ mViolationMessage.append(mCurrentEventType).append(": ");
+ } else {
+ mViolationMessage.append("\n ");
+ }
+ mViolationMessage.append(message);
+ }
+
+ private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
+ KeyState last = null;
+ KeyState state = mKeyStateList;
+ while (state != null) {
+ if (state.deviceId == deviceId && state.source == source
+ && state.keyCode == keyCode) {
+ if (remove) {
+ if (last != null) {
+ last.next = state.next;
+ } else {
+ mKeyStateList = state.next;
+ }
+ state.next = null;
+ }
+ return state;
+ }
+ last = state;
+ state = state.next;
+ }
+ return null;
+ }
+
+ private void addKeyState(int deviceId, int source, int keyCode) {
+ KeyState state = KeyState.obtain(deviceId, source, keyCode);
+ state.next = mKeyStateList;
+ mKeyStateList = state;
+ }
+
+ private static final class KeyState {
+ private static Object mRecycledListLock = new Object();
+ private static KeyState mRecycledList;
+
+ public KeyState next;
+ public int deviceId;
+ public int source;
+ public int keyCode;
+ public boolean unhandled;
+
+ private KeyState() {
+ }
+
+ public static KeyState obtain(int deviceId, int source, int keyCode) {
+ KeyState state;
+ synchronized (mRecycledListLock) {
+ state = mRecycledList;
+ if (state != null) {
+ mRecycledList = state.next;
+ } else {
+ state = new KeyState();
+ }
+ }
+ state.deviceId = deviceId;
+ state.source = source;
+ state.keyCode = keyCode;
+ state.unhandled = false;
+ return state;
+ }
+
+ public void recycle() {
+ synchronized (mRecycledListLock) {
+ next = mRecycledList;
+ mRecycledList = next;
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 3ff7fcd..885a75f 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -527,7 +527,7 @@ public class KeyCharacterMap {
*/
public static boolean[] deviceHasKeys(int[] keyCodes) {
boolean[] ret = new boolean[keyCodes.length];
- IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ IWindowManager wm = Display.getWindowManager();
try {
wm.hasKeys(keyCodes, ret);
} catch (RemoteException e) {
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 81d5a6e..13d8809 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -566,6 +566,19 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_BUTTON_15 = 202;
/** Key code constant: Generic Game Pad Button #16.*/
public static final int KEYCODE_BUTTON_16 = 203;
+ /** Key code constant: Language Switch key.
+ * Toggles the current input language such as switching between English and Japanese on
+ * a QWERTY keyboard. On some devices, the same function may be performed by
+ * pressing Shift+Spacebar. */
+ public static final int KEYCODE_LANGUAGE_SWITCH = 204;
+ /** Key code constant: Manner Mode key.
+ * Toggles silent or vibrate mode on and off to make the device behave more politely
+ * in certain settings such as on a crowded train. On some devices, the key may only
+ * operate when long-pressed. */
+ public static final int KEYCODE_MANNER_MODE = 205;
+ /** Key code constant: 3D Mode key.
+ * Toggles the display between 2D and 3D mode. */
+ public static final int KEYCODE_3D_MODE = 206;
private static final int LAST_KEYCODE = KEYCODE_BUTTON_16;
@@ -791,6 +804,9 @@ public class KeyEvent extends InputEvent implements Parcelable {
names.append(KEYCODE_BUTTON_14, "KEYCODE_BUTTON_14");
names.append(KEYCODE_BUTTON_15, "KEYCODE_BUTTON_15");
names.append(KEYCODE_BUTTON_16, "KEYCODE_BUTTON_16");
+ names.append(KEYCODE_LANGUAGE_SWITCH, "KEYCODE_LANGUAGE_SWITCH");
+ names.append(KEYCODE_MANNER_MODE, "KEYCODE_MANNER_MODE");
+ names.append(KEYCODE_3D_MODE, "KEYCODE_3D_MODE");
};
// Symbolic names of all metakeys in bit order from least significant to most significant.
@@ -1154,7 +1170,18 @@ public class KeyEvent extends InputEvent implements Parcelable {
* @hide
*/
public static final int FLAG_START_TRACKING = 0x40000000;
-
+
+ /**
+ * Private flag that indicates when the system has detected that this key event
+ * may be inconsistent with respect to the sequence of previously delivered key events,
+ * such as when a key up event is sent but the key was not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
/**
* Returns the maximum keycode.
*/
@@ -1519,6 +1546,33 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
/**
+ * Obtains a (potentially recycled) copy of another key event.
+ *
+ * @hide
+ */
+ public static KeyEvent obtain(KeyEvent other) {
+ KeyEvent ev = obtain();
+ ev.mDownTime = other.mDownTime;
+ ev.mEventTime = other.mEventTime;
+ ev.mAction = other.mAction;
+ ev.mKeyCode = other.mKeyCode;
+ ev.mRepeatCount = other.mRepeatCount;
+ ev.mMetaState = other.mMetaState;
+ ev.mDeviceId = other.mDeviceId;
+ ev.mScanCode = other.mScanCode;
+ ev.mFlags = other.mFlags;
+ ev.mSource = other.mSource;
+ ev.mCharacters = other.mCharacters;
+ return ev;
+ }
+
+ /** @hide */
+ @Override
+ public KeyEvent copy() {
+ return obtain(this);
+ }
+
+ /**
* Recycles a key event.
* Key events should only be recycled if they are owned by the system since user
* code expects them to be essentially immutable, "tracking" notwithstanding.
@@ -1619,7 +1673,19 @@ public class KeyEvent extends InputEvent implements Parcelable {
event.mFlags = flags;
return event;
}
-
+
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ return (mFlags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ mFlags = tainted ? mFlags | FLAG_TAINTED : mFlags & ~FLAG_TAINTED;
+ }
+
/**
* Don't use in new code, instead explicitly check
* {@link #getAction()}.
@@ -1741,12 +1807,33 @@ public class KeyEvent extends InputEvent implements Parcelable {
* @see #META_CAPS_LOCK_ON
* @see #META_NUM_LOCK_ON
* @see #META_SCROLL_LOCK_ON
+ * @see #getModifiers
*/
public final int getMetaState() {
return mMetaState;
}
/**
+ * Returns the state of the modifier keys.
+ * <p>
+ * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+ * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+ * not considered modifier keys. Consequently, this function specifically masks out
+ * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+ * </p><p>
+ * The value returned consists of the meta state (from {@link #getMetaState})
+ * normalized using {@link #normalizeMetaState(int)} and then masked with
+ * {@link #getModifierMetaStateMask} so that only valid modifier bits are retained.
+ * </p>
+ *
+ * @return An integer in which each bit set to 1 represents a pressed modifier key.
+ * @see #getMetaState
+ */
+ public final int getModifiers() {
+ return normalizeMetaState(mMetaState) & META_MODIFIER_MASK;
+ }
+
+ /**
* Returns the flags for this key event.
*
* @see #FLAG_WOKE_HERE
@@ -2253,6 +2340,12 @@ public class KeyEvent extends InputEvent implements Parcelable {
return mEventTime;
}
+ /** @hide */
+ @Override
+ public final long getEventTimeNano() {
+ return mEventTime * 1000000L;
+ }
+
/**
* Renamed to {@link #getDeviceId}.
*
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a17db5d..7611b08 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -172,6 +172,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* recent point, as well as any intermediate points since the last
* hover move event.
* <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
@@ -184,8 +186,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)}
* to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
* The pointer may or may not be down when this event is dispatched.
- * This action is always delivered to the winder under the pointer, which
- * may not be the window currently touched.
+ * <p></p>
+ * This action is always delivered to the window or view under the pointer, which
+ * may not be the window or view currently touched.
* <p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
@@ -195,6 +198,32 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int ACTION_SCROLL = 8;
/**
+ * Constant for {@link #getAction}: The pointer is not down but has entered the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_ENTER = 9;
+
+ /**
+ * Constant for {@link #getAction}: The pointer is not down but has exited the
+ * boundaries of a window or view.
+ * <p>
+ * This action is always delivered to the window or view that was previously under the pointer.
+ * </p><p>
+ * This action is not a touch event so it is delivered to
+ * {@link View#onGenericMotionEvent(MotionEvent)} rather than
+ * {@link View#onTouchEvent(MotionEvent)}.
+ * </p>
+ */
+ public static final int ACTION_HOVER_EXIT = 10;
+
+ /**
* Bits in the action code that represent a pointer index, used with
* {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting
* down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer
@@ -279,6 +308,17 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
/**
+ * Private flag that indicates when the system has detected that this motion event
+ * may be inconsistent with respect to the sequence of previously delivered motion events,
+ * such as when a pointer move event is sent but the pointer is not down.
+ *
+ * @hide
+ * @see #isTainted
+ * @see #setTainted
+ */
+ public static final int FLAG_TAINTED = 0x80000000;
+
+ /**
* Flag indicating the motion event intersected the top edge of the screen.
*/
public static final int EDGE_TOP = 0x00000001;
@@ -1025,6 +1065,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
private static native void nativeSetAction(int nativePtr, int action);
private static native boolean nativeIsTouchEvent(int nativePtr);
private static native int nativeGetFlags(int nativePtr);
+ private static native void nativeSetFlags(int nativePtr, int flags);
private static native int nativeGetEdgeFlags(int nativePtr);
private static native void nativeSetEdgeFlags(int nativePtr, int action);
private static native int nativeGetMetaState(int nativePtr);
@@ -1261,6 +1302,12 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return ev;
}
+ /** @hide */
+ @Override
+ public MotionEvent copy() {
+ return obtain(this);
+ }
+
/**
* Recycle the MotionEvent, to be re-used by a later caller. After calling
* this function you must not ever touch the event again.
@@ -1354,9 +1401,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
/**
* Returns true if this motion event is a touch event.
* <p>
- * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE}
- * or {@link #ACTION_SCROLL} because they are not actually touch events
- * (the pointer is not down).
+ * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
+ * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
+ * because they are not actually touch events (the pointer is not down).
* </p>
* @return True if this motion event is a touch event.
* @hide
@@ -1374,6 +1421,20 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return nativeGetFlags(mNativePtr);
}
+ /** @hide */
+ @Override
+ public final boolean isTainted() {
+ final int flags = getFlags();
+ return (flags & FLAG_TAINTED) != 0;
+ }
+
+ /** @hide */
+ @Override
+ public final void setTainted(boolean tainted) {
+ final int flags = getFlags();
+ nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
+ }
+
/**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
@@ -2313,6 +2374,10 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return "ACTION_HOVER_MOVE";
case ACTION_SCROLL:
return "ACTION_SCROLL";
+ case ACTION_HOVER_ENTER:
+ return "ACTION_HOVER_ENTER";
+ case ACTION_HOVER_EXIT:
+ return "ACTION_HOVER_EXIT";
}
int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
switch (action & ACTION_MASK) {
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
index 391ba1e..cd48a4f 100755
--- a/core/java/android/view/OrientationEventListener.java
+++ b/core/java/android/view/OrientationEventListener.java
@@ -21,7 +21,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.util.Config;
import android.util.Log;
/**
@@ -31,7 +30,7 @@ import android.util.Log;
public abstract class OrientationEventListener {
private static final String TAG = "OrientationEventListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean localLOGV = false;
private int mOrientation = ORIENTATION_UNKNOWN;
private SensorManager mSensorManager;
private boolean mEnabled = false;
diff --git a/core/java/android/view/PointerIcon.aidl b/core/java/android/view/PointerIcon.aidl
new file mode 100644
index 0000000..b09340b
--- /dev/null
+++ b/core/java/android/view/PointerIcon.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable PointerIcon;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
new file mode 100644
index 0000000..bb7ed41
--- /dev/null
+++ b/core/java/android/view/PointerIcon.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.internal.util.XmlUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system styles,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ *
+ * @hide
+ */
+public final class PointerIcon implements Parcelable {
+ private static final String TAG = "PointerIcon";
+
+ /** Style constant: Custom icon with a user-supplied bitmap. */
+ public static final int STYLE_CUSTOM = -1;
+
+ /** Style constant: Null icon. It has no bitmap. */
+ public static final int STYLE_NULL = 0;
+
+ /** Style constant: Arrow icon. (Default mouse pointer) */
+ public static final int STYLE_ARROW = 1000;
+
+ /** {@hide} Style constant: Spot hover icon for touchpads. */
+ public static final int STYLE_SPOT_HOVER = 2000;
+
+ /** {@hide} Style constant: Spot touch icon for touchpads. */
+ public static final int STYLE_SPOT_TOUCH = 2001;
+
+ /** {@hide} Style constant: Spot anchor icon for touchpads. */
+ public static final int STYLE_SPOT_ANCHOR = 2002;
+
+ // OEM private styles should be defined starting at this range to avoid
+ // conflicts with any system styles that may be defined in the future.
+ private static final int STYLE_OEM_FIRST = 10000;
+
+ // The default pointer icon.
+ private static final int STYLE_DEFAULT = STYLE_ARROW;
+
+ private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+
+ private final int mStyle;
+ private int mSystemIconResourceId;
+ private Bitmap mBitmap;
+ private float mHotSpotX;
+ private float mHotSpotY;
+
+ private PointerIcon(int style) {
+ mStyle = style;
+ }
+
+ /**
+ * Gets a special pointer icon that has no bitmap.
+ *
+ * @return The null pointer icon.
+ *
+ * @see #STYLE_NULL
+ */
+ public static PointerIcon getNullIcon() {
+ return gNullIcon;
+ }
+
+ /**
+ * Gets the default pointer icon.
+ *
+ * @param context The context.
+ * @return The default pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getDefaultIcon(Context context) {
+ return getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ /**
+ * Gets a system pointer icon for the given style.
+ * If style is not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param style The pointer icon style.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getSystemIcon(Context context, int style) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (style == STYLE_NULL) {
+ return gNullIcon;
+ }
+
+ int styleIndex = getSystemIconStyleIndex(style);
+ if (styleIndex == 0) {
+ styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
+ }
+
+ TypedArray a = context.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Pointer,
+ com.android.internal.R.attr.pointerStyle, 0);
+ int resourceId = a.getResourceId(styleIndex, -1);
+ a.recycle();
+
+ if (resourceId == -1) {
+ Log.w(TAG, "Missing theme resources for pointer icon style " + style);
+ return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ PointerIcon icon = new PointerIcon(style);
+ if ((resourceId & 0xff000000) == 0x01000000) {
+ icon.mSystemIconResourceId = resourceId;
+ } else {
+ icon.loadResource(context.getResources(), resourceId);
+ }
+ return icon;
+ }
+
+ /**
+ * Creates a custom pointer from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (bitmap == null) {
+ throw new IllegalArgumentException("bitmap must not be null");
+ }
+ validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.mBitmap = bitmap;
+ icon.mHotSpotX = hotSpotX;
+ icon.mHotSpotY = hotSpotY;
+ return icon;
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" /&gt;
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+ if (resources == null) {
+ throw new IllegalArgumentException("resources must not be null");
+ }
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.loadResource(resources, resourceId);
+ return icon;
+ }
+
+ /**
+ * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+ * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+ *
+ * @param context The context.
+ * @return The loaded pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ * @see #isLoaded()
+ * @hide
+ */
+ public PointerIcon load(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (mSystemIconResourceId == 0 || mBitmap != null) {
+ return this;
+ }
+
+ PointerIcon result = new PointerIcon(mStyle);
+ result.mSystemIconResourceId = mSystemIconResourceId;
+ result.loadResource(context.getResources(), mSystemIconResourceId);
+ return result;
+ }
+
+ /**
+ * Returns true if the pointer icon style is {@link #STYLE_NULL}.
+ *
+ * @return True if the pointer icon style is {@link #STYLE_NULL}.
+ */
+ public boolean isNullIcon() {
+ return mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Returns true if the pointer icon has been loaded and its bitmap and hotspot
+ * information are available.
+ *
+ * @return True if the pointer icon is loaded.
+ * @see #load(Context)
+ */
+ public boolean isLoaded() {
+ return mBitmap != null || mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Gets the style of the pointer icon.
+ *
+ * @return The pointer icon style.
+ */
+ public int getStyle() {
+ return mStyle;
+ }
+
+ /**
+ * Gets the bitmap of the pointer icon.
+ *
+ * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public Bitmap getBitmap() {
+ throwIfIconIsNotLoaded();
+ return mBitmap;
+ }
+
+ /**
+ * Gets the X offset of the pointer icon hotspot.
+ *
+ * @return The hotspot X offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotX() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotX;
+ }
+
+ /**
+ * Gets the Y offset of the pointer icon hotspot.
+ *
+ * @return The hotspot Y offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotY() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotY;
+ }
+
+ private void throwIfIconIsNotLoaded() {
+ if (!isLoaded()) {
+ throw new IllegalStateException("The icon is not loaded.");
+ }
+ }
+
+ public static final Parcelable.Creator<PointerIcon> CREATOR
+ = new Parcelable.Creator<PointerIcon>() {
+ public PointerIcon createFromParcel(Parcel in) {
+ int style = in.readInt();
+ if (style == STYLE_NULL) {
+ return getNullIcon();
+ }
+
+ int systemIconResourceId = in.readInt();
+ if (systemIconResourceId != 0) {
+ PointerIcon icon = new PointerIcon(style);
+ icon.mSystemIconResourceId = systemIconResourceId;
+ return icon;
+ }
+
+ Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+ float hotSpotX = in.readFloat();
+ float hotSpotY = in.readFloat();
+ return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public PointerIcon[] newArray(int size) {
+ return new PointerIcon[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mStyle);
+
+ if (mStyle != STYLE_NULL) {
+ out.writeInt(mSystemIconResourceId);
+ if (mSystemIconResourceId == 0) {
+ mBitmap.writeToParcel(out, flags);
+ out.writeFloat(mHotSpotX);
+ out.writeFloat(mHotSpotY);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || !(other instanceof PointerIcon)) {
+ return false;
+ }
+
+ PointerIcon otherIcon = (PointerIcon) other;
+ if (mStyle != otherIcon.mStyle
+ || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+ return false;
+ }
+
+ if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+ || mHotSpotX != otherIcon.mHotSpotX
+ || mHotSpotY != otherIcon.mHotSpotY)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void loadResource(Resources resources, int resourceId) {
+ XmlResourceParser parser = resources.getXml(resourceId);
+ final int bitmapRes;
+ final float hotSpotX;
+ final float hotSpotY;
+ try {
+ XmlUtils.beginDocument(parser, "pointer-icon");
+
+ TypedArray a = resources.obtainAttributes(
+ parser, com.android.internal.R.styleable.PointerIcon);
+ bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+ hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ a.recycle();
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+ } finally {
+ parser.close();
+ }
+
+ if (bitmapRes == 0) {
+ throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+ }
+
+ Drawable drawable = resources.getDrawable(bitmapRes);
+ if (!(drawable instanceof BitmapDrawable)) {
+ throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+ + "refer to a bitmap drawable.");
+ }
+
+ // Set the properties now that we have successfully loaded the icon.
+ mBitmap = ((BitmapDrawable)drawable).getBitmap();
+ mHotSpotX = hotSpotX;
+ mHotSpotY = hotSpotY;
+ }
+
+ private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+ throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+ }
+ if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+ throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+ }
+ }
+
+ private static int getSystemIconStyleIndex(int style) {
+ switch (style) {
+ case STYLE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+ case STYLE_SPOT_HOVER:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+ case STYLE_SPOT_TOUCH:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+ case STYLE_SPOT_ANCHOR:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index d638e70..5e07e1a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -163,6 +163,13 @@ public class ScaleGestureDetector {
private int mActiveId1;
private boolean mActive0MostRecent;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
ViewConfiguration config = ViewConfiguration.get(context);
mContext = context;
@@ -171,16 +178,20 @@ public class ScaleGestureDetector {
}
public boolean onTouchEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ }
+
final int action = event.getActionMasked();
- boolean handled = true;
if (action == MotionEvent.ACTION_DOWN) {
reset(); // Start fresh
}
- if (mInvalidGesture) return false;
-
- if (!mGestureInProgress) {
+ boolean handled = true;
+ if (mInvalidGesture) {
+ handled = false;
+ } else if (!mGestureInProgress) {
switch (action) {
case MotionEvent.ACTION_DOWN: {
mActiveId0 = event.getPointerId(0);
@@ -456,6 +467,10 @@ public class ScaleGestureDetector {
break;
}
}
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
return handled;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 87b3d79..0a7a375 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -34,7 +34,6 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.ParcelFileDescriptor;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.Log;
import java.lang.ref.WeakReference;
@@ -84,7 +83,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class SurfaceView extends View {
static private final String TAG = "SurfaceView";
static private final boolean DEBUG = false;
- static private final boolean localLOGV = DEBUG ? true : Config.LOGV;
+ static private final boolean localLOGV = DEBUG ? true : false;
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 4ab2881..fccef2b 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -16,8 +16,6 @@
package android.view;
-import android.util.Config;
-import android.util.Log;
import android.util.Poolable;
import android.util.Pool;
import android.util.Pools;
@@ -25,24 +23,15 @@ import android.util.PoolableManager;
/**
* Helper for tracking the velocity of touch events, for implementing
- * flinging and other such gestures. Use {@link #obtain} to retrieve a
- * new instance of the class when you are going to begin tracking, put
- * the motion events you receive into it with {@link #addMovement(MotionEvent)},
- * and when you want to determine the velocity call
- * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
- * and {@link #getXVelocity()}.
+ * flinging and other such gestures.
+ *
+ * Use {@link #obtain} to retrieve a new instance of the class when you are going
+ * to begin tracking. Put the motion events you receive into it with
+ * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call
+ * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
+ * and {@link #getXVelocity(int)} to retrieve the velocity for each pointer id.
*/
public final class VelocityTracker implements Poolable<VelocityTracker> {
- private static final String TAG = "VelocityTracker";
- private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG || Config.LOGV;
-
- private static final int NUM_PAST = 10;
- private static final int MAX_AGE_MILLISECONDS = 200;
-
- private static final int POINTER_POOL_CAPACITY = 20;
- private static final int INVALID_POINTER = -1;
-
private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
Pools.finitePool(new PoolableManager<VelocityTracker>() {
public VelocityTracker newInstance() {
@@ -56,31 +45,20 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
element.clear();
}
}, 2));
-
- private static Pointer sRecycledPointerListHead;
- private static int sRecycledPointerCount;
-
- private static final class Pointer {
- public Pointer next;
-
- public int id;
- public float xVelocity;
- public float yVelocity;
-
- public final float[] pastX = new float[NUM_PAST];
- public final float[] pastY = new float[NUM_PAST];
- public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
-
- public int generation;
- }
-
- private Pointer mPointerListHead; // sorted by id in increasing order
- private int mLastTouchIndex;
- private int mGeneration;
- private int mActivePointerId;
+ private static final int ACTIVE_POINTER_ID = -1;
+
+ private int mPtr;
private VelocityTracker mNext;
+ private static native int nativeInitialize();
+ private static native void nativeDispose(int ptr);
+ private static native void nativeClear(int ptr);
+ private static native void nativeAddMovement(int ptr, MotionEvent event);
+ private static native void nativeComputeCurrentVelocity(int ptr, int units, float maxVelocity);
+ private static native float nativeGetXVelocity(int ptr, int id);
+ private static native float nativeGetYVelocity(int ptr, int id);
+
/**
* Retrieve a new VelocityTracker object to watch the velocity of a
* motion. Be sure to call {@link #recycle} when done. You should
@@ -116,18 +94,26 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
}
private VelocityTracker() {
- clear();
+ mPtr = nativeInitialize();
}
-
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mPtr != 0) {
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
/**
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- releasePointerList(mPointerListHead);
-
- mPointerListHead = null;
- mLastTouchIndex = 0;
- mActivePointerId = INVALID_POINTER;
+ nativeClear(mPtr);
}
/**
@@ -137,110 +123,13 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* final {@link MotionEvent#ACTION_UP}. You can, however, call this
* for whichever events you desire.
*
- * @param ev The MotionEvent you received and would like to track.
+ * @param event The MotionEvent you received and would like to track.
*/
- public void addMovement(MotionEvent ev) {
- final int historySize = ev.getHistorySize();
- final int pointerCount = ev.getPointerCount();
- final int lastTouchIndex = mLastTouchIndex;
- final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
- final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
- final int generation = mGeneration++;
-
- mLastTouchIndex = finalTouchIndex;
-
- // Update pointer data.
- Pointer previousPointer = null;
- for (int i = 0; i < pointerCount; i++){
- final int pointerId = ev.getPointerId(i);
-
- // Find the pointer data for this pointer id.
- // This loop is optimized for the common case where pointer ids in the event
- // are in sorted order. However, we check for this case explicitly and
- // perform a full linear scan from the start if needed.
- Pointer nextPointer;
- if (previousPointer == null || pointerId < previousPointer.id) {
- previousPointer = null;
- nextPointer = mPointerListHead;
- } else {
- nextPointer = previousPointer.next;
- }
-
- final Pointer pointer;
- for (;;) {
- if (nextPointer != null) {
- final int nextPointerId = nextPointer.id;
- if (nextPointerId == pointerId) {
- pointer = nextPointer;
- break;
- }
- if (nextPointerId < pointerId) {
- nextPointer = nextPointer.next;
- continue;
- }
- }
-
- // Pointer went down. Add it to the list.
- // Write a sentinel at the end of the pastTime trace so we will be able to
- // tell when the trace started.
- if (mActivePointerId == INVALID_POINTER) {
- // Congratulations! You're the new active pointer!
- mActivePointerId = pointerId;
- }
- pointer = obtainPointer();
- pointer.id = pointerId;
- pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
- pointer.next = nextPointer;
- if (previousPointer == null) {
- mPointerListHead = pointer;
- } else {
- previousPointer.next = pointer;
- }
- break;
- }
-
- pointer.generation = generation;
- previousPointer = pointer;
-
- final float[] pastX = pointer.pastX;
- final float[] pastY = pointer.pastY;
- final long[] pastTime = pointer.pastTime;
-
- for (int j = 0; j < historySize; j++) {
- final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
- pastX[touchIndex] = ev.getHistoricalX(i, j);
- pastY[touchIndex] = ev.getHistoricalY(i, j);
- pastTime[touchIndex] = ev.getHistoricalEventTime(j);
- }
- pastX[finalTouchIndex] = ev.getX(i);
- pastY[finalTouchIndex] = ev.getY(i);
- pastTime[finalTouchIndex] = ev.getEventTime();
- }
-
- // Find removed pointers.
- previousPointer = null;
- for (Pointer pointer = mPointerListHead; pointer != null; ) {
- final Pointer nextPointer = pointer.next;
- final int pointerId = pointer.id;
- if (pointer.generation != generation) {
- // Pointer went up. Remove it from the list.
- if (previousPointer == null) {
- mPointerListHead = nextPointer;
- } else {
- previousPointer.next = nextPointer;
- }
- releasePointer(pointer);
-
- if (pointerId == mActivePointerId) {
- // Pick a new active pointer. How is arbitrary.
- mActivePointerId = mPointerListHead != null ?
- mPointerListHead.id : INVALID_POINTER;
- }
- } else {
- previousPointer = pointer;
- }
- pointer = nextPointer;
+ public void addMovement(MotionEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
}
+ nativeAddMovement(mPtr, event);
}
/**
@@ -250,7 +139,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @see #computeCurrentVelocity(int, float)
*/
public void computeCurrentVelocity(int units) {
- computeCurrentVelocity(units, Float.MAX_VALUE);
+ nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
}
/**
@@ -267,78 +156,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* must be positive.
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
- final int lastTouchIndex = mLastTouchIndex;
-
- for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
- final long[] pastTime = pointer.pastTime;
-
- // Search backwards in time for oldest acceptable time.
- // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
- int oldestTouchIndex = lastTouchIndex;
- int numTouches = 1;
- final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
- while (numTouches < NUM_PAST) {
- final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
- final long nextOldestTime = pastTime[nextOldestTouchIndex];
- if (nextOldestTime < minTime) { // also handles end of trace sentinel
- break;
- }
- oldestTouchIndex = nextOldestTouchIndex;
- numTouches += 1;
- }
-
- // If we have a lot of samples, skip the last received sample since it is
- // probably pretty noisy compared to the sum of all of the traces already acquired.
- if (numTouches > 3) {
- numTouches -= 1;
- }
-
- // Kind-of stupid.
- final float[] pastX = pointer.pastX;
- final float[] pastY = pointer.pastY;
-
- final float oldestX = pastX[oldestTouchIndex];
- final float oldestY = pastY[oldestTouchIndex];
- final long oldestTime = pastTime[oldestTouchIndex];
-
- float accumX = 0;
- float accumY = 0;
-
- for (int i = 1; i < numTouches; i++) {
- final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
- final int duration = (int)(pastTime[touchIndex] - oldestTime);
-
- if (duration == 0) continue;
-
- float delta = pastX[touchIndex] - oldestX;
- float velocity = (delta / duration) * units; // pixels/frame.
- accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
-
- delta = pastY[touchIndex] - oldestY;
- velocity = (delta / duration) * units; // pixels/frame.
- accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
- }
-
- if (accumX < -maxVelocity) {
- accumX = - maxVelocity;
- } else if (accumX > maxVelocity) {
- accumX = maxVelocity;
- }
-
- if (accumY < -maxVelocity) {
- accumY = - maxVelocity;
- } else if (accumY > maxVelocity) {
- accumY = maxVelocity;
- }
-
- pointer.xVelocity = accumX;
- pointer.yVelocity = accumY;
-
- if (localLOGV) {
- Log.v(TAG, "Pointer " + pointer.id
- + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
- }
- }
+ nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
}
/**
@@ -348,8 +166,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- Pointer pointer = getPointer(mActivePointerId);
- return pointer != null ? pointer.xVelocity : 0;
+ return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
}
/**
@@ -359,8 +176,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- Pointer pointer = getPointer(mActivePointerId);
- return pointer != null ? pointer.yVelocity : 0;
+ return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
}
/**
@@ -371,8 +187,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed X velocity.
*/
public float getXVelocity(int id) {
- Pointer pointer = getPointer(id);
- return pointer != null ? pointer.xVelocity : 0;
+ return nativeGetXVelocity(mPtr, id);
}
/**
@@ -383,68 +198,6 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed Y velocity.
*/
public float getYVelocity(int id) {
- Pointer pointer = getPointer(id);
- return pointer != null ? pointer.yVelocity : 0;
- }
-
- private Pointer getPointer(int id) {
- for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
- if (pointer.id == id) {
- return pointer;
- }
- }
- return null;
- }
-
- private static Pointer obtainPointer() {
- synchronized (sPool) {
- if (sRecycledPointerCount != 0) {
- Pointer element = sRecycledPointerListHead;
- sRecycledPointerCount -= 1;
- sRecycledPointerListHead = element.next;
- element.next = null;
- return element;
- }
- }
- return new Pointer();
- }
-
- private static void releasePointer(Pointer pointer) {
- synchronized (sPool) {
- if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
- pointer.next = sRecycledPointerListHead;
- sRecycledPointerCount += 1;
- sRecycledPointerListHead = pointer;
- }
- }
- }
-
- private static void releasePointerList(Pointer pointer) {
- if (pointer != null) {
- synchronized (sPool) {
- int count = sRecycledPointerCount;
- if (count >= POINTER_POOL_CAPACITY) {
- return;
- }
-
- Pointer tail = pointer;
- for (;;) {
- count += 1;
- if (count >= POINTER_POOL_CAPACITY) {
- break;
- }
-
- Pointer next = tail.next;
- if (next == null) {
- break;
- }
- tail = next;
- }
-
- tail.next = sRecycledPointerListHead;
- sRecycledPointerCount = count;
- sRecycledPointerListHead = pointer;
- }
- }
+ return nativeGetYVelocity(mPtr, id);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5a96efd..5af2e56 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1304,6 +1304,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
static final int VIEW_STATE_PRESSED = 1 << 4;
static final int VIEW_STATE_ACTIVATED = 1 << 5;
static final int VIEW_STATE_ACCELERATED = 1 << 6;
+ static final int VIEW_STATE_HOVERED = 1 << 7;
+ static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+ static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
static final int[] VIEW_STATE_IDS = new int[] {
R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
@@ -1313,6 +1316,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
R.attr.state_pressed, VIEW_STATE_PRESSED,
R.attr.state_activated, VIEW_STATE_ACTIVATED,
R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
+ R.attr.state_hovered, VIEW_STATE_HOVERED,
+ R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED,
};
static {
@@ -1623,6 +1629,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
+ * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
+ * @hide
+ */
+ private static final int HOVERED = 0x10000000;
+
+ /**
* Indicates that pivotX or pivotY were explicitly set and we should not assume the center
* for transform operations
*
@@ -1643,6 +1655,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
static final int INVALIDATED = 0x80000000;
+ /* Masks for mPrivateFlags2 */
+
+ /**
+ * Indicates that this view has reported that it can accept the current drag's content.
+ * Cleared when the drag operation concludes.
+ * @hide
+ */
+ static final int DRAG_CAN_ACCEPT = 0x00000001;
+
+ /**
+ * Indicates that this view is currently directly under the drag location in a
+ * drag-and-drop operation involving content that it can accept. Cleared when
+ * the drag exits the view, or when the drag operation concludes.
+ * @hide
+ */
+ static final int DRAG_HOVERED = 0x00000002;
+
+ /* End of masks for mPrivateFlags2 */
+
+ static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED;
+
/**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
@@ -1814,6 +1847,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
@ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY")
})
int mPrivateFlags;
+ int mPrivateFlags2;
/**
* This view's request for the visibility of the status bar.
@@ -2243,12 +2277,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private ViewPropertyAnimator mAnimator = null;
/**
- * Cache drag/drop state
- *
- */
- boolean mCanAcceptDrop;
-
- /**
* Flag indicating that a drag can cross window boundaries. When
* {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
* with this flag set, all visible applications will be able to participate
@@ -2356,6 +2384,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
Rect mLocalDirtyRect;
/**
+ * Consistency verifier for debugging purposes.
+ * @hide
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -3419,6 +3455,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!isShown()) {
return;
}
+
+ // Populate these here since they are related to the View that
+ // sends the event and should not be modified while dispatching
+ // to descendants.
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
@@ -3434,22 +3474,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
dispatchPopulateAccessibilityEvent(event);
- AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+ // In the beginning we called #isShown(), so we know that getParent() is not null.
+ getParent().requestSendAccessibilityEvent(this, event);
}
/**
- * Dispatches an {@link AccessibilityEvent} to the {@link View} children
- * to be populated.
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} children to be populated.
+ * This method first calls {@link #onPopulateAccessibilityEvent(AccessibilityEvent)}
+ * on this view allowing it to populate information about itself and also decide
+ * whether to intercept the population i.e. to prevent its children from populating
+ * the event.
*
* @param event The event.
*
* @return True if the event population was completed.
*/
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
return false;
}
/**
+ * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * giving a chance to this View to populate the accessibility evnet with
+ * information about itself.
+ *
+ * @param event The accessibility event which to populate.
+ */
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+
+ }
+
+ /**
* Gets the {@link View} description. It briefly describes the view and is
* primarily used for accessibility support. Set this property to enable
* better accessibility support for your application. This is especially
@@ -4562,21 +4618,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled, false otherwise.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
- // If any attached key listener a first crack at the event.
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 0);
+ }
//noinspection SimplifiableIfStatement,deprecation
- if (android.util.Config.LOGV) {
+ if (false) {
captureViewInfo("captureViewKeyEvent", this);
}
+ // Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
- return event.dispatch(this, mAttachInfo != null
- ? mAttachInfo.mKeyDispatchState : null, this);
+ if (event.dispatch(this, mAttachInfo != null
+ ? mAttachInfo.mKeyDispatchState : null, this)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4597,16 +4663,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
- //noinspection SimplifiableIfStatement
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
+ if (onFilterTouchEventForSecurity(event)) {
+ //noinspection SimplifiableIfStatement
+ if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+ mOnTouchListener.onTouch(this, event)) {
+ return true;
+ }
+
+ if (onTouchEvent(event)) {
+ return true;
+ }
}
- return onTouchEvent(event);
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4634,8 +4710,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ }
+
//Log.i("view", "view=" + this + ", " + event.toString());
- return onTrackballEvent(event);
+ if (onTrackballEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4643,20 +4730,90 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* <p>
* Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
* are delivered to the view under the pointer. All other generic motion events are
- * delivered to the focused view.
+ * delivered to the focused view. Hover events are handled specially and are delivered
+ * to {@link #onHoverEvent}.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE
+ || action == MotionEvent.ACTION_HOVER_EXIT) {
+ if (dispatchHoverEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericPointerEvent(event)) {
+ return true;
+ }
+ } else if (dispatchGenericFocusedEvent(event)) {
+ return true;
+ }
+
//noinspection SimplifiableIfStatement
if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
- return onGenericMotionEvent(event);
+ if (onGenericMotionEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a hover event.
+ * <p>
+ * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ return onHoverEvent(event);
+ }
+
+ /**
+ * Dispatch a generic motion event to the view under the first pointer.
+ * <p>
+ * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Dispatch a generic motion event to the currently focused view.
+ * <p>
+ * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
+ * </p>
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ * @hide
+ */
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+ return false;
}
/**
@@ -4855,7 +5012,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return;
}
Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
- outRect.set(0, 0, d.getWidth(), d.getHeight());
+ d.getRectSize(outRect);
}
/**
@@ -5223,15 +5380,80 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* </code>
*
* @param event The generic motion event being processed.
- *
- * @return Return true if you have consumed the event, false if you haven't.
- * The default implementation always returns false.
+ * @return True if the event was handled, false otherwise.
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
/**
+ * Implement this method to handle hover events.
+ * <p>
+ * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER},
+ * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * </p><p>
+ * The view receives hover enter as the pointer enters the bounds of the view and hover
+ * exit as the pointer exits the bound of the view or just before the pointer goes down
+ * (which implies that {@link #onTouchEvent} will be called soon).
+ * </p><p>
+ * If the view would like to handle the hover event itself and prevent its children
+ * from receiving hover, it should return true from this method. If this method returns
+ * true and a child has already received a hover enter event, the child will
+ * automatically receive a hover exit event.
+ * </p><p>
+ * The default implementation sets the hovered state of the view if the view is
+ * clickable.
+ * </p>
+ *
+ * @param event The motion event that describes the hover.
+ * @return True if this view handled the hover event and does not want its children
+ * to receive the hover event.
+ */
+ public boolean onHoverEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ setHovered(true);
+ break;
+
+ case MotionEvent.ACTION_HOVER_EXIT:
+ setHovered(false);
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the view is currently hovered.
+ *
+ * @return True if the view is currently hovered.
+ */
+ public boolean isHovered() {
+ return (mPrivateFlags & HOVERED) != 0;
+ }
+
+ /**
+ * Sets whether the view is currently hovered.
+ *
+ * @param hovered True if the view is hovered.
+ */
+ public void setHovered(boolean hovered) {
+ if (hovered) {
+ if ((mPrivateFlags & HOVERED) == 0) {
+ mPrivateFlags |= HOVERED;
+ refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ }
+ } else {
+ if ((mPrivateFlags & HOVERED) != 0) {
+ mPrivateFlags &= ~HOVERED;
+ refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+ }
+ }
+
+ /**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
@@ -5241,6 +5463,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
+ if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
+ mPrivateFlags &= ~PRESSED;
+ refreshDrawableState();
+ }
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
@@ -7212,8 +7438,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mPrivateFlags &= ~DRAWN;
mPrivateFlags |= INVALIDATED;
mPrivateFlags &= ~DRAWING_CACHE_VALID;
- if (mParent != null && mAttachInfo != null && mAttachInfo.mHardwareAccelerated) {
- mParent.invalidateChild(this, null);
+ if (mParent != null && mAttachInfo != null) {
+ if (mAttachInfo.mHardwareAccelerated) {
+ mParent.invalidateChild(this, null);
+ } else {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
+ // Don't call invalidate -- we don't want to internally scroll
+ // our own bounds
+ mParent.invalidateChild(this, r);
+ }
}
}
}
@@ -7319,8 +7553,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean post(Runnable action) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
ViewRoot.getRunQueue().post(action);
@@ -7348,8 +7583,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean postDelayed(Runnable action, long delayMillis) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
ViewRoot.getRunQueue().postDelayed(action, delayMillis);
@@ -7371,8 +7607,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
public boolean removeCallbacks(Runnable action) {
Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ handler = attachInfo.mHandler;
} else {
// Assume that post will succeed later
ViewRoot.getRunQueue().removeCallbacks(action);
@@ -7419,11 +7656,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- if (mAttachInfo != null) {
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_MSG;
msg.obj = this;
- mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}
@@ -7443,7 +7681,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
- if (mAttachInfo != null) {
+ AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
info.target = this;
info.left = left;
@@ -7454,7 +7693,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_RECT_MSG;
msg.obj = info;
- mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
+ attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}
@@ -9867,12 +10106,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
- if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested) {
+ if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
+ HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
viewStateIndex |= VIEW_STATE_ACCELERATED;
}
+ if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
+
+ final int privateFlags2 = mPrivateFlags2;
+ if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
+ if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
drawableState = VIEW_STATE_SETS[viewStateIndex];
@@ -11540,6 +11785,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return onDragEvent(event);
}
+ boolean canAcceptDrag() {
+ return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
+ }
+
/**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 739758c..94eb429 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -19,7 +19,6 @@ package android.view;
import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
-import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.SparseArray;
@@ -156,6 +155,13 @@ public class ViewConfiguration {
private static final int MAXIMUM_FLING_VELOCITY = 8000;
/**
+ * Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ */
+ private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+
+ /**
* The maximum size of View's drawing cache, expressed in bytes. This size
* should be at least equal to the size of the screen in ARGB888 format.
*/
@@ -185,6 +191,7 @@ public class ViewConfiguration {
private final int mTouchSlop;
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
+ private final int mScaledTouchExplorationTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -206,6 +213,7 @@ public class ViewConfiguration {
mTouchSlop = TOUCH_SLOP;
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
+ mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -242,6 +250,7 @@ public class ViewConfiguration {
mTouchSlop = (int) (sizeAndDensity * TOUCH_SLOP + 0.5f);
mPagingTouchSlop = (int) (sizeAndDensity * PAGING_TOUCH_SLOP + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+ mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
// Size of the screen in bytes, in ARGB_8888 format
@@ -444,6 +453,17 @@ public class ViewConfiguration {
}
/**
+ * @return Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ *
+ * @hide
+ */
+ public int getScaledTouchExplorationTapSlop() {
+ return mScaledTouchExplorationTapSlop;
+ }
+
+ /**
* @return Distance a touch must be outside the bounds of a window for it
* to be counted as outside the window for purposes of dismissing that
* window.
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index c19a107..881cb76 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -16,7 +16,6 @@
package android.view;
-import android.util.Config;
import android.util.Log;
import android.util.DisplayMetrics;
import android.content.res.Resources;
@@ -141,8 +140,24 @@ public class ViewDebug {
public static final boolean DEBUG_DRAG = false;
/**
+ * Enables logging of factors that affect the latency and responsiveness of an application.
+ *
+ * Logs the relative difference between the time an event was created and the time it
+ * was delivered.
+ *
+ * Logs the time spent waiting for Surface.lockCanvas() or eglSwapBuffers().
+ * This is time that the event loop spends blocked and unresponsive. Ideally, drawing
+ * and animations should be perfectly synchronized with VSYNC so that swap buffers
+ * is instantaneous.
+ *
+ * Logs the time spent in ViewRoot.performTraversals() or ViewRoot.draw().
+ * @hide
+ */
+ public static final boolean DEBUG_LATENCY = false;
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
- * view consistency checks happen only if {@link android.util.Config#DEBUG} is set
+ * view consistency checks happen only if {@link false} is set
* to true. The value of this property can be configured externally in one of the
* following files:</p>
* <ul>
@@ -156,7 +171,7 @@ public class ViewDebug {
public static boolean consistencyCheckEnabled = false;
static {
- if (Config.DEBUG) {
+ if (false) {
Debug.setFieldsOn(ViewDebug.class, true);
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8dc86ac..7b404b4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -147,6 +147,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownY;
+ // Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE.
+ private View mHoveredChild;
+
/**
* Internal flags.
*
@@ -583,6 +586,35 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*/
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ ViewParent parent = getParent();
+ if (parent == null) {
+ return false;
+ }
+ final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+ if (!propagate) {
+ return false;
+ }
+ return parent.requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * Called when a child has requested sending an {@link AccessibilityEvent} and
+ * gives an opportunity to its parent to augment the event.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event should be sent.
+ *
+ * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+ */
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mFocused != null &&
@@ -926,6 +958,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
+ child.mPrivateFlags2 &= ~View.DRAG_MASK;
if (child.getVisibility() == VISIBLE) {
final boolean handled = notifyChildOfDrag(children[i]);
if (handled) {
@@ -946,6 +979,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (View child : mDragNotifiedChildren) {
// If a child was notified about an ongoing drag, it's told that it's over
child.dispatchDragEvent(event);
+ child.mPrivateFlags2 &= ~View.DRAG_MASK;
+ child.refreshDrawableState();
}
mDragNotifiedChildren.clear();
@@ -976,8 +1011,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int action = event.mAction;
// If we've dragged off of a child view, send it the EXITED message
if (mCurrentDragView != null) {
+ final View view = mCurrentDragView;
event.mAction = DragEvent.ACTION_DRAG_EXITED;
- mCurrentDragView.dispatchDragEvent(event);
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.refreshDrawableState();
}
mCurrentDragView = target;
@@ -985,6 +1023,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (target != null) {
event.mAction = DragEvent.ACTION_DRAG_ENTERED;
target.dispatchDragEvent(event);
+ target.mPrivateFlags2 |= View.DRAG_HOVERED;
+ target.refreshDrawableState();
}
event.mAction = action; // restore the event's original state
}
@@ -1015,7 +1055,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
case DragEvent.ACTION_DRAG_EXITED: {
if (mCurrentDragView != null) {
- mCurrentDragView.dispatchDragEvent(event);
+ final View view = mCurrentDragView;
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.refreshDrawableState();
+
mCurrentDragView = null;
}
} break;
@@ -1053,7 +1097,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View[] children = mChildren;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
- if (!child.mCanAcceptDrop) {
+ if (!child.canAcceptDrag()) {
continue;
}
@@ -1069,11 +1113,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
}
+ boolean canAccept = false;
if (! mDragNotifiedChildren.contains(child)) {
mDragNotifiedChildren.add(child);
- child.mCanAcceptDrop = child.dispatchDragEvent(mCurrentDrag);
+ canAccept = child.dispatchDragEvent(mCurrentDrag);
+ if (canAccept && !child.canAcceptDrag()) {
+ child.mPrivateFlags2 |= View.DRAG_CAN_ACCEPT;
+ child.refreshDrawableState();
+ }
}
- return child.mCanAcceptDrop;
+ return canAccept;
}
@Override
@@ -1106,10 +1155,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 1);
+ }
+
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchKeyEvent(event);
+ if (mFocused.dispatchKeyEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1132,21 +1193,69 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
+ }
+
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchTrackballEvent(event);
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchTrackballEvent(event);
+ if (mFocused.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
- /**
- * {@inheritDoc}
- */
+ /** @hide */
@Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- // Send the event to the child under the pointer.
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ // Send the hover enter or hover move event to the view group first.
+ // If it handles the event then a hovered child should receive hover exit.
+ boolean handled = false;
+ final boolean interceptHover;
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ interceptHover = true;
+ } else {
+ handled = super.dispatchHoverEvent(event);
+ interceptHover = handled;
+ }
+
+ // Send successive hover events to the hovered child as long as the pointer
+ // remains within the child's bounds.
+ MotionEvent eventNoHistory = event;
+ if (mHoveredChild != null) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (interceptHover
+ || !isTransformedTouchPointInView(x, y, mHoveredChild, null)) {
+ // Pointer exited the child.
+ // Send it a hover exit with only the most recent coordinates. We could
+ // try to find the exact point in history when the pointer left the view
+ // but it is not worth the effort.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
+ eventNoHistory.setAction(action);
+ mHoveredChild = null;
+ } else {
+ // Pointer is still within the child.
+ handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
+ }
+ }
+
+ // Find a new hovered child if needed.
+ if (!interceptHover && mHoveredChild == null
+ && (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)) {
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final View[] children = mChildren;
@@ -1155,45 +1264,99 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
- && child.getAnimation() == null) {
- // Skip invisible child unless it is animating.
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
- if (!isTransformedTouchPointInView(x, y, child, null)) {
- // Scroll point is out of child's bounds.
- continue;
+ // Found the hovered child.
+ mHoveredChild = child;
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ // Pointer was moving within the view group and entered the child.
+ // Send it a hover enter and hover move with only the most recent
+ // coordinates. We could try to find the exact point in history when
+ // the pointer entered the view but it is not worth the effort.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
+ eventNoHistory.setAction(action);
+
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
+ } else { /* must be ACTION_HOVER_ENTER */
+ // Pointer entered the child.
+ handled |= dispatchTransformedGenericPointerEvent(event, child);
}
+ break;
+ }
+ }
+ }
- final float offsetX = mScrollX - child.mLeft;
- final float offsetY = mScrollY - child.mTop;
- final boolean handled;
- if (!child.hasIdentityMatrix()) {
- MotionEvent transformedEvent = MotionEvent.obtain(event);
- transformedEvent.offsetLocation(offsetX, offsetY);
- transformedEvent.transform(child.getInverseMatrix());
- handled = child.dispatchGenericMotionEvent(transformedEvent);
- transformedEvent.recycle();
- } else {
- event.offsetLocation(offsetX, offsetY);
- handled = child.dispatchGenericMotionEvent(event);
- event.offsetLocation(-offsetX, -offsetY);
- }
+ // Recycle the copy of the event that we made.
+ if (eventNoHistory != event) {
+ eventNoHistory.recycle();
+ }
- if (handled) {
- return true;
- }
+ // Send hover exit to the view group. If there was a child, we will already have
+ // sent the hover exit to it.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ handled |= super.dispatchHoverEvent(event);
+ }
+
+ // Done.
+ return handled;
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ // Handle the event only if leaf. This guarantees that
+ // the leafs (or any custom class that returns true from
+ // this method) will get a change to process the hover.
+ if (getChildCount() == 0) {
+ return super.onHoverEvent(event);
+ }
+ return false;
+ }
+
+ private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
+ if (event.getHistorySize() == 0) {
+ return event;
+ }
+ return MotionEvent.obtainNoHistory(event);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean dispatchGenericPointerEvent(MotionEvent event) {
+ // Send the event to the child under the pointer.
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ final View[] children = mChildren;
+ final float x = event.getX();
+ final float y = event.getY();
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
}
- }
- // No child handled the event. Send it to this view group.
- return super.dispatchGenericMotionEvent(event);
+ if (dispatchTransformedGenericPointerEvent(event, child)) {
+ return true;
+ }
+ }
}
+ // No child handled the event. Send it to this view group.
+ return super.dispatchGenericPointerEvent(event);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchGenericMotionEvent(event);
+ return super.dispatchGenericFocusedEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
return mFocused.dispatchGenericMotionEvent(event);
}
@@ -1201,166 +1364,193 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Dispatches a generic pointer event to a child, taking into account
+ * transformations that apply to the child.
+ *
+ * @param event The event to send.
+ * @param child The view to send the event to.
+ * @return {@code true} if the child handled the event.
+ */
+ private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
+ final float offsetX = mScrollX - child.mLeft;
+ final float offsetY = mScrollY - child.mTop;
+
+ boolean handled;
+ if (!child.hasIdentityMatrix()) {
+ MotionEvent transformedEvent = MotionEvent.obtain(event);
+ transformedEvent.offsetLocation(offsetX, offsetY);
+ transformedEvent.transform(child.getInverseMatrix());
+ handled = child.dispatchGenericMotionEvent(transformedEvent);
+ transformedEvent.recycle();
+ } else {
+ event.offsetLocation(offsetX, offsetY);
+ handled = child.dispatchGenericMotionEvent(event);
+ event.offsetLocation(-offsetX, -offsetY);
+ }
+ return handled;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
-
- final int action = ev.getAction();
- final int actionMasked = action & MotionEvent.ACTION_MASK;
-
- // Handle an initial down.
- if (actionMasked == MotionEvent.ACTION_DOWN
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- // Throw away all previous state when starting a new touch gesture.
- // The framework may have dropped the up or cancel event for the previous gesture
- // due to an app switch, ANR, or some other state change.
- cancelAndClearTouchTargets(ev);
- resetTouchState();
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
- // Check for interception.
- final boolean intercepted;
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (!disallowIntercept) {
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
- } else {
- intercepted = false;
+ boolean handled = false;
+ if (onFilterTouchEventForSecurity(ev)) {
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
+
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
}
- } else {
- // There are no touch targets and this action is not an initial down
- // so this view group continues to intercept touches.
- intercepted = true;
- }
- // Check for cancelation.
- final boolean canceled = resetCancelNextUpFlag(this)
- || actionMasked == MotionEvent.ACTION_CANCEL;
-
- // Update list of touch targets for pointer down, if needed.
- final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
- TouchTarget newTouchTarget = null;
- boolean alreadyDispatchedToNewTouchTarget = false;
- if (!canceled && !intercepted) {
+ // Check for interception.
+ final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
- || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- final int actionIndex = ev.getActionIndex(); // always 0 for down
- final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
- : TouchTarget.ALL_POINTER_IDS;
-
- // Clean up earlier touch targets for this pointer id in case they
- // have become out of sync.
- removePointersFromTouchTargets(idBitsToAssign);
-
- final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
- // Find a child that can receive the event. Scan children from front to back.
- final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
-
- for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
- && child.getAnimation() == null) {
- // Skip invisible child unless it is animating.
- continue;
- }
+ || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case it was changed
+ } else {
+ intercepted = false;
+ }
+ } else {
+ // There are no touch targets and this action is not an initial down
+ // so this view group continues to intercept touches.
+ intercepted = true;
+ }
- if (!isTransformedTouchPointInView(x, y, child, null)) {
- // New pointer is out of child's bounds.
- continue;
- }
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
+
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
+
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ // Find a child that can receive the event.
+ // Scan children from front to back.
+ final View[] children = mChildren;
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- // Child is already receiving touch within its bounds.
- // Give it the new pointer in addition to the ones it is handling.
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- break;
- }
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
- resetCancelNextUpFlag(child);
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- // Child wants to receive touch within its bounds.
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = i;
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- break;
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ mLastTouchDownTime = ev.getDownTime();
+ mLastTouchDownIndex = i;
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
+ }
}
}
- }
- if (newTouchTarget == null && mFirstTouchTarget != null) {
- // Did not find a child to receive the event.
- // Assign the pointer to the least recently added target.
- newTouchTarget = mFirstTouchTarget;
- while (newTouchTarget.next != null) {
- newTouchTarget = newTouchTarget.next;
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
}
- newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
- }
- // Dispatch to touch targets.
- boolean handled = false;
- if (mFirstTouchTarget == null) {
- // No touch targets so treat this as an ordinary view.
- handled = dispatchTransformedTouchEvent(ev, canceled, null,
- TouchTarget.ALL_POINTER_IDS);
- } else {
- // Dispatch to touch targets, excluding the new touch target if we already
- // dispatched to it. Cancel touch targets if necessary.
- TouchTarget predecessor = null;
- TouchTarget target = mFirstTouchTarget;
- while (target != null) {
- final TouchTarget next = target.next;
- if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
- handled = true;
- } else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- if (dispatchTransformedTouchEvent(ev, cancelChild,
- target.child, target.pointerIdBits)) {
+ // Dispatch to touch targets.
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
+ } else {
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
- }
- if (cancelChild) {
- if (predecessor == null) {
- mFirstTouchTarget = next;
- } else {
- predecessor.next = next;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child)
+ || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
+ continue;
}
- target.recycle();
- target = next;
- continue;
}
+ predecessor = target;
+ target = next;
}
- predecessor = target;
- target = next;
}
- }
- // Update list of touch targets for pointer up or cancel, if needed.
- if (canceled
- || actionMasked == MotionEvent.ACTION_UP
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- resetTouchState();
- } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
- final int actionIndex = ev.getActionIndex();
- final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
- removePointersFromTouchTargets(idBitsToRemove);
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled
+ || actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
+ }
}
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
+ }
return handled;
}
@@ -1476,6 +1666,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns true if a child view can receive pointer events.
+ * @hide
+ */
+ private static boolean canViewReceivePointerEvents(View child) {
+ return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ || child.getAnimation() != null;
+ }
+
+ /**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
@@ -1931,11 +2130,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+ // Let our children have a shot in populating the event.
for (int i = 0, count = getChildCount(); i < count; i++) {
- populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ boolean handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ if (handled) {
+ return handled;
+ }
}
- return populated;
+ return false;
}
/**
@@ -1975,7 +2179,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
- if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingRight) != 0) {
+ if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) {
mGroupFlags |= FLAG_PADDING_NOT_NULL;
} else {
mGroupFlags &= ~FLAG_PADDING_NOT_NULL;
@@ -3244,6 +3448,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == mHoveredChild) {
+ mHoveredChild = null;
+ }
+
boolean clearChildFocus = false;
if (view == mFocused) {
view.clearFocusForRemoval();
@@ -3307,6 +3515,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
final boolean notifyListener = onHierarchyChangeListener != null;
final View focused = mFocused;
+ final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3320,6 +3529,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == hoveredChild) {
+ mHoveredChild = null;
+ }
+
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3377,6 +3590,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener listener = mOnHierarchyChangeListener;
final boolean notify = listener != null;
final View focused = mFocused;
+ final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3389,6 +3603,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
+ if (view == hoveredChild) {
+ mHoveredChild = null;
+ }
+
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index d7d4c3f..655df39 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Rect;
+import android.view.accessibility.AccessibilityEvent;
/**
* Defines the responsibilities for a class that will be a parent of a View.
@@ -222,4 +223,22 @@ public interface ViewParent {
*/
public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
boolean immediate);
+
+ /**
+ * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+ * The child has already populated a record for itself in the event and is delegating
+ * to its parent to send the event. The parent can optionally add a record for itself.
+ * <p>
+ * Note: An accessibility event is fired by an individual view which populates the
+ * event with a record for its state and requests from its parent to perform
+ * the sending. The parent can optionally add a record for itself before
+ * dispatching the request to its parent. A parent can also choose not to
+ * respect the request for sending the event. The accessibility event is sent
+ * by the topmost view in the view tree.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event was sent.
+ */
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 7d6e18f..f02daba 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -49,7 +49,6 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -186,6 +185,8 @@ public final class ViewRoot extends Handler implements ViewParent,
final Rect mVisRect; // used to retrieve visible rect of focused view.
boolean mTraversalScheduled;
+ long mLastTraversalFinishedTimeNanos;
+ long mLastDrawDurationNanos;
boolean mWillDrawSoon;
boolean mLayoutRequested;
boolean mFirst;
@@ -250,14 +251,20 @@ public final class ViewRoot extends Handler implements ViewParent,
private final int mDensity;
+ /**
+ * Consistency verifier for debugging purposes.
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
public static IWindowSession getWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
if (!mInitialized) {
try {
InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
- sWindowSession = IWindowManager.Stub.asInterface(
- ServiceManager.getService("window"))
- .openSession(imm.getClient(), imm.getInputContext());
+ sWindowSession = Display.getWindowManager().openSession(
+ imm.getClient(), imm.getInputContext());
mInitialized = true;
} catch (RemoteException e) {
}
@@ -501,7 +508,12 @@ public final class ViewRoot extends Handler implements ViewParent,
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
- if (attrs != null && hardwareAccelerated) {
+ if (hardwareAccelerated) {
+ if (!HardwareRenderer.isAvailable()) {
+ mAttachInfo.mHardwareAccelerationRequested = true;
+ return;
+ }
+
// Only enable hardware acceleration if we are not in the system process
// The window manager creates ViewRoots to display animated preview windows
// of launching apps and we don't want those to be hardware accelerated
@@ -524,8 +536,6 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested
= mAttachInfo.mHardwareRenderer != null;
- } else if (HardwareRenderer.isAvailable()) {
- mAttachInfo.mHardwareAccelerationRequested = true;
}
}
}
@@ -661,6 +671,14 @@ public final class ViewRoot extends Handler implements ViewParent,
public void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
+
+ if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
+ final long now = System.nanoTime();
+ Log.d(TAG, "Latency: Scheduled traversal, it has been "
+ + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
+ + "ms since the last traversal finished.");
+ }
+
sendEmptyMessage(DO_TRAVERSAL);
}
}
@@ -1253,7 +1271,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ if (false && ViewDebug.consistencyCheckEnabled) {
if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) {
throw new IllegalStateException("The view hierarchy is an inconsistent state,"
+ "please refer to the logs with the tag "
@@ -1379,8 +1397,18 @@ public final class ViewRoot extends Handler implements ViewParent,
if (!cancelDraw && !newSurface) {
mFullRedrawNeeded = false;
+
+ final long drawStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ drawStartTime = System.nanoTime();
+ }
+
draw(fullRedrawNeeded);
+ if (ViewDebug.DEBUG_LATENCY) {
+ mLastDrawDurationNanos = System.nanoTime() - drawStartTime;
+ }
+
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
|| mReportNextDraw) {
if (LOCAL_LOGV) {
@@ -1591,8 +1619,20 @@ public final class ViewRoot extends Handler implements ViewParent,
int right = dirty.right;
int bottom = dirty.bottom;
+ final long lockCanvasStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ lockCanvasStartTime = System.nanoTime();
+ }
+
canvas = surface.lockCanvas(dirty);
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(TAG, "Latency: Spent "
+ + ((now - lockCanvasStartTime) * 0.000001f)
+ + "ms waiting for surface.lockCanvas()");
+ }
+
if (left != dirty.left || top != dirty.top || right != dirty.right ||
bottom != dirty.bottom) {
mAttachInfo.mIgnoreDirtyState = true;
@@ -1669,7 +1709,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mIgnoreDirtyState = false;
}
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ if (false && ViewDebug.consistencyCheckEnabled) {
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
@@ -2001,8 +2041,24 @@ public final class ViewRoot extends Handler implements ViewParent,
Debug.startMethodTracing("ViewRoot");
}
+ final long traversalStartTime;
+ if (ViewDebug.DEBUG_LATENCY) {
+ traversalStartTime = System.nanoTime();
+ mLastDrawDurationNanos = 0;
+ }
+
performTraversals();
+ if (ViewDebug.DEBUG_LATENCY) {
+ long now = System.nanoTime();
+ Log.d(TAG, "Latency: Spent "
+ + ((now - traversalStartTime) * 0.000001f)
+ + "ms in performTraversals(), with "
+ + (mLastDrawDurationNanos * 0.000001f)
+ + "ms of that time in draw()");
+ mLastTraversalFinishedTimeNanos = now;
+ }
+
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
@@ -2170,25 +2226,68 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- private void startInputEvent(InputQueue.FinishedCallback finishedCallback) {
+ private void startInputEvent(InputEvent event, InputQueue.FinishedCallback finishedCallback) {
if (mFinishedCallback != null) {
Slog.w(TAG, "Received a new input event from the input queue but there is "
+ "already an unfinished input event in progress.");
}
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventReceiveTimeNanos = System.nanoTime();
+ mInputEventDeliverTimeNanos = 0;
+ mInputEventDeliverPostImeTimeNanos = 0;
+ }
+
mFinishedCallback = finishedCallback;
}
- private void finishInputEvent(boolean handled) {
+ private void finishInputEvent(InputEvent event, boolean handled) {
if (LOCAL_LOGV) Log.v(TAG, "Telling window manager input event is finished");
- if (mFinishedCallback != null) {
- mFinishedCallback.finished(handled);
- mFinishedCallback = null;
- } else {
+ if (mFinishedCallback == null) {
Slog.w(TAG, "Attempted to tell the input queue that the current input event "
+ "is finished but there is no input event actually in progress.");
+ return;
}
+
+ if (ViewDebug.DEBUG_LATENCY) {
+ final long now = System.nanoTime();
+ final long eventTime = event.getEventTimeNano();
+ final StringBuilder msg = new StringBuilder();
+ msg.append("Latency: Spent ");
+ msg.append((now - mInputEventReceiveTimeNanos) * 0.000001f);
+ msg.append("ms processing ");
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ msg.append("key event, action=");
+ msg.append(KeyEvent.actionToString(keyEvent.getAction()));
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ msg.append("motion event, action=");
+ msg.append(MotionEvent.actionToString(motionEvent.getAction()));
+ msg.append(", historySize=");
+ msg.append(motionEvent.getHistorySize());
+ }
+ msg.append(", handled=");
+ msg.append(handled);
+ msg.append(", received at +");
+ msg.append((mInputEventReceiveTimeNanos - eventTime) * 0.000001f);
+ if (mInputEventDeliverTimeNanos != 0) {
+ msg.append("ms, delivered at +");
+ msg.append((mInputEventDeliverTimeNanos - eventTime) * 0.000001f);
+ }
+ if (mInputEventDeliverPostImeTimeNanos != 0) {
+ msg.append("ms, delivered post IME at +");
+ msg.append((mInputEventDeliverPostImeTimeNanos - eventTime) * 0.000001f);
+ }
+ msg.append("ms, finished at +");
+ msg.append((now - eventTime) * 0.000001f);
+ msg.append("ms.");
+ Log.d(TAG, msg.toString());
+ }
+
+ mFinishedCallback.finished(handled);
+ mFinishedCallback = null;
}
/**
@@ -2313,6 +2412,18 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverPointerEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ if (event.isTouchEvent()) {
+ mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ } else {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
@@ -2329,7 +2440,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (isDown) {
ensureTouchMode(true);
}
- if(Config.LOGV) {
+ if(false) {
captureMotionLog("captureDispatchPointer", event);
}
@@ -2407,7 +2518,7 @@ public final class ViewRoot extends Handler implements ViewParent,
private void finishMotionEvent(MotionEvent event, boolean sendDone, boolean handled) {
event.recycle();
if (sendDone) {
- finishInputEvent(handled);
+ finishInputEvent(event, handled);
}
if (LOCAL_LOGV || WATCH_POINTER) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -2417,8 +2528,16 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverTrackballEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishMotionEvent(event, sendDone, false);
@@ -2547,6 +2666,14 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ }
+
final int source = event.getSource();
final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
@@ -2782,6 +2909,14 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverTimeNanos = System.nanoTime();
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onKeyEvent(event, 0);
+ }
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
@@ -2828,6 +2963,10 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
+ if (ViewDebug.DEBUG_LATENCY) {
+ mInputEventDeliverPostImeTimeNanos = System.nanoTime();
+ }
+
// If the view went away, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
@@ -2840,7 +2979,7 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
- if (Config.LOGV) {
+ if (false) {
captureKeyLog("captureDispatchKeyEvent", event);
}
@@ -2941,7 +3080,7 @@ public final class ViewRoot extends Handler implements ViewParent,
private void finishKeyEvent(KeyEvent event, boolean sendDone, boolean handled) {
if (sendDone) {
- finishInputEvent(handled);
+ finishInputEvent(event, handled);
}
}
@@ -3232,16 +3371,19 @@ public final class ViewRoot extends Handler implements ViewParent,
sendMessage(msg);
}
+ private long mInputEventReceiveTimeNanos;
+ private long mInputEventDeliverTimeNanos;
+ private long mInputEventDeliverPostImeTimeNanos;
private InputQueue.FinishedCallback mFinishedCallback;
private final InputHandler mInputHandler = new InputHandler() {
public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
- startInputEvent(finishedCallback);
+ startInputEvent(event, finishedCallback);
dispatchKey(event, true);
}
public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
- startInputEvent(finishedCallback);
+ startInputEvent(event, finishedCallback);
dispatchMotion(event, true);
}
};
@@ -3388,6 +3530,14 @@ public final class ViewRoot extends Handler implements ViewParent,
public void childDrawableStateChanged(View child) {
}
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mView == null) {
+ return false;
+ }
+ AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event);
+ return true;
+ }
+
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index a4c4544..8336959 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -19,7 +19,6 @@ package android.view;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.AndroidRuntimeException;
-import android.util.Config;
import android.util.Log;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
@@ -102,7 +101,7 @@ public class WindowManagerImpl implements WindowManager {
private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
{
- if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);
+ if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 334c68e..3d19380 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -81,6 +81,8 @@ public interface WindowManagerPolicy {
public final static int FLAG_INJECTED = 0x01000000;
public final static int FLAG_TRUSTED = 0x02000000;
+ public final static int FLAG_FILTERED = 0x04000000;
+ public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
public final static int FLAG_WOKE_HERE = 0x10000000;
public final static int FLAG_BRIGHT_HERE = 0x20000000;
@@ -394,6 +396,12 @@ public interface WindowManagerPolicy {
LocalPowerManager powerManager);
/**
+ * Called by window manager once it has the initial, default native
+ * display dimensions.
+ */
+ public void setInitialDisplaySize(int width, int height);
+
+ /**
* Check permissions when adding a window.
*
* @param attrs The window's LayoutParams.
@@ -808,6 +816,13 @@ public interface WindowManagerPolicy {
boolean displayEnabled);
/**
+ * Return the currently locked screen rotation, if any. Return
+ * Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, or
+ * Surface.ROTATION_270 if locked; return -1 if not locked.
+ */
+ public int getLockedRotationLw();
+
+ /**
* Called when the system is mostly done booting to determine whether
* the system should go into safe mode.
*/
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 62d3e6a..d128b57 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -21,7 +21,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.util.Config;
import android.util.Log;
import android.util.Slog;
@@ -47,7 +46,7 @@ import android.util.Slog;
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG || Config.DEBUG;
+ private static final boolean localLOGV = DEBUG || false;
private SensorManager mSensorManager;
private boolean mEnabled;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index fc61700..11c9392 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -21,13 +21,26 @@ import android.os.Parcelable;
import android.text.TextUtils;
import java.util.ArrayList;
-import java.util.List;
/**
* This class represents accessibility events that are sent by the system when
* something notable happens in the user interface. For example, when a
* {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
* <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * a record for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally add a record for itself before dispatching a similar
+ * request to its parent. A parent can also choose not to respect the request for sending
+ * an event. The accessibility event is sent by the topmost view in the view tree.
+ * Therefore, an {@link android.accessibilityservice.AccessibilityService} can explore
+ * all records in an accessibility event to obtain more information about the context
+ * in which the event was fired.
+ * <p>
+ * A client can add, remove, and modify records. The getters and setters for individual
+ * properties operate on the current record which can be explicitly set by the client. By
+ * default current is the first record. Thus, querying a record would require setting
+ * it as the current one and interacting with the property getters and setters.
+ * <p>
* This class represents various semantically different accessibility event
* types. Each event type has associated a set of related properties. In other
* words, each event type is characterized via a subset of the properties exposed
@@ -145,7 +158,7 @@ import java.util.List;
* @see android.view.accessibility.AccessibilityManager
* @see android.accessibilityservice.AccessibilityService
*/
-public final class AccessibilityEvent implements Parcelable {
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
/**
* Invalid selection/focus position.
@@ -159,7 +172,12 @@ public final class AccessibilityEvent implements Parcelable {
*
* @see #getBeforeText()
* @see #getText()
+ * </br>
+ * Note: This constant is no longer needed since there
+ * is no limit on the length of text that is contained
+ * in an accessibility event anymore.
*/
+ @Deprecated
public static final int MAX_TEXT_LENGTH = 500;
/**
@@ -202,6 +220,26 @@ public final class AccessibilityEvent implements Parcelable {
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
/**
+ * Represents the event of a hover enter over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+ /**
+ * Represents the event of a hover exit over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+ /**
+ * Represents the event of starting a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+ /**
+ * Represents the event of ending a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -214,116 +252,53 @@ public final class AccessibilityEvent implements Parcelable {
*/
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
- private static final int MAX_POOL_SIZE = 2;
+ private static final int MAX_POOL_SIZE = 10;
private static final Object mPoolLock = new Object();
private static AccessibilityEvent sPool;
private static int sPoolSize;
- private static final int CHECKED = 0x00000001;
- private static final int ENABLED = 0x00000002;
- private static final int PASSWORD = 0x00000004;
- private static final int FULL_SCREEN = 0x00000080;
-
private AccessibilityEvent mNext;
+ private boolean mIsInPool;
private int mEventType;
- private int mBooleanProperties;
- private int mCurrentItemIndex;
- private int mItemCount;
- private int mFromIndex;
- private int mAddedCount;
- private int mRemovedCount;
-
- private long mEventTime;
-
- private CharSequence mClassName;
private CharSequence mPackageName;
- private CharSequence mContentDescription;
- private CharSequence mBeforeText;
-
- private Parcelable mParcelableData;
-
- private final List<CharSequence> mText = new ArrayList<CharSequence>();
+ private long mEventTime;
- private boolean mIsInPool;
+ private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
/*
* Hide constructor from clients.
*/
private AccessibilityEvent() {
- mCurrentItemIndex = INVALID_POSITION;
- }
-
- /**
- * Gets if the source is checked.
- *
- * @return True if the view is checked, false otherwise.
- */
- public boolean isChecked() {
- return getBooleanProperty(CHECKED);
- }
-
- /**
- * Sets if the source is checked.
- *
- * @param isChecked True if the view is checked, false otherwise.
- */
- public void setChecked(boolean isChecked) {
- setBooleanProperty(CHECKED, isChecked);
- }
- /**
- * Gets if the source is enabled.
- *
- * @return True if the view is enabled, false otherwise.
- */
- public boolean isEnabled() {
- return getBooleanProperty(ENABLED);
- }
-
- /**
- * Sets if the source is enabled.
- *
- * @param isEnabled True if the view is enabled, false otherwise.
- */
- public void setEnabled(boolean isEnabled) {
- setBooleanProperty(ENABLED, isEnabled);
- }
-
- /**
- * Gets if the source is a password field.
- *
- * @return True if the view is a password field, false otherwise.
- */
- public boolean isPassword() {
- return getBooleanProperty(PASSWORD);
}
/**
- * Sets if the source is a password field.
+ * Gets the number of records contained in the event.
*
- * @param isPassword True if the view is a password field, false otherwise.
+ * @return The number of records.
*/
- public void setPassword(boolean isPassword) {
- setBooleanProperty(PASSWORD, isPassword);
+ public int getRecordCount() {
+ return mRecords.size();
}
/**
- * Sets if the source is taking the entire screen.
+ * Appends an {@link AccessibilityRecord} to the end of event records.
*
- * @param isFullScreen True if the source is full screen, false otherwise.
+ * @param record The record to append.
*/
- public void setFullScreen(boolean isFullScreen) {
- setBooleanProperty(FULL_SCREEN, isFullScreen);
+ public void appendRecord(AccessibilityRecord record) {
+ mRecords.add(record);
}
/**
- * Gets if the source is taking the entire screen.
+ * Gets the records at a given index.
*
- * @return True if the source is full screen, false otherwise.
+ * @param index The index.
+ * @return The records at the specified index.
*/
- public boolean isFullScreen() {
- return getBooleanProperty(FULL_SCREEN);
+ public AccessibilityRecord getRecord(int index) {
+ return mRecords.get(index);
}
/**
@@ -345,96 +320,6 @@ public final class AccessibilityEvent implements Parcelable {
}
/**
- * Gets the number of items that can be visited.
- *
- * @return The number of items.
- */
- public int getItemCount() {
- return mItemCount;
- }
-
- /**
- * Sets the number of items that can be visited.
- *
- * @param itemCount The number of items.
- */
- public void setItemCount(int itemCount) {
- mItemCount = itemCount;
- }
-
- /**
- * Gets the index of the source in the list of items the can be visited.
- *
- * @return The current item index.
- */
- public int getCurrentItemIndex() {
- return mCurrentItemIndex;
- }
-
- /**
- * Sets the index of the source in the list of items that can be visited.
- *
- * @param currentItemIndex The current item index.
- */
- public void setCurrentItemIndex(int currentItemIndex) {
- mCurrentItemIndex = currentItemIndex;
- }
-
- /**
- * Gets the index of the first character of the changed sequence.
- *
- * @return The index of the first character.
- */
- public int getFromIndex() {
- return mFromIndex;
- }
-
- /**
- * Sets the index of the first character of the changed sequence.
- *
- * @param fromIndex The index of the first character.
- */
- public void setFromIndex(int fromIndex) {
- mFromIndex = fromIndex;
- }
-
- /**
- * Gets the number of added characters.
- *
- * @return The number of added characters.
- */
- public int getAddedCount() {
- return mAddedCount;
- }
-
- /**
- * Sets the number of added characters.
- *
- * @param addedCount The number of added characters.
- */
- public void setAddedCount(int addedCount) {
- mAddedCount = addedCount;
- }
-
- /**
- * Gets the number of removed characters.
- *
- * @return The number of removed characters.
- */
- public int getRemovedCount() {
- return mRemovedCount;
- }
-
- /**
- * Sets the number of removed characters.
- *
- * @param removedCount The number of removed characters.
- */
- public void setRemovedCount(int removedCount) {
- mRemovedCount = removedCount;
- }
-
- /**
* Gets the time in which this event was sent.
*
* @return The event time.
@@ -453,24 +338,6 @@ public final class AccessibilityEvent implements Parcelable {
}
/**
- * Gets the class name of the source.
- *
- * @return The class name.
- */
- public CharSequence getClassName() {
- return mClassName;
- }
-
- /**
- * Sets the class name of the source.
- *
- * @param className The lass name.
- */
- public void setClassName(CharSequence className) {
- mClassName = className;
- }
-
- /**
* Gets the package name of the source.
*
* @return The package name.
@@ -489,70 +356,6 @@ public final class AccessibilityEvent implements Parcelable {
}
/**
- * Gets the text of the event. The index in the list represents the priority
- * of the text. Specifically, the lower the index the higher the priority.
- *
- * @return The text.
- */
- public List<CharSequence> getText() {
- return mText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @return The text before the change.
- */
- public CharSequence getBeforeText() {
- return mBeforeText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @param beforeText The text before the change.
- */
- public void setBeforeText(CharSequence beforeText) {
- mBeforeText = beforeText;
- }
-
- /**
- * Gets the description of the source.
- *
- * @return The description.
- */
- public CharSequence getContentDescription() {
- return mContentDescription;
- }
-
- /**
- * Sets the description of the source.
- *
- * @param contentDescription The description.
- */
- public void setContentDescription(CharSequence contentDescription) {
- mContentDescription = contentDescription;
- }
-
- /**
- * Gets the {@link Parcelable} data.
- *
- * @return The parcelable data.
- */
- public Parcelable getParcelableData() {
- return mParcelableData;
- }
-
- /**
- * Sets the {@link Parcelable} data of the event.
- *
- * @param parcelableData The parcelable data.
- */
- public void setParcelableData(Parcelable parcelableData) {
- mParcelableData = parcelableData;
- }
-
- /**
* Returns a cached instance if such is available or a new one is
* instantiated with type property set.
*
@@ -590,11 +393,11 @@ public final class AccessibilityEvent implements Parcelable {
* <p>
* <b>Note: You must not touch the object after calling this function.</b>
*/
+ @Override
public void recycle() {
if (mIsInPool) {
return;
}
-
clear();
synchronized (mPoolLock) {
if (sPoolSize <= MAX_POOL_SIZE) {
@@ -609,44 +412,15 @@ public final class AccessibilityEvent implements Parcelable {
/**
* Clears the state of this instance.
*/
- private void clear() {
+ @Override
+ protected void clear() {
+ super.clear();
mEventType = 0;
- mBooleanProperties = 0;
- mCurrentItemIndex = INVALID_POSITION;
- mItemCount = 0;
- mFromIndex = 0;
- mAddedCount = 0;
- mRemovedCount = 0;
- mEventTime = 0;
- mClassName = null;
mPackageName = null;
- mContentDescription = null;
- mBeforeText = null;
- mParcelableData = null;
- mText.clear();
- }
-
- /**
- * Gets the value of a boolean property.
- *
- * @param property The property.
- * @return The value.
- */
- private boolean getBooleanProperty(int property) {
- return (mBooleanProperties & property) == property;
- }
-
- /**
- * Sets a boolean property.
- *
- * @param property The property.
- * @param value The value.
- */
- private void setBooleanProperty(int property, boolean value) {
- if (value) {
- mBooleanProperties |= property;
- } else {
- mBooleanProperties &= ~property;
+ mEventTime = 0;
+ while (!mRecords.isEmpty()) {
+ AccessibilityRecord record = mRecords.remove(0);
+ record.recycle();
}
}
@@ -657,38 +431,82 @@ public final class AccessibilityEvent implements Parcelable {
*/
public void initFromParcel(Parcel parcel) {
mEventType = parcel.readInt();
- mBooleanProperties = parcel.readInt();
- mCurrentItemIndex = parcel.readInt();
- mItemCount = parcel.readInt();
- mFromIndex = parcel.readInt();
- mAddedCount = parcel.readInt();
- mRemovedCount = parcel.readInt();
- mEventTime = parcel.readLong();
- mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mParcelableData = parcel.readParcelable(null);
- parcel.readList(mText, null);
+ mEventTime = parcel.readLong();
+ readAccessibilityRecordFromParcel(this, parcel);
+
+ // Read the records.
+ final int recordCount = parcel.readInt();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = AccessibilityRecord.obtain();
+ readAccessibilityRecordFromParcel(record, parcel);
+ mRecords.add(record);
+ }
}
+ /**
+ * Reads an {@link AccessibilityRecord} from a parcel.
+ *
+ * @param record The record to initialize.
+ * @param parcel The parcel to read from.
+ */
+ private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+ Parcel parcel) {
+ record.mBooleanProperties = parcel.readInt();
+ record.mCurrentItemIndex = parcel.readInt();
+ record.mItemCount = parcel.readInt();
+ record.mFromIndex = parcel.readInt();
+ record.mAddedCount = parcel.readInt();
+ record.mRemovedCount = parcel.readInt();
+ record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mParcelableData = parcel.readParcelable(null);
+ parcel.readList(record.mText, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mEventType);
- parcel.writeInt(mBooleanProperties);
- parcel.writeInt(mCurrentItemIndex);
- parcel.writeInt(mItemCount);
- parcel.writeInt(mFromIndex);
- parcel.writeInt(mAddedCount);
- parcel.writeInt(mRemovedCount);
- parcel.writeLong(mEventTime);
- TextUtils.writeToParcel(mClassName, parcel, 0);
TextUtils.writeToParcel(mPackageName, parcel, 0);
- TextUtils.writeToParcel(mContentDescription, parcel, 0);
- TextUtils.writeToParcel(mBeforeText, parcel, 0);
- parcel.writeParcelable(mParcelableData, flags);
- parcel.writeList(mText);
+ parcel.writeLong(mEventTime);
+ writeAccessibilityRecordToParcel(this, parcel, flags);
+
+ // Write the records.
+ final int recordCount = getRecordCount();
+ parcel.writeInt(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ writeAccessibilityRecordToParcel(record, parcel, flags);
+ }
+ }
+
+ /**
+ * Writes an {@link AccessibilityRecord} to a parcel.
+ *
+ * @param record The record to write.
+ * @param parcel The parcel to which to write.
+ */
+ private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+ int flags) {
+ parcel.writeInt(record.mBooleanProperties);
+ parcel.writeInt(record.mCurrentItemIndex);
+ parcel.writeInt(record.mItemCount);
+ parcel.writeInt(record.mFromIndex);
+ parcel.writeInt(record.mAddedCount);
+ parcel.writeInt(record.mRemovedCount);
+ TextUtils.writeToParcel(record.mClassName, parcel, flags);
+ TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+ TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+ parcel.writeParcelable(record.mParcelableData, flags);
+ parcel.writeList(record.mText);
}
+ /**
+ * {@inheritDoc}
+ */
public int describeContents() {
return 0;
}
@@ -696,24 +514,21 @@ public final class AccessibilityEvent implements Parcelable {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(super.toString());
builder.append("; EventType: " + mEventType);
builder.append("; EventTime: " + mEventTime);
- builder.append("; ClassName: " + mClassName);
builder.append("; PackageName: " + mPackageName);
- builder.append("; Text: " + mText);
- builder.append("; ContentDescription: " + mContentDescription);
- builder.append("; ItemCount: " + mItemCount);
- builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
- builder.append("; IsEnabled: " + isEnabled());
- builder.append("; IsPassword: " + isPassword());
- builder.append("; IsChecked: " + isChecked());
- builder.append("; IsFullScreen: " + isFullScreen());
- builder.append("; BeforeText: " + mBeforeText);
- builder.append("; FromIndex: " + mFromIndex);
- builder.append("; AddedCount: " + mAddedCount);
- builder.append("; RemovedCount: " + mRemovedCount);
- builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" \n{\n");
+ builder.append(super.toString());
+ builder.append("\n");
+ for (int i = 0; i < mRecords.size(); i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ builder.append(" Record ");
+ builder.append(i);
+ builder.append(":");
+ builder.append(record.toString());
+ builder.append("\n");
+ }
+ builder.append("}\n");
return builder.toString();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index f406da9..dd77193 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -16,8 +16,8 @@
package android.view.accessibility;
-import static android.util.Config.LOGV;
-
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Binder;
@@ -46,6 +46,8 @@ import java.util.List;
* @see android.content.Context#getSystemService
*/
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
private static final String LOG_TAG = "AccessibilityManager";
static final Object sInstanceSync = new Object();
@@ -166,7 +168,7 @@ public final class AccessibilityManager {
long identityToken = Binder.clearCallingIdentity();
doRecycle = mService.sendAccessibilityEvent(event);
Binder.restoreCallingIdentity(identityToken);
- if (LOGV) {
+ if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
} catch (RemoteException re) {
@@ -187,7 +189,7 @@ public final class AccessibilityManager {
}
try {
mService.interrupt();
- if (LOGV) {
+ if (DEBUG) {
Log.i(LOG_TAG, "Requested interrupt from all services");
}
} catch (RemoteException re) {
@@ -204,7 +206,33 @@ public final class AccessibilityManager {
List<ServiceInfo> services = null;
try {
services = mService.getAccessibilityServiceList();
- if (LOGV) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ return Collections.unmodifiableList(services);
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackType The type of feedback.
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+ * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+ * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+ * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+ */
+ public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ List<ServiceInfo> services = null;
+ try {
+ services = mService.getEnabledAccessibilityServiceList(feedbackType);
+ if (DEBUG) {
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
} catch (RemoteException re) {
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 0000000..e095f43
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an accessibility event. This class encapsulates
+ * the information for a {@link android.view.View}. Note that not all properties
+ * are applicable to all view types. For detailed information please refer to
+ * {@link AccessibilityEvent}.
+ *
+ * @see AccessibilityEvent
+ */
+public class AccessibilityRecord {
+
+ private static final int INVALID_POSITION = -1;
+
+ private static final int PROPERTY_CHECKED = 0x00000001;
+ private static final int PROPERTY_ENABLED = 0x00000002;
+ private static final int PROPERTY_PASSWORD = 0x00000004;
+ private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+
+ private static final int MAX_POOL_SIZE = 10;
+ private static final Object mPoolLock = new Object();
+ private static AccessibilityRecord sPool;
+ private static int sPoolSize;
+
+ private AccessibilityRecord mNext;
+ private boolean mIsInPool;
+
+ protected int mBooleanProperties;
+ protected int mCurrentItemIndex;
+ protected int mItemCount;
+ protected int mFromIndex;
+ protected int mAddedCount;
+ protected int mRemovedCount;
+
+ protected CharSequence mClassName;
+ protected CharSequence mContentDescription;
+ protected CharSequence mBeforeText;
+ protected Parcelable mParcelableData;
+
+ protected final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+ /*
+ * Hide constructor.
+ */
+ protected AccessibilityRecord() {
+
+ }
+
+ /**
+ * Gets if the source is checked.
+ *
+ * @return True if the view is checked, false otherwise.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets if the source is checked.
+ *
+ * @param isChecked True if the view is checked, false otherwise.
+ */
+ public void setChecked(boolean isChecked) {
+ setBooleanProperty(PROPERTY_CHECKED, isChecked);
+ }
+
+ /**
+ * Gets if the source is enabled.
+ *
+ * @return True if the view is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets if the source is enabled.
+ *
+ * @param isEnabled True if the view is enabled, false otherwise.
+ */
+ public void setEnabled(boolean isEnabled) {
+ setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+ }
+
+ /**
+ * Gets if the source is a password field.
+ *
+ * @return True if the view is a password field, false otherwise.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets if the source is a password field.
+ *
+ * @param isPassword True if the view is a password field, false otherwise.
+ */
+ public void setPassword(boolean isPassword) {
+ setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+ }
+
+ /**
+ * Sets if the source is taking the entire screen.
+ *
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ */
+ public void setFullScreen(boolean isFullScreen) {
+ setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+ }
+
+ /**
+ * Gets if the source is taking the entire screen.
+ *
+ * @return True if the source is full screen, false otherwise.
+ */
+ public boolean isFullScreen() {
+ return getBooleanProperty(PROPERTY_FULL_SCREEN);
+ }
+
+ /**
+ * Gets the number of items that can be visited.
+ *
+ * @return The number of items.
+ */
+ public int getItemCount() {
+ return mItemCount;
+ }
+
+ /**
+ * Sets the number of items that can be visited.
+ *
+ * @param itemCount The number of items.
+ */
+ public void setItemCount(int itemCount) {
+ mItemCount = itemCount;
+ }
+
+ /**
+ * Gets the index of the source in the list of items the can be visited.
+ *
+ * @return The current item index.
+ */
+ public int getCurrentItemIndex() {
+ return mCurrentItemIndex;
+ }
+
+ /**
+ * Sets the index of the source in the list of items that can be visited.
+ *
+ * @param currentItemIndex The current item index.
+ */
+ public void setCurrentItemIndex(int currentItemIndex) {
+ mCurrentItemIndex = currentItemIndex;
+ }
+
+ /**
+ * Gets the index of the first character of the changed sequence.
+ *
+ * @return The index of the first character.
+ */
+ public int getFromIndex() {
+ return mFromIndex;
+ }
+
+ /**
+ * Sets the index of the first character of the changed sequence.
+ *
+ * @param fromIndex The index of the first character.
+ */
+ public void setFromIndex(int fromIndex) {
+ mFromIndex = fromIndex;
+ }
+
+ /**
+ * Gets the number of added characters.
+ *
+ * @return The number of added characters.
+ */
+ public int getAddedCount() {
+ return mAddedCount;
+ }
+
+ /**
+ * Sets the number of added characters.
+ *
+ * @param addedCount The number of added characters.
+ */
+ public void setAddedCount(int addedCount) {
+ mAddedCount = addedCount;
+ }
+
+ /**
+ * Gets the number of removed characters.
+ *
+ * @return The number of removed characters.
+ */
+ public int getRemovedCount() {
+ return mRemovedCount;
+ }
+
+ /**
+ * Sets the number of removed characters.
+ *
+ * @param removedCount The number of removed characters.
+ */
+ public void setRemovedCount(int removedCount) {
+ mRemovedCount = removedCount;
+ }
+
+ /**
+ * Gets the class name of the source.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class name of the source.
+ *
+ * @param className The lass name.
+ */
+ public void setClassName(CharSequence className) {
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of the event. The index in the list represents the priority
+ * of the text. Specifically, the lower the index the higher the priority.
+ *
+ * @return The text.
+ */
+ public List<CharSequence> getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @return The text before the change.
+ */
+ public CharSequence getBeforeText() {
+ return mBeforeText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @param beforeText The text before the change.
+ */
+ public void setBeforeText(CharSequence beforeText) {
+ mBeforeText = beforeText;
+ }
+
+ /**
+ * Gets the description of the source.
+ *
+ * @return The description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the description of the source.
+ *
+ * @param contentDescription The description.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the {@link Parcelable} data.
+ *
+ * @return The parcelable data.
+ */
+ public Parcelable getParcelableData() {
+ return mParcelableData;
+ }
+
+ /**
+ * Sets the {@link Parcelable} data of the event.
+ *
+ * @param parcelableData The parcelable data.
+ */
+ public void setParcelableData(Parcelable parcelableData) {
+ mParcelableData = parcelableData;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ public boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) == property;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ protected static AccessibilityRecord obtain() {
+ synchronized (mPoolLock) {
+ if (sPool != null) {
+ AccessibilityRecord record = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ record.mNext = null;
+ record.mIsInPool = false;
+ return record;
+ }
+ return new AccessibilityRecord();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ return;
+ }
+ clear();
+ synchronized (mPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ protected void clear() {
+ mBooleanProperties = 0;
+ mCurrentItemIndex = INVALID_POSITION;
+ mItemCount = 0;
+ mFromIndex = 0;
+ mAddedCount = 0;
+ mRemovedCount = 0;
+ mClassName = null;
+ mContentDescription = null;
+ mBeforeText = null;
+ mParcelableData = null;
+ mText.clear();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(" [ ClassName: " + mClassName);
+ builder.append("; Text: " + mText);
+ builder.append("; ContentDescription: " + mContentDescription);
+ builder.append("; ItemCount: " + mItemCount);
+ builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+ builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
+ builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
+ builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
+ builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+ builder.append("; BeforeText: " + mBeforeText);
+ builder.append("; FromIndex: " + mFromIndex);
+ builder.append("; AddedCount: " + mAddedCount);
+ builder.append("; RemovedCount: " + mRemovedCount);
+ builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" ]");
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7633569..aaaae32 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -35,5 +35,7 @@ interface IAccessibilityManager {
List<ServiceInfo> getAccessibilityServiceList();
+ List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
+
void interrupt();
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index e644045..dd2d00d 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -644,7 +644,7 @@ public class BaseInputConnection implements InputConnection {
lp.println("Composing text:");
TextUtils.dumpSpans(text, lp, " ");
}
-
+
// Position the cursor appropriately, so that after replacing the
// desired range of text it will be located in the correct spot.
// This allows us to deal with filters performing edits on the text
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ea9e402..a6639d1 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -26,8 +26,9 @@ import android.view.KeyEvent;
* is used to perform such things as reading text around the cursor,
* committing text to the text box, and sending raw key events to the application.
*
- * <p>Implementations of this interface should generally be done by
- * subclassing {@link BaseInputConnection}.
+ * <p>Applications should never directly implement this interface, but instead
+ * subclass from {@link BaseInputConnection}. This will ensure that the
+ * application does not break when new methods are added to the interface.
*/
public interface InputConnection {
/**
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 4d9d51e..690ea85 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -58,8 +58,7 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.getCursorCapsMode(reqModes);
}
- public ExtractedText getExtractedText(ExtractedTextRequest request,
- int flags) {
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
return mTarget.getExtractedText(request, flags);
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 32eec9f..1f7441d 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -322,7 +322,12 @@ public final class InputMethodInfo implements Parcelable {
InputMethodInfo obj = (InputMethodInfo) o;
return mId.equals(obj.mId);
}
-
+
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
/**
* Used to package this object into a {@link Parcel}.
*
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a39c7c7..eef2a33 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -505,6 +505,13 @@ public final class InputMethodManager {
}
}
+ /**
+ * Returns a list of enabled input method subtypes for the specified input method info.
+ * @param imi An input method info whose subtypes list will be returned.
+ * @param allowsImplicitlySelectedSubtypes A boolean flag to allow to return the implicitly
+ * selected subtypes. If an input method info doesn't have enabled subtypes, the framework
+ * will implicitly enable subtypes according to the current system language.
+ */
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
try {
@@ -1429,16 +1436,26 @@ public final class InputMethodManager {
}
}
- public void showInputMethodAndSubtypeEnabler(String topId) {
+ /**
+ * Show the settings for enabling subtypes of the specified input method.
+ * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
+ * subtypes of all input methods will be shown.
+ */
+ public void showInputMethodAndSubtypeEnabler(String imiId) {
synchronized (mH) {
try {
- mService.showInputMethodAndSubtypeEnablerFromClient(mClient, topId);
+ mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
}
+ /**
+ * Returns the current input method subtype. This subtype is one of the subtypes in
+ * the current input method. This method returns null when the current input method doesn't
+ * have any input method subtype.
+ */
public InputMethodSubtype getCurrentInputMethodSubtype() {
synchronized (mH) {
try {
@@ -1450,6 +1467,12 @@ public final class InputMethodManager {
}
}
+ /**
+ * Switch to a new input method subtype of the current input method.
+ * @param subtype A new input method subtype to switch.
+ * @return true if the current subtype was successfully switched. When the specified subtype is
+ * null, this method returns false.
+ */
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
synchronized (mH) {
try {
@@ -1461,6 +1484,9 @@ public final class InputMethodManager {
}
}
+ /**
+ * Returns a map of all shortcut input method info and their subtypes.
+ */
public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
synchronized (mH) {
HashMap<InputMethodInfo, List<InputMethodSubtype>> ret =
@@ -1493,6 +1519,15 @@ public final class InputMethodManager {
}
}
+ /**
+ * Force switch to the last used input method and subtype. If the last input method didn't have
+ * any subtypes, the framework will simply switch to the last input method with no subtype
+ * specified.
+ * @param imeToken Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @return true if the current input method and subtype was successfully switched to the last
+ * used input method and subtype.
+ */
public boolean switchToLastInputMethod(IBinder imeToken) {
synchronized (mH) {
try {
@@ -1504,6 +1539,17 @@ public final class InputMethodManager {
}
}
+ public InputMethodSubtype getLastInputMethodSubtype() {
+ synchronized (mH) {
+ try {
+ return mService.getLastInputMethodSubtype();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ return null;
+ }
+ }
+ }
+
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index c7a7374..6306274 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1043,13 +1043,16 @@ class BrowserFrame extends Handler {
// These ids need to be in sync with enum rawResId in PlatformBridge.h
private static final int NODOMAIN = 1;
private static final int LOADERROR = 2;
- private static final int DRAWABLEDIR = 3;
+ /* package */ static final int DRAWABLEDIR = 3;
private static final int FILE_UPLOAD_LABEL = 4;
private static final int RESET_LABEL = 5;
private static final int SUBMIT_LABEL = 6;
private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
- String getRawResFilename(int id) {
+ private String getRawResFilename(int id) {
+ return getRawResFilename(id, mContext);
+ }
+ /* package */ static String getRawResFilename(int id, Context context) {
int resid;
switch (id) {
case NODOMAIN:
@@ -1066,19 +1069,19 @@ class BrowserFrame extends Handler {
break;
case FILE_UPLOAD_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.upload_file);
case RESET_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.reset);
case SUBMIT_LABEL:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.submit);
case FILE_UPLOAD_NO_FILE_CHOSEN:
- return mContext.getResources().getString(
+ return context.getResources().getString(
com.android.internal.R.string.no_file_chosen);
default:
@@ -1086,7 +1089,7 @@ class BrowserFrame extends Handler {
return "";
}
TypedValue value = new TypedValue();
- mContext.getResources().getValue(resid, value, true);
+ context.getResources().getValue(resid, value, true);
if (id == DRAWABLEDIR) {
String path = value.string.toString();
int index = path.lastIndexOf('/');
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 060c0bb..d1b8cfc 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -184,7 +184,9 @@ class HTML5VideoViewProxy extends Handler
// we need to pause the old one and re-create a new media player
// inside the HTML5VideoView.
if (mHTML5VideoView != null) {
- mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
+ if (!backFromFullScreenMode) {
+ mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
+ }
// release the media player to avoid finalize error
mHTML5VideoView.release();
}
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index ccf3d6b..7c0e478 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -90,9 +90,7 @@ public class WebHistoryItem implements Cloneable {
* another item, the identifiers will be the same even if they are not the
* same object.
* @return The id for this item.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public int getId() {
return mId;
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 2b507fd..71d6080 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -43,10 +43,8 @@ public class WebSettings {
* SINGLE_COLUMN moves all content into one column that is the width of the
* view.
* NARROW_COLUMNS makes all columns no wider than the screen if possible.
- * @deprecated This enum is now obsolete.
*/
// XXX: These must match LayoutAlgorithm in Settings.h in WebCore.
- @Deprecated
public enum LayoutAlgorithm {
NORMAL,
SINGLE_COLUMN,
@@ -512,18 +510,14 @@ public class WebSettings {
/**
* Enables dumping the pages navigation cache to a text file.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public void setNavDump(boolean enabled) {
mNavDump = enabled;
}
/**
* Returns true if dumping the navigation cache is enabled.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public boolean getNavDump() {
return mNavDump;
}
@@ -661,9 +655,7 @@ public class WebSettings {
* Set whether the WebView uses its background for over scroll background.
* If true, it will use the WebView's background. If false, it will use an
* internal pattern. Default is true.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
mUseWebViewBackgroundForOverscroll = view;
}
@@ -671,9 +663,7 @@ public class WebSettings {
/**
* Returns true if this WebView uses WebView's background instead of
* internal pattern for over scroll background.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public boolean getUseWebViewBackgroundForOverscrollBackground() {
return mUseWebViewBackgroundForOverscroll;
}
@@ -876,9 +866,7 @@ public class WebSettings {
* WebView.
* @param l A LayoutAlgorithm enum specifying the algorithm to use.
* @see WebSettings.LayoutAlgorithm
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
// XXX: This will only be affective if libwebcore was built with
// ANDROID_LAYOUT defined.
@@ -893,9 +881,7 @@ public class WebSettings {
* @return LayoutAlgorithm enum value describing the layout algorithm
* being used.
* @see WebSettings.LayoutAlgorithm
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public synchronized LayoutAlgorithm getLayoutAlgorithm() {
return mLayoutAlgorithm;
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 0f24edc..7f4f103 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -51,8 +51,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -515,7 +515,6 @@ import junit.framework.Assert;
int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
}
- updateCursorControllerPositions();
}
@Override
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index cf83456..b5d0492 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
@@ -853,17 +854,13 @@ public class WebView extends AbsoluteLayout
private PictureListener mPictureListener;
/**
* Interface to listen for new pictures as they change.
- * @deprecated This interface is now obsolete.
*/
- @Deprecated
public interface PictureListener {
/**
* Notify the listener that the picture has changed.
* @param view The WebView that owns the picture.
* @param picture The new picture.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public void onNewPicture(WebView view, Picture picture);
}
@@ -874,8 +871,9 @@ public class WebView extends AbsoluteLayout
*/
public static final int UNKNOWN_TYPE = 0;
/**
- * HitTestResult for hitting a HTML::a tag
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int ANCHOR_TYPE = 1;
/**
* HitTestResult for hitting a phone number
@@ -894,8 +892,9 @@ public class WebView extends AbsoluteLayout
*/
public static final int IMAGE_TYPE = 5;
/**
- * HitTestResult for hitting a HTML::a tag which contains HTML::img
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int IMAGE_ANCHOR_TYPE = 6;
/**
* HitTestResult for hitting a HTML::a tag with src=http
@@ -1510,9 +1509,7 @@ public class WebView extends AbsoluteLayout
/**
* Enables platform notifications of data state and proxy changes.
- * @deprecated Obsolete - platform notifications are always enabled.
*/
- @Deprecated
public static void enablePlatformNotifications() {
Network.enablePlatformNotifications();
}
@@ -1520,9 +1517,7 @@ public class WebView extends AbsoluteLayout
/**
* If platform notifications are enabled, this should be called
* from the Activity's onPause() or onStop().
- * @deprecated Obsolete - platform notifications are always enabled.
*/
- @Deprecated
public static void disablePlatformNotifications() {
Network.disablePlatformNotifications();
}
@@ -1625,9 +1620,7 @@ public class WebView extends AbsoluteLayout
* @param dest The file to store the serialized picture data. Will be
* overwritten with this WebView's picture data.
* @return True if the picture was successfully saved.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public boolean savePicture(Bundle b, final File dest) {
if (dest == null || b == null) {
return false;
@@ -1689,9 +1682,7 @@ public class WebView extends AbsoluteLayout
* @param b A Bundle containing the saved display data.
* @param src The file where the picture data was stored.
* @return True if the picture was successfully restored.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public boolean restorePicture(Bundle b, File src) {
if (src == null || b == null) {
return false;
@@ -2393,6 +2384,8 @@ public class WebView extends AbsoluteLayout
*/
public void setTitleBarGravity(int gravity) {
mTitleGravity = gravity;
+ // force refresh
+ invalidate();
}
/**
@@ -3634,9 +3627,7 @@ public class WebView extends AbsoluteLayout
* Set the Picture listener. This is an interface used to receive
* notifications of a new Picture.
* @param listener An implementation of WebView.PictureListener.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public void setPictureListener(PictureListener listener) {
mPictureListener = listener;
}
@@ -4995,9 +4986,7 @@ public class WebView extends AbsoluteLayout
/**
* Use this method to put the WebView into text selection mode.
* Do not rely on this functionality; it will be deprecated in the future.
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public void emulateShiftHeld() {
setUpSelect(false, 0, 0);
}
@@ -7865,7 +7854,10 @@ public class WebView extends AbsoluteLayout
}
case WEBCORE_INITIALIZED_MSG_ID:
// nativeCreate sets mNativeClass to a non-zero value
- nativeCreate(msg.arg1);
+ String drawableDir = BrowserFrame.getRawResFilename(
+ BrowserFrame.DRAWABLEDIR, mContext);
+ AssetManager am = mContext.getAssets();
+ nativeCreate(msg.arg1, drawableDir, am);
break;
case UPDATE_TEXTFIELD_TEXT_MSG_ID:
// Make sure that the textfield is currently focused
@@ -8689,10 +8681,6 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
}
- /**
- * @deprecated This method is now obsolete.
- */
- @Deprecated
public void debugDump() {
nativeDebugDump();
mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
@@ -8760,7 +8748,7 @@ public class WebView extends AbsoluteLayout
private native Rect nativeCacheHitNodeBounds();
private native int nativeCacheHitNodePointer();
/* package */ native void nativeClearCursor();
- private native void nativeCreate(int ptr);
+ private native void nativeCreate(int ptr, String drawableDir, AssetManager am);
private native int nativeCursorFramePointer();
private native Rect nativeCursorNodeBounds();
private native int nativeCursorNodePointer();
diff --git a/core/java/android/webkit/webdriver/By.java b/core/java/android/webkit/webdriver/By.java
new file mode 100644
index 0000000..fa4fe74
--- /dev/null
+++ b/core/java/android/webkit/webdriver/By.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import java.util.List;
+
+/**
+ * Mechanism to locate elements within the DOM of the page.
+ * @hide
+ */
+public abstract class By {
+ public abstract WebElement findElement(WebElement element);
+ public abstract List<WebElement> findElements(WebElement element);
+
+ /**
+ * Locates an element by its HTML id attribute.
+ *
+ * @param id The HTML id attribute to look for.
+ * @return A By instance that locates elements by their HTML id attributes.
+ */
+ public static By id(final String id) {
+ throwIfNull(id);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementById(id);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsById(id); // Yes, it happens a lot.
+ }
+
+ @Override
+ public String toString() {
+ return "By.id: " + id;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by the matching the exact text on the HTML link.
+ *
+ * @param linkText The exact text to match against.
+ * @return A By instance that locates elements by the text displayed by
+ * the link.
+ */
+ public static By linkText(final String linkText) {
+ throwIfNull(linkText);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByLinkText(linkText);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByLinkText(linkText);
+ }
+
+ @Override
+ public String toString() {
+ return "By.linkText: " + linkText;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching partial part of the text displayed by an
+ * HTML link.
+ *
+ * @param linkText The text that should be contained by the text displayed
+ * on the link.
+ * @return A By instance that locates elements that contain the given link
+ * text.
+ */
+ public static By partialLinkText(final String linkText) {
+ throwIfNull(linkText);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByPartialLinkText(linkText);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByPartialLinkText(linkText);
+ }
+
+ @Override
+ public String toString() {
+ return "By.partialLinkText: " + linkText;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its HTML name attribute.
+ *
+ * @param name The value of the HTML name attribute.
+ * @return A By instance that locates elements by the HTML name attribute.
+ */
+ public static By name(final String name) {
+ throwIfNull(name);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByName(name);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByName(name);
+ }
+
+ @Override
+ public String toString() {
+ return "By.name: " + name;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its class name.
+ * @param className The class name
+ * @return A By instance that locates elements by their class name attribute.
+ */
+ public static By className(final String className) {
+ throwIfNull(className);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByClassName(className);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByClassName(className);
+ }
+
+ @Override
+ public String toString() {
+ return "By.className: " + className;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its css property.
+ *
+ * @param css The css property.
+ * @return A By instance that locates elements by their css property.
+ */
+ public static By css(final String css) {
+ throwIfNull(css);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByCss(css);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByCss(css);
+ }
+
+ @Override
+ public String toString() {
+ return "By.css: " + css;
+ }
+ };
+ }
+
+ /**
+ * Locates an element by matching its HTML tag name.
+ *
+ * @param tagName The HTML tag name to look for.
+ * @return A By instance that locates elements using the name of the
+ * HTML tag.
+ */
+ public static By tagName(final String tagName) {
+ throwIfNull(tagName);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByTagName(tagName);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByTagName(tagName);
+ }
+
+ @Override
+ public String toString() {
+ return "By.tagName: " + tagName;
+ }
+ };
+ }
+
+ /**
+ * Locates an element using an XPath expression.
+ *
+ * <p>When using XPath, be aware that this follows standard conventions: a
+ * search prefixed with "//" will search the entire document, not just the
+ * children of the current node. Use ".//" to limit your search to the
+ * children of this {@link android.webkit.webdriver.WebElement}.
+ *
+ * @param xpath The XPath expression to use.
+ * @return A By instance that locates elements using the given XPath.
+ */
+ public static By xpath(final String xpath) {
+ throwIfNull(xpath);
+ return new By() {
+ @Override
+ public WebElement findElement(WebElement element) {
+ return element.findElementByXPath(xpath);
+ }
+
+ @Override
+ public List<WebElement> findElements(WebElement element) {
+ return element.findElementsByXPath(xpath);
+ }
+
+ @Override
+ public String toString() {
+ return "By.xpath: " + xpath;
+ }
+ };
+ }
+
+ private static void throwIfNull(String argument) {
+ if (argument == null) {
+ throw new IllegalArgumentException(
+ "Cannot find elements with null locator.");
+ }
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java
new file mode 100644
index 0000000..618820d
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriver.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.os.Handler;
+import android.os.Message;
+import android.webkit.WebView;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Drives a web application by controlling the WebView. This class
+ * provides a DOM-like API allowing to get information about the page,
+ * navigate, and interact with the web application. This is particularly useful
+ * for testing a web application.
+ *
+ * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main
+ * thread, and invoked from another thread. Here is a sample usage:
+ *
+ * public class WebDriverStubActivity extends Activity {
+ * private WebDriver mDriver;
+ *
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * WebView view = new WebView(this);
+ * mDriver = new WebDriver(view);
+ * setContentView(view);
+ * }
+ *
+ *
+ * public WebDriver getDriver() {
+ * return mDriver;
+ * }
+ *}
+ *
+ * public class WebDriverTest extends
+ * ActivityInstrumentationTestCase2<WebDriverStubActivity>{
+ * private WebDriver mDriver;
+ *
+ * public WebDriverTest() {
+ * super(WebDriverStubActivity.class);
+ * }
+ *
+ * protected void setUp() throws Exception {
+ * super.setUp();
+ * mDriver = getActivity().getDriver();
+ * }
+ *
+ * public void testGoogle() {
+ * mDriver.get("http://google.com");
+ * WebElement searchBox = mDriver.findElement(By.name("q"));
+ * q.sendKeys("Cheese!");
+ * q.submit();
+ * assertTrue(mDriver.findElements(By.partialLinkText("Cheese")).size() > 0);
+ * }
+ *}
+ *
+ * @hide
+ */
+public class WebDriver {
+ // Timeout for page load in milliseconds.
+ private static final int LOADING_TIMEOUT = 30000;
+ // Timeout for executing JavaScript in the WebView in milliseconds.
+ private static final int JS_EXECUTION_TIMEOUT = 10000;
+
+ // Commands posted to the handler
+ private static final int CMD_GET_URL = 1;
+ private static final int CMD_EXECUTE_SCRIPT = 2;
+
+ private static final String ELEMENT_KEY = "ELEMENT";
+ private static final String STATUS = "status";
+ private static final String VALUE = "value";
+
+ private static final long MAIN_THREAD = Thread.currentThread().getId();
+
+ // This is updated by a callabck from JavaScript when the result is ready.
+ private String mJsResult;
+
+ // Used for synchronization
+ private final Object mSyncObject;
+
+ // Updated when the command is done executing in the main thread.
+ private volatile boolean mCommandDone;
+
+ private WebView mWebView;
+
+ // This WebElement represents the object document.documentElement
+ private WebElement mDocumentElement;
+
+ // This Handler runs in the main UI thread.
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == CMD_GET_URL) {
+ final String url = (String) msg.obj;
+ mWebView.loadUrl(url);
+ } else if (msg.what == CMD_EXECUTE_SCRIPT) {
+ mWebView.loadUrl("javascript:" + (String) msg.obj);
+ }
+ }
+ };
+
+ /**
+ * Error codes from the WebDriver wire protocol
+ * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
+ */
+ private enum ErrorCode {
+ SUCCESS(0),
+ NO_SUCH_ELEMENT(7),
+ NO_SUCH_FRAME(8),
+ UNKNOWN_COMMAND(9),
+ UNSUPPORTED_OPERATION(9), // Alias
+ STALE_ELEMENT_REFERENCE(10),
+ ELEMENT_NOT_VISISBLE(11),
+ INVALID_ELEMENT_STATE(12),
+ UNKNOWN_ERROR(13),
+ ELEMENT_NOT_SELECTABLE(15),
+ XPATH_LOOKUP_ERROR(19),
+ NO_SUCH_WINDOW(23),
+ INVALID_COOKIE_DOMAIN(24),
+ UNABLE_TO_SET_COOKIE(25),
+ MODAL_DIALOG_OPENED(26),
+ MODAL_DIALOG_OPEN(27),
+ SCRIPT_TIMEOUT(28);
+
+ private final int mCode;
+ private static ErrorCode[] values = ErrorCode.values();
+
+ ErrorCode(int code) {
+ this.mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ public static ErrorCode get(final int intValue) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].getCode() == intValue) {
+ return values[i];
+ }
+ }
+ throw new IllegalArgumentException(intValue
+ + " does not map to any ErrorCode.");
+ }
+ }
+
+ public WebDriver(WebView webview) {
+ this.mWebView = webview;
+ if (mWebView == null) {
+ throw new IllegalArgumentException("WebView cannot be null");
+ }
+ if (!mWebView.getSettings().getJavaScriptEnabled()) {
+ throw new RuntimeException("Javascript is disabled in the WebView. "
+ + "Enable it to use WebDriver");
+ }
+ shouldRunInMainThread(true);
+
+ mSyncObject = new Object();
+ this.mWebView = webview;
+ WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper(
+ webview.getWebChromeClient(), this);
+ mWebView.setWebChromeClient(chromeWrapper);
+ mWebView.addJavascriptInterface(new JavascriptResultReady(),
+ "webdriver");
+ }
+
+ /**
+ * Loads a URL in the WebView. This function is blocking and will return
+ * when the page has finished loading.
+ *
+ * @param url The URL to load.
+ */
+ public void get(String url) {
+ executeCommand(CMD_GET_URL, url, LOADING_TIMEOUT);
+ mDocumentElement = (WebElement) executeScript("return document.body;");
+ }
+
+ /**
+ * @return The source page of the currently loaded page in WebView.
+ */
+ public String getPageSource() {
+ return (String) executeScript("return new XMLSerializer()."
+ + "serializeToString(document);");
+ }
+
+ /**
+ * Find the first {@link android.webkit.webdriver.WebElement} using the
+ * given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return The first matching element on the current context.
+ * @throws {@link android.webkit.webdriver.WebElementNotFoundException} if
+ * no matching element was found.
+ */
+ public WebElement findElement(By by) {
+ checkNotNull(mDocumentElement, "Load a page using WebDriver.get() "
+ + "before looking for elements.");
+ return by.findElement(mDocumentElement);
+ }
+
+ /**
+ * Finds all {@link android.webkit.webdriver.WebElement} within the page
+ * using the given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return A list of all {@link android.webkit.webdriver.WebElement} found,
+ * or an empty list if nothing matches.
+ */
+ public List<WebElement> findElements(By by) {
+ checkNotNull(mDocumentElement, "Load a page using WebDriver.get() "
+ + "before looking for elements.");
+ return by.findElements(mDocumentElement);
+ }
+
+ /**
+ * Clears the WebView.
+ */
+ public void quit() {
+ mWebView.clearCache(true);
+ mWebView.clearFormData();
+ mWebView.clearHistory();
+ mWebView.clearSslPreferences();
+ mWebView.clearView();
+ }
+
+ /**
+ * Executes javascript in the context of the main frame.
+ *
+ * If the script has a return value the following happens:
+ * <ul>
+ * <li>For an HTML element, this method returns a WebElement</li>
+ * <li>For a decimal, a Double is returned</li>
+ * <li>For non-decimal number, a Long is returned</li>
+ * <li>For a boolean, a Boolean is returned</li>
+ * <li>For all other cases, a String is returned</li>
+ * <li>For an array, this returns a List<Object> with each object
+ * following the rules above.</li>
+ * <li>For an object literal this returns a Map<String, Object>. Note that
+ * Object literals keys can only be Strings. Non Strings keys will
+ * be filtered out.</li>
+ * </ul>
+ *
+ * <p> Arguments must be a number, a boolean, a string a WebElement or
+ * a list of any combination of the above. The arguments will be made
+ * available to the javascript via the "arguments" magic variable,
+ * as if the function was called via "Function.apply".
+ *
+ * @param script The JavaScript to execute.
+ * @param args The arguments to the script. Can be any of a number, boolean,
+ * string, WebElement or a List of those.
+ * @return A Boolean, Long, Double, String, WebElement, List or null.
+ */
+ public Object executeScript(final String script, final Object... args) {
+ String scriptArgs = "[" + convertToJsArgs(args) + "]";
+ String injectScriptJs = getResourceAsString(R.raw.execute_script_android);
+ return executeRawJavascript("(" + injectScriptJs +
+ ")(" + escapeAndQuote(script) + ", " + scriptArgs + ", true)");
+ }
+
+ /**
+ * Converts the arguments passed to a JavaScript friendly format.
+ *
+ * @param args The arguments to convert.
+ * @return Comma separated Strings containing the arguments.
+ */
+ /*package*/ String convertToJsArgs(final Object... args) {
+ StringBuilder toReturn = new StringBuilder();
+ int length = args.length;
+ for (int i = 0; i < length; i++) {
+ toReturn.append((i > 0) ? "," : "");
+ if (args[i] instanceof List<?>) {
+ toReturn.append("[");
+ List<Object> aList = (List<Object>) args[i];
+ for (int j = 0 ; j < aList.size(); j++) {
+ String comma = ((j == 0) ? "" : ",");
+ toReturn.append(comma + convertToJsArgs(aList.get(j)));
+ }
+ toReturn.append("]");
+ } else if (args[i] instanceof Map<?, ?>) {
+ Map<Object, Object> aMap = (Map<Object, Object>) args[i];
+ String toAdd = "{";
+ for (Object key: aMap.keySet()) {
+ toAdd += key + ":"
+ + convertToJsArgs(aMap.get(key)) + ",";
+ }
+ toReturn.append(toAdd.substring(0, toAdd.length() -1) + "}");
+ } else if (args[i] instanceof WebElement) {
+ // WebElement are represented in JavaScript by Objects as
+ // follow: {ELEMENT:"id"} where "id" refers to the id
+ // of the HTML element in the javascript cache that can
+ // be accessed throught bot.inject.cache.getCache_()
+ toReturn.append("{\"" + ELEMENT_KEY + "\":\""
+ + ((WebElement) args[i]).getId() + "\"}");
+ } else if (args[i] instanceof Number || args[i] instanceof Boolean) {
+ toReturn.append(String.valueOf(args[i]));
+ } else if (args[i] instanceof String) {
+ toReturn.append(escapeAndQuote((String) args[i]));
+ } else {
+ throw new IllegalArgumentException(
+ "Javascript arguments can be "
+ + "a Number, a Boolean, a String, a WebElement, "
+ + "or a List or a Map of those. Got: "
+ + ((args[i] == null) ? "null" : args[i].getClass()
+ + ", value: " + args[i].toString()));
+ }
+ }
+ return toReturn.toString();
+ }
+
+ /*package*/ Object executeRawJavascript(final String script) {
+ String result = executeCommand(CMD_EXECUTE_SCRIPT,
+ "window.webdriver.resultReady(" + script + ")",
+ JS_EXECUTION_TIMEOUT);
+ try {
+ JSONObject json = new JSONObject(result);
+ throwIfError(json);
+ Object value = json.get(VALUE);
+ return convertJsonToJavaObject(value);
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JavaScript result: "
+ + result.toString(), e);
+ }
+ }
+
+ /*package*/ String getResourceAsString(final int resourceId) {
+ InputStream is = mWebView.getResources().openRawResource(resourceId);
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ br.close();
+ is.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to open JavaScript resource.", e);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Wraps the given string into quotes and escape existing quotes
+ * and backslashes.
+ * "foo" -> "\"foo\""
+ * "foo\"" -> "\"foo\\\"\""
+ * "fo\o" -> "\"fo\\o\""
+ *
+ * @param toWrap The String to wrap in quotes
+ * @return a String wrapping the original String in quotes
+ */
+ private static String escapeAndQuote(final String toWrap) {
+ StringBuilder toReturn = new StringBuilder("\"");
+ for (int i = 0; i < toWrap.length(); i++) {
+ char c = toWrap.charAt(i);
+ if (c == '\"') {
+ toReturn.append("\\\"");
+ } else if (c == '\\') {
+ toReturn.append("\\\\");
+ } else {
+ toReturn.append(c);
+ }
+ }
+ toReturn.append("\"");
+ return toReturn.toString();
+ }
+
+ private Object convertJsonToJavaObject(final Object toConvert) {
+ try {
+ if (toConvert == null
+ || toConvert.equals(null)
+ || "undefined".equals(toConvert)
+ || "null".equals(toConvert)) {
+ return null;
+ } else if (toConvert instanceof Boolean) {
+ return toConvert;
+ } else if (toConvert instanceof Double
+ || toConvert instanceof Float) {
+ return Double.valueOf(String.valueOf(toConvert));
+ } else if (toConvert instanceof Integer
+ || toConvert instanceof Long) {
+ return Long.valueOf(String.valueOf(toConvert));
+ } else if (toConvert instanceof JSONArray) { // List
+ return convertJsonArrayToList((JSONArray) toConvert);
+ } else if (toConvert instanceof JSONObject) { // Map or WebElment
+ JSONObject map = (JSONObject) toConvert;
+ if (map.opt(ELEMENT_KEY) != null) { // WebElement
+ return new WebElement(this, (String) map.get(ELEMENT_KEY));
+ } else { // Map
+ return convertJsonObjectToMap(map);
+ }
+ } else {
+ return toConvert.toString();
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JavaScript result: "
+ + toConvert.toString(), e);
+ }
+ }
+
+ private List<Object> convertJsonArrayToList(final JSONArray json) {
+ List<Object> toReturn = Lists.newArrayList();
+ for (int i = 0; i < json.length(); i++) {
+ try {
+ toReturn.add(convertJsonToJavaObject(json.get(i)));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON: "
+ + json.toString(), e);
+ }
+ }
+ return toReturn;
+ }
+
+ private Map<Object, Object> convertJsonObjectToMap(final JSONObject json) {
+ Map<Object, Object> toReturn = Maps.newHashMap();
+ for (Iterator it = json.keys(); it.hasNext();) {
+ String key = (String) it.next();
+ try {
+ Object value = json.get(key);
+ toReturn.put(convertJsonToJavaObject(key),
+ convertJsonToJavaObject(value));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON:"
+ + json.toString(), e);
+ }
+ }
+ return toReturn;
+ }
+
+ private void throwIfError(final JSONObject jsonObject) {
+ ErrorCode status;
+ String errorMsg;
+ try {
+ status = ErrorCode.get((Integer) jsonObject.get(STATUS));
+ errorMsg = String.valueOf(jsonObject.get(VALUE));
+ } catch (JSONException e) {
+ throw new RuntimeException("Failed to parse JSON Object: "
+ + jsonObject, e);
+ }
+ switch (status) {
+ case SUCCESS:
+ return;
+ case NO_SUCH_ELEMENT:
+ throw new WebElementNotFoundException("Could not find "
+ + "WebElement.");
+ case STALE_ELEMENT_REFERENCE:
+ throw new WebElementStaleException("WebElement is stale.");
+ default:
+ throw new WebDriverException("Error: " + errorMsg);
+ }
+ }
+
+ private void shouldRunInMainThread(boolean value) {
+ assert (value == (MAIN_THREAD == Thread.currentThread().getId()));
+ }
+
+ /**
+ * Interface called from JavaScript when the result is ready.
+ */
+ private class JavascriptResultReady {
+
+ /**
+ * A callback from JavaScript to Java that passes the result as a
+ * parameter. This method is available from the WebView's
+ * JavaScript DOM as window.webdriver.resultReady().
+ *
+ * @param result The result that should be sent to Java from Javascript.
+ */
+ public void resultReady(final String result) {
+ synchronized (mSyncObject) {
+ mJsResult = result;
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+ }
+
+ /* package */ void notifyCommandDone() {
+ synchronized (mSyncObject) {
+ mCommandDone = true;
+ mSyncObject.notify();
+ }
+ }
+
+ /**
+ * Executes the given command by posting a message to mHandler. This thread
+ * will block until the command which runs in the main thread is done.
+ *
+ * @param command The command to run.
+ * @param arg The argument for that command.
+ * @param timeout A timeout in milliseconds.
+ */
+ private String executeCommand(int command, final Object arg, long timeout) {
+ shouldRunInMainThread(false);
+ synchronized (mSyncObject) {
+ mCommandDone = false;
+ Message msg = mHandler.obtainMessage(command);
+ msg.obj = arg;
+ mHandler.sendMessage(msg);
+
+ long end = System.currentTimeMillis() + timeout;
+ while (!mCommandDone) {
+ if (System.currentTimeMillis() >= end) {
+ throw new RuntimeException("Timeout executing command.");
+ }
+ try {
+ mSyncObject.wait(timeout);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return mJsResult;
+ }
+
+ private void checkNotNull(Object obj, String errosMsg) {
+ if (obj == null) {
+ throw new NullPointerException(errosMsg);
+ }
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebDriverException.java b/core/java/android/webkit/webdriver/WebDriverException.java
new file mode 100644
index 0000000..1a579c2
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebDriverException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * @hide
+ */
+public class WebDriverException extends RuntimeException {
+ public WebDriverException() {
+ super();
+ }
+
+ public WebDriverException(String reason) {
+ super(reason);
+ }
+
+ public WebDriverException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebDriverException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElement.java b/core/java/android/webkit/webdriver/WebElement.java
new file mode 100644
index 0000000..96700d7
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElement.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Represents an HTML element. Typically most interactions with a web page
+ * will be performed through this class.
+ *
+ * @hide
+ */
+public class WebElement {
+ private final String mId;
+ private final WebDriver mDriver;
+
+ private static final String LOCATOR_ID = "id";
+ private static final String LOCATOR_LINK_TEXT = "linkText";
+ private static final String LOCATOR_PARTIAL_LINK_TEXT = "partialLinkText";
+ private static final String LOCATOR_NAME = "name";
+ private static final String LOCATOR_CLASS_NAME = "className";
+ private static final String LOCATOR_CSS = "css";
+ private static final String LOCATOR_TAG_NAME = "tagName";
+ private static final String LOCATOR_XPATH = "xpath";
+
+ /**
+ * Package constructor to prevent clients from creating a new WebElement
+ * instance.
+ *
+ * <p> A WebElement represents an HTML element on the page.
+ * The corresponding HTML element is stored in a JS cache in the page
+ * that can be accessed through JavaScript using "bot.inject.cache".
+ *
+ * @param driver The WebDriver instance to use.
+ * @param id The index of the HTML element in the JavaSctipt cache.
+ * document.documentElement object.
+ */
+ /* package */ WebElement(final WebDriver driver, final String id) {
+ this.mId = id;
+ this.mDriver = driver;
+ }
+
+ /**
+ * Finds the first {@link android.webkit.webdriver.WebElement} using the
+ * given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return The first matching element on the current context.
+ */
+ public WebElement findElement(final By by) {
+ return by.findElement(this);
+ }
+
+ /**
+ * Finds all {@link android.webkit.webdriver.WebElement} within the page
+ * using the given method.
+ *
+ * @param by The locating mechanism to use.
+ * @return A list of all {@link android.webkit.webdriver.WebElement} found,
+ * or an empty list if nothing matches.
+ */
+ public List<WebElement> findElements(final By by) {
+ return by.findElements(this);
+ }
+
+ /**
+ * Gets the visisble (i.e. not hidden by CSS) innerText of this element,
+ * inlcuding sub-elements.
+ *
+ * @return the innerText of this element.
+ * @throws {@link android.webkit.webdriver.WebElementStaleException} if this
+ * element is stale, i.e. not on the current DOM.
+ */
+ public String getText() {
+ String getText = mDriver.getResourceAsString(R.raw.get_text_android);
+ return (String) executeAtom(getText, this);
+ }
+
+ /**
+ * Gets the value of an HTML attribute for this element or the value of the
+ * property with the same name if the attribute is not present. If neither
+ * is set, null is returned.
+ *
+ * @param attribute the HTML attribute.
+ * @return the value of that attribute or the value of the property with the
+ * same name if the attribute is not set, or null if neither are set. For
+ * boolean attribute values this will return the string "true" or "false".
+ */
+ public String getAttribute(String attribute) {
+ String getAttribute = mDriver.getResourceAsString(
+ R.raw.get_attribute_value_android);
+ return (String) executeAtom(getAttribute, this, attribute);
+ }
+
+ /**
+ * @return the tag name of this element.
+ */
+ public String getTagName() {
+ return (String) mDriver.executeScript("return arguments[0].tagName;",
+ this);
+ }
+
+ /**
+ * @return true if this element is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ String isEnabled = mDriver.getResourceAsString(
+ R.raw.is_enabled_android);
+ return (Boolean) executeAtom(isEnabled, this);
+ }
+
+ /**
+ * Determines whether this element is selected or not. This applies to input
+ * elements such as checkboxes, options in a select, and radio buttons.
+ *
+ * @return True if this element is selected, false otherwise.
+ */
+ public boolean isSelected() {
+ String isSelected = mDriver.getResourceAsString(
+ R.raw.is_selected_android);
+ return (Boolean) executeAtom(isSelected, this);
+ }
+
+ /**
+ * Selects an element on the page. This works for selecting checkboxes,
+ * options in a select, and radio buttons.
+ */
+ public void setSelected() {
+ String setSelected = mDriver.getResourceAsString(
+ R.raw.set_selected_android);
+ executeAtom(setSelected, this);
+ }
+
+ /**
+ * This toggles the checkboxe state from selected to not selected, or
+ * from not selected to selected.
+ *
+ * @return True if the toggled element is selected, false otherwise.
+ */
+ public boolean toggle() {
+ String toggle = mDriver.getResourceAsString(R.raw.toggle_android);
+ return (Boolean) executeAtom(toggle, this);
+ }
+
+ /*package*/ String getId() {
+ return mId;
+ }
+
+ /* package */ WebElement findElementById(final String locator) {
+ return findElement(LOCATOR_ID, locator);
+ }
+
+ /* package */ WebElement findElementByLinkText(final String linkText) {
+ return findElement(LOCATOR_LINK_TEXT, linkText);
+ }
+
+ /* package */ WebElement findElementByPartialLinkText(
+ final String linkText) {
+ return findElement(LOCATOR_PARTIAL_LINK_TEXT, linkText);
+ }
+
+ /* package */ WebElement findElementByName(final String name) {
+ return findElement(LOCATOR_NAME, name);
+ }
+
+ /* package */ WebElement findElementByClassName(final String className) {
+ return findElement(LOCATOR_CLASS_NAME, className);
+ }
+
+ /* package */ WebElement findElementByCss(final String css) {
+ return findElement(LOCATOR_CSS, css);
+ }
+
+ /* package */ WebElement findElementByTagName(final String tagName) {
+ return findElement(LOCATOR_TAG_NAME, tagName);
+ }
+
+ /* package */ WebElement findElementByXPath(final String xpath) {
+ return findElement(LOCATOR_XPATH, xpath);
+ }
+
+ /* package */ List<WebElement> findElementsById(final String locator) {
+ return findElements(LOCATOR_ID, locator);
+ }
+
+ /* package */ List<WebElement> findElementsByLinkText(final String linkText) {
+ return findElements(LOCATOR_LINK_TEXT, linkText);
+ }
+
+ /* package */ List<WebElement> findElementsByPartialLinkText(
+ final String linkText) {
+ return findElements(LOCATOR_PARTIAL_LINK_TEXT, linkText);
+ }
+
+ /* package */ List<WebElement> findElementsByName(final String name) {
+ return findElements(LOCATOR_NAME, name);
+ }
+
+ /* package */ List<WebElement> findElementsByClassName(final String className) {
+ return findElements(LOCATOR_CLASS_NAME, className);
+ }
+
+ /* package */ List<WebElement> findElementsByCss(final String css) {
+ return findElements(LOCATOR_CSS, css);
+ }
+
+ /* package */ List<WebElement> findElementsByTagName(final String tagName) {
+ return findElements(LOCATOR_TAG_NAME, tagName);
+ }
+
+ /* package */ List<WebElement> findElementsByXPath(final String xpath) {
+ return findElements(LOCATOR_XPATH, xpath);
+ }
+
+ private Object executeAtom(final String atom, final Object... args) {
+ String scriptArgs = mDriver.convertToJsArgs(args);
+ return mDriver.executeRawJavascript("(" +
+ atom + ")(" + scriptArgs + ")");
+ }
+
+ private List<WebElement> findElements(String strategy, String locator) {
+ String findElements = mDriver.getResourceAsString(
+ R.raw.find_elements_android);
+ return (List<WebElement>) executeAtom(findElements,
+ strategy, locator, this);
+ }
+
+ private WebElement findElement(String strategy, String locator) {
+ String findElement = mDriver.getResourceAsString(
+ R.raw.find_element_android);
+ WebElement el = (WebElement) executeAtom(findElement,
+ strategy, locator, this);
+ if (el == null) {
+ throw new WebElementNotFoundException("Could not find element "
+ + "with " + strategy + ": " + locator);
+ }
+ return el;
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementNotFoundException.java b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
new file mode 100644
index 0000000..e66d279
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementNotFoundException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * Thrown when a {@link android.webkit.webdriver.WebElement} is not found in the
+ * DOM of the page.
+ * @hide
+ */
+public class WebElementNotFoundException extends RuntimeException {
+
+ public WebElementNotFoundException() {
+ super();
+ }
+
+ public WebElementNotFoundException(String reason) {
+ super(reason);
+ }
+
+ public WebElementNotFoundException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebElementNotFoundException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebElementStaleException.java b/core/java/android/webkit/webdriver/WebElementStaleException.java
new file mode 100644
index 0000000..c59e794
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebElementStaleException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+/**
+ * Thrown when trying to access a {@link android.webkit.webdriver.WebElement}
+ * that is stale. This mean that the {@link android.webkit.webdriver.WebElement}
+ * is no longer present on the DOM of the page.
+ * @hide
+ */
+public class WebElementStaleException extends RuntimeException {
+
+ public WebElementStaleException() {
+ super();
+ }
+
+ public WebElementStaleException(String reason) {
+ super(reason);
+ }
+
+ public WebElementStaleException(String reason, Throwable cause) {
+ super(reason, cause);
+ }
+
+ public WebElementStaleException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
new file mode 100644
index 0000000..ea33c5b
--- /dev/null
+++ b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.webdriver;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Message;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+
+/* package */ class WebchromeClientWrapper extends WebChromeClient {
+
+ private final WebChromeClient mDelegate;
+ private final WebDriver mDriver;
+
+ public WebchromeClientWrapper(WebChromeClient delegate, WebDriver driver) {
+ if (delegate == null) {
+ this.mDelegate = new WebChromeClient();
+ } else {
+ this.mDelegate = delegate;
+ }
+ this.mDriver = driver;
+ }
+
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ if (newProgress == 100) {
+ mDriver.notifyCommandDone();
+ }
+ mDelegate.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ mDelegate.onReceivedTitle(view, title);
+ }
+
+ @Override
+ public void onReceivedIcon(WebView view, Bitmap icon) {
+ mDelegate.onReceivedIcon(view, icon);
+ }
+
+ @Override
+ public void onReceivedTouchIconUrl(WebView view, String url,
+ boolean precomposed) {
+ mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
+ }
+
+ @Override
+ public void onShowCustomView(View view,
+ CustomViewCallback callback) {
+ mDelegate.onShowCustomView(view, callback);
+ }
+
+ @Override
+ public void onHideCustomView() {
+ mDelegate.onHideCustomView();
+ }
+
+ @Override
+ public boolean onCreateWindow(WebView view, boolean dialog,
+ boolean userGesture, Message resultMsg) {
+ return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg);
+ }
+
+ @Override
+ public void onRequestFocus(WebView view) {
+ mDelegate.onRequestFocus(view);
+ }
+
+ @Override
+ public void onCloseWindow(WebView window) {
+ mDelegate.onCloseWindow(window);
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsAlert(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsConfirm(view, url, message, result);
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message,
+ String defaultValue, JsPromptResult result) {
+ return mDelegate.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ @Override
+ public boolean onJsBeforeUnload(WebView view, String url, String message,
+ JsResult result) {
+ return mDelegate.onJsBeforeUnload(view, url, message, result);
+ }
+
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+ long currentQuota, long estimatedSize, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota,
+ estimatedSize, totalUsedQuota, quotaUpdater);
+ }
+
+ @Override
+ public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
+ quotaUpdater);
+ }
+
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin,
+ GeolocationPermissions.Callback callback) {
+ mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
+ }
+
+ @Override
+ public void onGeolocationPermissionsHidePrompt() {
+ mDelegate.onGeolocationPermissionsHidePrompt();
+ }
+
+ @Override
+ public boolean onJsTimeout() {
+ return mDelegate.onJsTimeout();
+ }
+
+ @Override
+ public void onConsoleMessage(String message, int lineNumber,
+ String sourceID) {
+ mDelegate.onConsoleMessage(message, lineNumber, sourceID);
+ }
+
+ @Override
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ return mDelegate.onConsoleMessage(consoleMessage);
+ }
+
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ return mDelegate.getDefaultVideoPoster();
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return mDelegate.getVideoLoadingProgressView();
+ }
+
+ @Override
+ public void getVisitedHistory(ValueCallback<String[]> callback) {
+ mDelegate.getVisitedHistory(callback);
+ }
+
+ @Override
+ public void openFileChooser(ValueCallback<Uri> uploadFile,
+ String acceptType) {
+ mDelegate.openFileChooser(uploadFile, acceptType);
+ }
+
+ @Override
+ public void setInstallableWebApp() {
+ mDelegate.setInstallableWebApp();
+ }
+
+ @Override
+ public void setupAutoFill(Message msg) {
+ mDelegate.setupAutoFill(msg);
+ }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index d39271e..d63d421 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,7 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -2556,6 +2557,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ // Add a record for ourselves as well.
+ AccessibilityEvent record = AccessibilityEvent.obtain();
+ // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent
+ record.setClassName(getClass().getName());
+ child.dispatchPopulateAccessibilityEvent(record);
+ event.appendRecord(record);
+ return true;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
@@ -4529,8 +4541,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Otherwise resurrects the selection and returns true if resurrected.
*/
boolean resurrectSelectionIfNeeded() {
- if (mSelectedPosition < 0) {
- return resurrectSelection();
+ if (mSelectedPosition < 0 && resurrectSelection()) {
+ updateSelectorState();
+ return true;
}
return false;
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 0da73a4..2621e64 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -201,7 +201,8 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
+ void onProgressRefresh(float scale, boolean fromUser) {
+ super.onProgressRefresh(scale, fromUser);
Drawable thumb = mThumb;
if (thumb != null) {
setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f16efbd..060f1a9 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -876,7 +876,6 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
// This is an exceptional case which occurs when a window gets the
// focus and sends a focus event via its focused child to announce
// current focus/selection. AdapterView fires selection but not focus
@@ -885,22 +884,27 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
- // we send selection events only from AdapterView to avoid
- // generation of such event for each child
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+
+ // We send selection events only from AdapterView to avoid
+ // generation of such event for each child.
View selectedView = getSelectedView();
if (selectedView != null) {
- populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+ return selectedView.dispatchPopulateAccessibilityEvent(event);
}
- if (!populated) {
- if (selectedView != null) {
- event.setEnabled(selectedView.isEnabled());
- }
- event.setItemCount(getCount());
- event.setCurrentItemIndex(getSelectedItemPosition());
- }
+ return false;
+ }
- return populated;
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ event.setEnabled(selectedView.isEnabled());
+ }
+ event.setItemCount(getCount());
+ event.setCurrentItemIndex(getSelectedItemPosition());
}
@Override
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 072992e..c773527 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -79,7 +79,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
/**
* Map of the children of the {@link AdapterViewAnimator}.
*/
- HashMap<Integer, ViewAndIndex> mViewsMap = new HashMap<Integer, ViewAndIndex>();
+ HashMap<Integer, ViewAndMetaData> mViewsMap = new HashMap<Integer, ViewAndMetaData>();
/**
* List of views pending removal from the {@link AdapterViewAnimator}
@@ -103,11 +103,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int mCurrentWindowStartUnbounded = 0;
/**
- * Handler to post events to the main thread
- */
- Handler mMainQueue;
-
- /**
* Listens for data changes from the adapter
*/
AdapterDataSetObserver mDataSetObserver;
@@ -163,15 +158,18 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
private static final int DEFAULT_ANIMATION_DURATION = 200;
public AdapterViewAnimator(Context context) {
- super(context);
- initViewAnimator();
+ this(context, null);
}
public AdapterViewAnimator(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewAnimator);
+ com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
int resource = a.getResourceId(
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
@@ -203,17 +201,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* Initialize this {@link AdapterViewAnimator}
*/
private void initViewAnimator() {
- mMainQueue = new Handler(Looper.myLooper());
mPreviousViews = new ArrayList<Integer>();
}
- class ViewAndIndex {
- ViewAndIndex(View v, int i) {
- view = v;
- index = i;
- }
+ class ViewAndMetaData {
View view;
- int index;
+ int relativeIndex;
+ int adapterPosition;
+ long itemId;
+
+ ViewAndMetaData(View view, int relativeIndex, int adapterPosition, long itemId) {
+ this.view = view;
+ this.relativeIndex = relativeIndex;
+ this.adapterPosition = adapterPosition;
+ this.itemId = itemId;
+ }
}
/**
@@ -379,6 +381,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
}
+ private ViewAndMetaData getMetaDataForChild(View child) {
+ for (ViewAndMetaData vm: mViewsMap.values()) {
+ if (vm.view == child) {
+ return vm;
+ }
+ }
+ return null;
+ }
+
LayoutParams createOrReuseLayoutParams(View v) {
final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
if (currentLp instanceof ViewGroup.LayoutParams) {
@@ -481,7 +492,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (remove) {
View previousView = mViewsMap.get(index).view;
- int oldRelativeIndex = mViewsMap.get(index).index;
+ int oldRelativeIndex = mViewsMap.get(index).relativeIndex;
mPreviousViews.add(index);
transformViewForTransition(oldRelativeIndex, -1, previousView, animate);
@@ -497,7 +508,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int index = modulo(i, getWindowSize());
int oldRelativeIndex;
if (mViewsMap.containsKey(index)) {
- oldRelativeIndex = mViewsMap.get(index).index;
+ oldRelativeIndex = mViewsMap.get(index).relativeIndex;
} else {
oldRelativeIndex = -1;
}
@@ -510,14 +521,16 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (inOldRange) {
View view = mViewsMap.get(index).view;
- mViewsMap.get(index).index = newRelativeIndex;
+ mViewsMap.get(index).relativeIndex = newRelativeIndex;
applyTransformForChildAtIndex(view, newRelativeIndex);
transformViewForTransition(oldRelativeIndex, newRelativeIndex, view, animate);
// Otherwise this view is new to the window
} else {
// Get the new view from the adapter, add it and apply any transform / animation
- View newView = mAdapter.getView(modulo(i, adapterCount), null, this);
+ final int adapterPosition = modulo(i, adapterCount);
+ View newView = mAdapter.getView(adapterPosition, null, this);
+ long itemId = mAdapter.getItemId(adapterPosition);
// We wrap the new view in a FrameLayout so as to respect the contract
// with the adapter, that is, that we don't modify this view directly
@@ -527,7 +540,8 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
if (newView != null) {
fl.addView(newView);
}
- mViewsMap.put(index, new ViewAndIndex(fl, newRelativeIndex));
+ mViewsMap.put(index, new ViewAndMetaData(fl, newRelativeIndex,
+ adapterPosition, itemId));
addChild(fl);
applyTransformForChildAtIndex(fl, newRelativeIndex);
transformViewForTransition(-1, newRelativeIndex, fl, animate);
@@ -604,6 +618,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
case MotionEvent.ACTION_UP: {
if (mTouchMode == TOUCH_MODE_DOWN_IN_CURRENT_VIEW) {
final View v = getCurrentView();
+ final ViewAndMetaData viewData = getMetaDataForChild(v);
if (v != null) {
if (isTransformedTouchPointInView(ev.getX(), ev.getY(), v, null)) {
final Handler handler = getHandler();
@@ -616,7 +631,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
hideTapFeedback(v);
post(new Runnable() {
public void run() {
- performItemClick(v, 0, 0);
+ if (viewData != null) {
+ performItemClick(v, viewData.adapterPosition,
+ viewData.itemId);
+ } else {
+ performItemClick(v, 0, 0);
+ }
}
});
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index bf63607..bd595a5 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -199,11 +199,8 @@ public class CheckedTextView extends TextView implements Checkable {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
- if (!populated) {
- event.setChecked(mChecked);
- }
- return populated;
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.setChecked(mChecked);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0df45cc..f050d41 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -208,22 +208,18 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
-
- if (!populated) {
- int resourceId = 0;
- if (mChecked) {
- resourceId = R.string.accessibility_compound_button_selected;
- } else {
- resourceId = R.string.accessibility_compound_button_unselected;
- }
- String state = getResources().getString(resourceId);
- event.getText().add(state);
- event.setChecked(mChecked);
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ int resourceId = 0;
+ if (mChecked) {
+ resourceId = R.string.accessibility_compound_button_selected;
+ } else {
+ resourceId = R.string.accessibility_compound_button_unselected;
}
-
- return populated;
+ String state = getResources().getString(resourceId);
+ event.getText().add(state);
+ event.setChecked(mChecked);
}
@Override
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 516162a..6c4c39d 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -21,7 +21,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
-import android.util.Config;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -440,7 +439,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
*/
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
}
}
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
index 3fadf4c..44d1656 100644
--- a/core/java/android/widget/CursorTreeAdapter.java
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -22,7 +22,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
-import android.util.Config;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
@@ -499,7 +498,7 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem
@Override
public void onChange(boolean selfChange) {
if (mAutoRequery && mCursor != null) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor +
+ if (false) Log.v("Cursor", "Auto requerying " + mCursor +
" due to update");
mDataValid = mCursor.requery();
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 1d442db..30fb927 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -353,13 +353,14 @@ public class DatePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
| DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mCurrentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
/**
@@ -410,74 +411,28 @@ public class DatePicker extends FrameLayout {
}
/**
- * Reorders the spinners according to the date format in the current
- * {@link Locale}.
+ * Reorders the spinners according to the date format that is
+ * explicitly set by the user and if no such is set fall back
+ * to the current locale's default format.
*/
private void reorderSpinners() {
- java.text.DateFormat format;
- String order;
-
- /*
- * If the user is in a locale where the medium date format is still
- * numeric (Japanese and Czech, for example), respect the date format
- * order setting. Otherwise, use the order that the locale says is
- * appropriate for a spelled-out date.
- */
-
- if (getShortMonths()[0].startsWith("1")) {
- format = DateFormat.getDateFormat(getContext());
- } else {
- format = DateFormat.getMediumDateFormat(getContext());
- }
-
- if (format instanceof SimpleDateFormat) {
- order = ((SimpleDateFormat) format).toPattern();
- } else {
- // Shouldn't happen, but just in case.
- order = new String(DateFormat.getDateFormatOrder(getContext()));
- }
-
- /*
- * Remove the 3 spinners from their parent and then add them back in the
- * required order.
- */
- LinearLayout parent = mSpinners;
- parent.removeAllViews();
-
- boolean quoted = false;
- boolean didDay = false, didMonth = false, didYear = false;
-
- for (int i = 0; i < order.length(); i++) {
- char c = order.charAt(i);
-
- if (c == '\'') {
- quoted = !quoted;
- }
-
- if (!quoted) {
- if (c == DateFormat.DATE && !didDay) {
- parent.addView(mDaySpinner);
- didDay = true;
- } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) {
- parent.addView(mMonthSpinner);
- didMonth = true;
- } else if (c == DateFormat.YEAR && !didYear) {
- parent.addView(mYearSpinner);
- didYear = true;
- }
+ mSpinners.removeAllViews();
+ char[] order = DateFormat.getDateFormatOrder(getContext());
+ for (int i = 0; i < order.length; i++) {
+ switch (order[i]) {
+ case DateFormat.DATE:
+ mSpinners.addView(mDaySpinner);
+ break;
+ case DateFormat.MONTH:
+ mSpinners.addView(mMonthSpinner);
+ break;
+ case DateFormat.YEAR:
+ mSpinners.addView(mYearSpinner);
+ break;
+ default:
+ throw new IllegalArgumentException();
}
}
-
- // Shouldn't happen, but just in case.
- if (!didMonth) {
- parent.addView(mMonthSpinner);
- }
- if (!didDay) {
- parent.addView(mDaySpinner);
- }
- if (!didYear) {
- parent.addView(mYearSpinner);
- }
}
/**
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index f659ead..590a768 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -39,7 +39,7 @@ import java.util.ArrayList;
* Children are drawn in a stack, with the most recently added child on top.
* The size of the frame layout is the size of its largest child (plus padding), visible
* or not (if the FrameLayout's parent permits). Views that are GONE are used for sizing
- * only if {@link #setMeasureAllChildren(boolean) setConsiderGoneChildrenWhenMeasuring()}
+ * only if {@link #setMeasureAllChildren(boolean) setMeasureAllChildren()}
* is set to true.
*
* @attr ref android.R.styleable#FrameLayout_foreground
@@ -566,4 +566,3 @@ public class FrameLayout extends ViewGroup {
}
}
}
-
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fd0e53d..dbe9288 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -230,6 +230,30 @@ public class LinearLayout extends ViewGroup {
requestLayout();
}
+ /**
+ * Set padding displayed on both ends of dividers.
+ *
+ * @param padding Padding value in pixels that will be applied to each end
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #getDividerPadding()
+ */
+ public void setDividerPadding(int padding) {
+ mDividerPadding = padding;
+ }
+
+ /**
+ * Get the padding size used to inset dividers in pixels
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #setDividerPadding(int)
+ */
+ public int getDividerPadding() {
+ return mDividerPadding;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
@@ -244,29 +268,15 @@ public class LinearLayout extends ViewGroup {
}
void drawDividersVertical(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int top = getPaddingTop();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
top += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawHorizontalDivider(canvas, top);
- top += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawHorizontalDivider(canvas, top);
top += mDividerHeight;
}
@@ -276,35 +286,21 @@ public class LinearLayout extends ViewGroup {
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawHorizontalDivider(canvas, top);
}
}
void drawDividersHorizontal(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int left = getPaddingLeft();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
left += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawVerticalDivider(canvas, left);
- left += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawVerticalDivider(canvas, left);
left += mDividerWidth;
}
@@ -314,7 +310,7 @@ public class LinearLayout extends ViewGroup {
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawVerticalDivider(canvas, left);
}
}
@@ -523,6 +519,23 @@ public class LinearLayout extends ViewGroup {
}
/**
+ * Determines where to position dividers between children.
+ *
+ * @param childIndex Index of child to check for preceding divider
+ * @return true if there should be a divider before the child at childIndex
+ * @hide Pending API consideration. Currently only used internally by the system.
+ */
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+ } else if (childIndex == getChildCount()) {
+ return (mShowDividers & SHOW_DIVIDER_END) != 0;
+ } else {
+ return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+ }
+ }
+
+ /**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
@@ -554,14 +567,7 @@ public class LinearLayout extends ViewGroup {
int largestChildHeight = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how tall everyone is. Also remember max width.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -575,12 +581,7 @@ public class LinearLayout extends ViewGroup {
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
@@ -677,7 +678,7 @@ public class LinearLayout extends ViewGroup {
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
@@ -881,14 +882,7 @@ public class LinearLayout extends ViewGroup {
int largestChildWidth = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how wide everyone is. Also remember max height.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -902,12 +896,7 @@ public class LinearLayout extends ViewGroup {
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
@@ -1022,7 +1011,7 @@ public class LinearLayout extends ViewGroup {
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerWidth;
}
@@ -1358,13 +1347,6 @@ public class LinearLayout extends ViewGroup {
}
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childTop += mDividerHeight;
- }
-
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
@@ -1399,15 +1381,15 @@ public class LinearLayout extends ViewGroup {
break;
}
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += mDividerHeight;
+ }
+
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- if (showDividerMiddle) {
- childTop += mDividerHeight;
- }
-
i += getChildrenSkipCount(child, i);
}
}
@@ -1458,13 +1440,6 @@ public class LinearLayout extends ViewGroup {
}
}
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childLeft += mDividerWidth;
- }
-
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
@@ -1523,16 +1498,16 @@ public class LinearLayout extends ViewGroup {
break;
}
+ if (hasDividerBeforeChildAt(i)) {
+ childLeft += mDividerWidth;
+ }
+
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
childLeft += childWidth + lp.rightMargin +
getNextLocationOffset(child);
- if (showDividerMiddle) {
- childLeft += mDividerWidth;
- }
-
i += getChildrenSkipCount(child, i);
}
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index af954c9..5618dbe 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -251,7 +251,7 @@ public class ListView extends AbsListView {
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
- if (mAdapter != null) {
+ if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
@@ -261,6 +261,12 @@ public class ListView extends AbsListView {
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
+
+ // in the case of re-adding a header view, or adding one later on,
+ // we need to notify the observer
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
}
/**
@@ -294,7 +300,9 @@ public class ListView extends AbsListView {
if (mHeaderViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mHeaderViewInfos);
@@ -328,6 +336,12 @@ public class ListView extends AbsListView {
* @param isSelectable true if the footer view can be selected
*/
public void addFooterView(View v, Object data, boolean isSelectable) {
+
+ // NOTE: do not enforce the adapter being null here, since unlike in
+ // addHeaderView, it was never enforced here, and so existing apps are
+ // relying on being able to add a footer and then calling setAdapter to
+ // force creation of the HeaderViewListAdapter wrapper
+
FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
@@ -371,7 +385,9 @@ public class ListView extends AbsListView {
if (mFooterViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mFooterViewInfos);
@@ -1982,36 +1998,32 @@ public class ListView extends AbsListView {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
// If the item count is less than 15 then subtract disabled items from the count and
// position. Otherwise ignore disabled items.
- if (!populated) {
- int itemCount = 0;
- int currentItemIndex = getSelectedItemPosition();
-
- ListAdapter adapter = getAdapter();
- if (adapter != null) {
- final int count = adapter.getCount();
- if (count < 15) {
- for (int i = 0; i < count; i++) {
- if (adapter.isEnabled(i)) {
- itemCount++;
- } else if (i <= currentItemIndex) {
- currentItemIndex--;
- }
+ int itemCount = 0;
+ int currentItemIndex = getSelectedItemPosition();
+
+ ListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ final int count = adapter.getCount();
+ if (count < 15) {
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ itemCount++;
+ } else if (i <= currentItemIndex) {
+ currentItemIndex--;
}
- } else {
- itemCount = count;
}
+ } else {
+ itemCount = count;
}
-
- event.setItemCount(itemCount);
- event.setCurrentItemIndex(currentItemIndex);
}
- return populated;
+ event.setItemCount(itemCount);
+ event.setCurrentItemIndex(currentItemIndex);
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index cf72ec4..96d41a0 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -41,6 +43,8 @@ import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -49,8 +53,6 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
/**
* <p>
@@ -125,6 +127,7 @@ import com.android.internal.R;
public class ProgressBar extends View {
private static final int MAX_LEVEL = 10000;
private static final int ANIMATION_RESOLUTION = 200;
+ private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
int mMinWidth;
int mMaxWidth;
@@ -156,6 +159,8 @@ public class ProgressBar extends View {
private int mAnimationResolution;
+ private AccessibilityEventSender mAccessibilityEventSender;
+
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
* @param context the application environment
@@ -542,8 +547,11 @@ public class ProgressBar extends View {
onProgressRefresh(scale, fromUser);
}
}
-
- void onProgressRefresh(float scale, boolean fromUser) {
+
+ void onProgressRefresh(float scale, boolean fromUser) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ scheduleAccessibilityEventSender();
+ }
}
private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
@@ -1007,8 +1015,46 @@ public class ProgressBar extends View {
if (mIndeterminate) {
stopAnimation();
}
+ if(mRefreshProgressRunnable != null) {
+ removeCallbacks(mRefreshProgressRunnable);
+ }
+ if (mAccessibilityEventSender != null) {
+ removeCallbacks(mAccessibilityEventSender);
+ }
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
}
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
+ }
+
+ /**
+ * Schedule a command for sending an accessibility event.
+ * </br>
+ * Note: A command is used to ensure that accessibility events
+ * are sent at most one in a given time frame to save
+ * system resources while the progress changes quickly.
+ */
+ private void scheduleAccessibilityEventSender() {
+ if (mAccessibilityEventSender == null) {
+ mAccessibilityEventSender = new AccessibilityEventSender();
+ } else {
+ removeCallbacks(mAccessibilityEventSender);
+ }
+ postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
+ }
+
+ /**
+ * Command for sending an accessibility event.
+ */
+ private class AccessibilityEventSender implements Runnable {
+ public void run() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c854fac..9cf2718 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -125,7 +125,7 @@ public class RemoteViews implements Parcelable, Filter {
* SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
*/
private abstract static class Action implements Parcelable {
- public abstract void apply(View root) throws ActionException;
+ public abstract void apply(View root, ViewGroup rootParent) throws ActionException;
public int describeContents() {
return 0;
@@ -183,7 +183,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (!(view instanceof AdapterView<?>)) return;
@@ -214,7 +214,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -295,7 +295,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -360,6 +360,60 @@ public class RemoteViews implements Parcelable, Filter {
public final static int TAG = 8;
}
+ private class SetRemoteViewsAdapterIntent extends Action {
+ public SetRemoteViewsAdapterIntent(int id, Intent intent) {
+ this.viewId = id;
+ this.intent = intent;
+ }
+
+ public SetRemoteViewsAdapterIntent(Parcel parcel) {
+ viewId = parcel.readInt();
+ intent = Intent.CREATOR.createFromParcel(parcel);
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ intent.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent) {
+ final View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ // Ensure that we are applying to an AppWidget root
+ if (!(rootParent instanceof AppWidgetHostView)) {
+ Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " +
+ "AppWidgets (root id: " + viewId + ")");
+ return;
+ }
+ // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
+ if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
+ Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " +
+ "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
+ return;
+ }
+
+ // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
+ // RemoteViewsService
+ AppWidgetHostView host = (AppWidgetHostView) rootParent;
+ intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
+ if (target instanceof AbsListView) {
+ AbsListView v = (AbsListView) target;
+ v.setRemoteViewsAdapter(intent);
+ } else if (target instanceof AdapterViewAnimator) {
+ AdapterViewAnimator v = (AdapterViewAnimator) target;
+ v.setRemoteViewsAdapter(intent);
+ }
+ }
+
+ int viewId;
+ Intent intent;
+
+ public final static int TAG = 10;
+ }
+
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
@@ -383,7 +437,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -479,7 +533,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View target = root.findViewById(viewId);
if (target == null) return;
@@ -539,7 +593,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -755,7 +809,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final View view = root.findViewById(viewId);
if (view == null) return;
@@ -850,7 +904,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void apply(View root) {
+ public void apply(View root, ViewGroup rootParent) {
final Context context = root.getContext();
final ViewGroup target = (ViewGroup) root.findViewById(viewId);
if (target == null) return;
@@ -952,6 +1006,9 @@ public class RemoteViews implements Parcelable, Filter {
case SetOnClickFillInIntent.TAG:
mActions.add(new SetOnClickFillInIntent(parcel));
break;
+ case SetRemoteViewsAdapterIntent.TAG:
+ mActions.add(new SetRemoteViewsAdapterIntent(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -1287,16 +1344,29 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
*
- * @param appWidgetId The id of the app widget which contains the specified view
+ * @param appWidgetId The id of the app widget which contains the specified view. (This
+ * parameter is ignored in this deprecated method)
* @param viewId The id of the view whose text should change
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated This method has been deprecated. See
+ * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
*/
+ @Deprecated
public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
- // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
- // RemoteViewsService
- intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, appWidgetId);
- setIntent(viewId, "setRemoteViewsAdapter", intent);
+ setRemoteAdapter(viewId, intent);
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+ * Can only be used for App Widgets.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param intent The intent of the service which will be
+ * providing data to the RemoteViewsAdapter
+ */
+ public void setRemoteAdapter(int viewId, Intent intent) {
+ addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
/**
@@ -1499,7 +1569,7 @@ public class RemoteViews implements Parcelable, Filter {
result = inflater.inflate(mLayoutId, parent, false);
- performApply(result);
+ performApply(result, parent);
return result;
}
@@ -1514,15 +1584,15 @@ public class RemoteViews implements Parcelable, Filter {
*/
public void reapply(Context context, View v) {
prepareContext(context);
- performApply(v);
+ performApply(v, (ViewGroup) v.getParent());
}
- private void performApply(View v) {
+ private void performApply(View v, ViewGroup parent) {
if (mActions != null) {
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
- a.apply(v);
+ a.apply(v, parent);
}
}
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 1c0a2bb..40b0a9c 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -29,6 +29,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -156,13 +157,16 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// create in response to this bind
factory.onDataSetChanged();
}
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error notifying factory of data set changed in " +
"onServiceConnected(): " + e.getMessage());
// Return early to prevent anything further from being notified
// (effectively nothing has changed)
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error notifying factory of data set changed in " +
+ "onServiceConnected(): " + e.getMessage());
}
// Request meta data so that we have up to date data when calling back to
@@ -777,7 +781,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
tmpMetaData.count = count;
tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
}
- } catch (Exception e) {
+ } catch(RemoteException e) {
+ processException("updateMetaData", e);
+ } catch(RuntimeException e) {
processException("updateMetaData", e);
}
}
@@ -792,12 +798,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
+ return;
}
if (remoteViews == null) {
@@ -971,18 +980,20 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return getCount() <= 0;
}
-
private void onNotifyDataSetChanged() {
// Complete the actual notifyDataSetChanged() call initiated earlier
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
factory.onDataSetChanged();
- } catch (Exception e) {
+ } catch (RemoteException e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
// Return early to prevent from further being notified (since nothing has
// changed)
return;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+ return;
}
// Flush the cache so that we can reload new items from the service
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index e0b08d4..7ba4777 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -138,34 +138,87 @@ public abstract class RemoteViewsService extends Service {
return mIsCreated;
}
public synchronized void onDataSetChanged() {
- mFactory.onDataSetChanged();
+ try {
+ mFactory.onDataSetChanged();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
}
public synchronized int getCount() {
- return mFactory.getCount();
+ int count = 0;
+ try {
+ count = mFactory.getCount();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return count;
}
public synchronized RemoteViews getViewAt(int position) {
- RemoteViews rv = mFactory.getViewAt(position);
- rv.setIsWidgetCollectionChild(true);
+ RemoteViews rv = null;
+ try {
+ rv = mFactory.getViewAt(position);
+ if (rv != null) {
+ rv.setIsWidgetCollectionChild(true);
+ }
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
return rv;
}
public synchronized RemoteViews getLoadingView() {
- return mFactory.getLoadingView();
+ RemoteViews rv = null;
+ try {
+ rv = mFactory.getLoadingView();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return rv;
}
public synchronized int getViewTypeCount() {
- return mFactory.getViewTypeCount();
+ int count = 0;
+ try {
+ count = mFactory.getViewTypeCount();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return count;
}
public synchronized long getItemId(int position) {
- return mFactory.getItemId(position);
+ long id = 0;
+ try {
+ id = mFactory.getItemId(position);
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return id;
}
public synchronized boolean hasStableIds() {
- return mFactory.hasStableIds();
+ boolean hasStableIds = false;
+ try {
+ hasStableIds = mFactory.hasStableIds();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return hasStableIds;
}
public void onDestroy(Intent intent) {
synchronized (sLock) {
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) {
RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc);
- factory.onDestroy();
+ try {
+ factory.onDestroy();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
RemoteViewsService.sRemoteViewFactories.remove(fc);
}
}
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 21c61bd..71c91e1 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -20,6 +20,7 @@ import java.lang.ref.WeakReference;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -132,6 +133,8 @@ public class StackView extends AdapterViewAnimator {
private int mMaximumVelocity;
private VelocityTracker mVelocityTracker;
private boolean mTransitionIsSetup = false;
+ private int mResOutColor;
+ private int mClickColor;
private static HolographicHelper sHolographicHelper;
private ImageView mHighlight;
@@ -146,12 +149,24 @@ public class StackView extends AdapterViewAnimator {
private final Rect stackInvalidateRect = new Rect();
public StackView(Context context) {
- super(context);
- initStackView();
+ this(context, null);
}
public StackView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, com.android.internal.R.attr.stackViewStyle);
+ }
+
+ public StackView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.StackView, defStyleAttr, 0);
+
+ mResOutColor = a.getColor(
+ com.android.internal.R.styleable.StackView_resOutColor, 0);
+ mClickColor = a.getColor(
+ com.android.internal.R.styleable.StackView_clickColor, 0);
+
+ a.recycle();
initStackView();
}
@@ -357,7 +372,7 @@ public class StackView extends AdapterViewAnimator {
private void setupStackSlider(View v, int mode) {
mStackSlider.setMode(mode);
if (v != null) {
- mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
+ mHighlight.setImageBitmap(sHolographicHelper.createResOutline(v, mResOutColor));
mHighlight.setRotation(v.getRotation());
mHighlight.setTranslationY(v.getTranslationY());
mHighlight.setTranslationX(v.getTranslationX());
@@ -412,8 +427,8 @@ public class StackView extends AdapterViewAnimator {
// Here we need to make sure that the z-order of the children is correct
for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
int index = modulo(i, getWindowSize());
- ViewAndIndex vi = mViewsMap.get(index);
- if (vi != null) {
+ ViewAndMetaData vm = mViewsMap.get(index);
+ if (vm != null) {
View v = mViewsMap.get(index).view;
if (v != null) v.bringToFront();
}
@@ -429,8 +444,8 @@ public class StackView extends AdapterViewAnimator {
if (!mClickFeedbackIsValid) {
View v = getViewAtRelativeIndex(1);
if (v != null) {
- mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
- HolographicHelper.CLICK_FEEDBACK));
+ mClickFeedback.setImageBitmap(
+ sHolographicHelper.createClickOutline(v, mClickColor));
mClickFeedback.setTranslationX(v.getTranslationX());
mClickFeedback.setTranslationY(v.getTranslationY());
}
@@ -1355,16 +1370,19 @@ public class StackView extends AdapterViewAnimator {
mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
}
- Bitmap createOutline(View v) {
- return createOutline(v, RES_OUT);
+ Bitmap createClickOutline(View v, int color) {
+ return createOutline(v, CLICK_FEEDBACK, color);
+ }
+
+ Bitmap createResOutline(View v, int color) {
+ return createOutline(v, RES_OUT, color);
}
- Bitmap createOutline(View v, int type) {
+ Bitmap createOutline(View v, int type, int color) {
+ mHolographicPaint.setColor(color);
if (type == RES_OUT) {
- mHolographicPaint.setColor(0xff6699ff);
mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
} else if (type == CLICK_FEEDBACK) {
- mHolographicPaint.setColor(0x886699ff);
mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
}
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 6f76dd0..31ec785 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -427,12 +427,19 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- event.setItemCount(getTabCount());
- event.setCurrentItemIndex(mSelectedTab);
+ onPopulateAccessibilityEvent(event);
+ // Dispatch only to the selected tab.
if (mSelectedTab != -1) {
- getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+ return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
}
- return true;
+ return false;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.setItemCount(getTabCount());
+ event.setCurrentItemIndex(mSelectedTab);
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 13b9285f..4d3aa68 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -82,10 +82,12 @@ import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.style.ClickableSpan;
import android.text.style.ParagraphStyle;
+import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.util.Log;
import android.util.TypedValue;
@@ -309,6 +311,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout;
private int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout;
+ private int mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout;
+ private int mTextEditSuggestionItemLayout;
+ private SuggestionsPopupWindow mSuggestionsPopupWindow;
+
private int mCursorDrawableRes;
private final Drawable[] mCursorDrawable = new Drawable[2];
private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2
@@ -777,6 +783,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTextEditSideNoPasteWindowLayout = a.getResourceId(attr, 0);
break;
+ case com.android.internal.R.styleable.TextView_textEditSuggestionsBottomWindowLayout:
+ mTextEditSuggestionsBottomWindowLayout = a.getResourceId(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextView_textEditSuggestionsTopWindowLayout:
+ mTextEditSuggestionsTopWindowLayout = a.getResourceId(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
+ mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
+ break;
+
case com.android.internal.R.styleable.TextView_textIsSelectable:
mTextIsSelectable = a.getBoolean(attr, false);
break;
@@ -2949,6 +2967,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
advancesIndex);
}
+ public float getTextRunAdvances(int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex,
+ Paint p, int reserved) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunAdvances(mChars, start + mStart, count,
+ contextStart + mStart, contextCount, flags, advances,
+ advancesIndex, reserved);
+ }
+
public int getTextRunCursor(int contextStart, int contextEnd, int flags,
int offset, int cursorOpt, Paint p) {
int contextCount = contextEnd - contextStart;
@@ -3977,13 +4005,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
observer.removeOnPreDrawListener(this);
mPreDrawState = PREDRAW_NOT_REGISTERED;
}
- // No need to create the controller, as getXXController would.
- if (mInsertionPointCursorController != null) {
- observer.removeOnTouchModeChangeListener(mInsertionPointCursorController);
- }
- if (mSelectionModifierCursorController != null) {
- observer.removeOnTouchModeChangeListener(mSelectionModifierCursorController);
- }
if (mError != null) {
hideError();
@@ -4210,6 +4231,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onDraw(Canvas canvas) {
+ if (mPreDrawState == PREDRAW_DONE) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnPreDrawListener(this);
+ mPreDrawState = PREDRAW_NOT_REGISTERED;
+ }
+
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return;
restartMarqueeIfNeeded();
@@ -4281,12 +4308,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (mPreDrawState == PREDRAW_DONE) {
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.removeOnPreDrawListener(this);
- mPreDrawState = PREDRAW_NOT_REGISTERED;
- }
-
int color = mCurTextColor;
if (mLayout == null) {
@@ -4549,15 +4570,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
- /**
- * Update the positions of the CursorControllers. Needed by WebTextView,
- * which does not draw.
- * @hide
- */
- protected void updateCursorControllerPositions() {
- // TODO remove
- }
-
@Override
public void getFocusedRect(Rect r) {
if (mLayout == null) {
@@ -5236,6 +5248,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param text The auto complete text the user has selected.
*/
public void onCommitCompletion(CompletionInfo text) {
+ // intentionally empty
}
/**
@@ -5422,6 +5435,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of edit operations through a call to link {@link #beginBatchEdit()}.
*/
public void onBeginBatchEdit() {
+ // intentionally empty
}
/**
@@ -5429,6 +5443,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of edit operations through a call to link {@link #endBatchEdit}.
*/
public void onEndBatchEdit() {
+ // intentionally empty
}
/**
@@ -6275,15 +6290,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (isFocused()) {
- // This offsets because getInterestingRect() is in terms of
- // viewport coordinates, but requestRectangleOnScreen()
- // is in terms of content coordinates.
+ // This offsets because getInterestingRect() is in terms of viewport coordinates, but
+ // requestRectangleOnScreen() is in terms of content coordinates.
- Rect r = new Rect(x, top, x + 1, bottom);
- getInterestingRect(r, line);
- r.offset(mScrollX, mScrollY);
+ if (mTempRect == null) mTempRect = new Rect();
+ mTempRect.set(x, top, x + 1, bottom);
+ getInterestingRect(mTempRect, line);
+ mTempRect.offset(mScrollX, mScrollY);
- if (requestRectangleOnScreen(r)) {
+ if (requestRectangleOnScreen(mTempRect)) {
changed = true;
}
}
@@ -6756,25 +6771,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * This method is called when the text is changed, in case any
- * subclasses would like to know.
+ * This method is called when the text is changed, in case any subclasses
+ * would like to know.
+ *
+ * Within <code>text</code>, the <code>lengthAfter</code> characters
+ * beginning at <code>start</code> have just replaced old text that had
+ * length <code>lengthBefore</code>. It is an error to attempt to make
+ * changes to <code>text</code> from this callback.
*
- * @param text The text the TextView is displaying.
- * @param start The offset of the start of the range of the text
- * that was modified.
- * @param before The offset of the former end of the range of the
- * text that was modified. If text was simply inserted,
- * this will be the same as <code>start</code>.
- * If text was replaced with new text or deleted, the
- * length of the old text was <code>before-start</code>.
- * @param after The offset of the end of the range of the text
- * that was modified. If text was simply deleted,
- * this will be the same as <code>start</code>.
- * If text was replaced with new text or inserted,
- * the length of the new text is <code>after-start</code>.
+ * @param text The text the TextView is displaying
+ * @param start The offset of the start of the range of the text that was
+ * modified
+ * @param lengthBefore The length of the former text that has been replaced
+ * @param lengthAfter The length of the replacement modified text
*/
- protected void onTextChanged(CharSequence text,
- int start, int before, int after) {
+ protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ // intentionally empty
}
/**
@@ -6785,6 +6797,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param selEnd The new selection end location.
*/
protected void onSelectionChanged(int selStart, int selEnd) {
+ // intentionally empty
}
/**
@@ -7200,14 +7213,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
-
- // Performed after super.onFocusChanged so that this TextView is registered and can ask for
- // the IME. Showing the IME while focus is moved using the D-Pad is a bad idea, however this
- // does not happen in that case (using the arrows on a bluetooth keyboard).
- if (focused && isTextEditable()) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) imm.showSoftInput(this, 0);
- }
}
private int getLastTapPosition() {
@@ -7326,9 +7331,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
- final int oldScrollX = mScrollX;
- final int oldScrollY = mScrollY;
-
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
@@ -7345,27 +7347,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (isTextEditable() || mTextIsSelectable) {
- if (mScrollX != oldScrollX || mScrollY != oldScrollY) { // TODO remove
- // Hide insertion anchor while scrolling. Leave selection.
- hideInsertionPointCursorController(); // TODO any motion should hide it
+ if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
+ // Show the IME, except when selecting in read-only text.
+ if (!mTextIsSelectable) {
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ handled |= imm != null && imm.showSoftInput(this, 0);
}
- if (touchIsFinished) {
- // Show the IME, except when selecting in read-only text.
- if (!mTextIsSelectable) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
- handled |= imm != null && imm.showSoftInput(this, 0);
- }
-
- boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
- if (!selectAllGotFocus && hasSelection()) {
- startSelectionActionMode();
- } else {
- stopSelectionActionMode();
- if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
- getInsertionController().show();
- }
+ boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
+ if (!selectAllGotFocus && hasSelection()) {
+ startSelectionActionMode();
+ } else {
+ stopSelectionActionMode();
+ hideSuggestions();
+ if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) {
+ getInsertionController().show();
}
}
}
@@ -7797,7 +7793,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Cases where the text ends with a '.' and we select from the end of the line
// (right after the dot), or when we select from the space character in "aaa, bbb".
continue;
- }
+ }
if (type == Character.SURROGATE) { // Two Character codepoint
end = start - 1; // Recheck as a pair when scanning forward
continue;
@@ -7900,9 +7896,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
if (!isShown()) {
- return false;
+ return;
}
final boolean isPassword = hasPasswordTransformationMethod();
@@ -7913,15 +7909,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
text = getHint();
}
if (!TextUtils.isEmpty(text)) {
- if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
- text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
- }
event.getText().add(text);
}
} else {
event.setPassword(isPassword);
}
- return false;
}
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
@@ -8045,7 +8037,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case ID_SELECTION_MODE:
if (mSelectionActionMode != null) {
// Selection mode is already started, simply change selected part.
- updateSelectedRegion();
+ selectCurrentWord();
} else {
startSelectionActionMode();
}
@@ -8180,7 +8172,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
stopSelectionActionMode();
Selection.setSelection((Spannable)mText, offset);
- getInsertionController().show(0);
+ getInsertionController().showWithPaste();
handled = true;
}
@@ -8195,8 +8187,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- // New selection at touch position
- updateSelectedRegion();
+ selectCurrentWord();
}
handled = true;
}
@@ -8212,17 +8203,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return handled;
}
- /**
- * When selection mode is already started, this method simply updates the selected part of text
- * to the text under the finger.
- */
- private void updateSelectedRegion() {
- // Start a new selection at current position, keep selectionAction mode on
- selectCurrentWord();
- // Updates handles' positions
- getSelectionController().show();
- }
-
private boolean touchPositionIsInSelection() {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
@@ -8245,6 +8225,201 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
}
+ private class SuggestionsPopupWindow implements OnClickListener {
+ private static final int MAX_NUMBER_SUGGESTIONS = 5;
+ private static final long NO_SUGGESTIONS = -1L;
+ private final PopupWindow mContainer;
+ private final ViewGroup[] mSuggestionViews = new ViewGroup[2];
+ private final int[] mSuggestionViewLayouts = new int[] {
+ mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout};
+
+ public SuggestionsPopupWindow() {
+ mContainer = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSuggestionsWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+
+ mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+ mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ private ViewGroup getViewGroup(boolean under) {
+ final int viewIndex = under ? 0 : 1;
+ ViewGroup viewGroup = mSuggestionViews[viewIndex];
+
+ if (viewGroup == null) {
+ final int layout = mSuggestionViewLayouts[viewIndex];
+ LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ if (inflater == null) {
+ throw new IllegalArgumentException(
+ "Unable to create TextEdit suggestion window inflater");
+ }
+
+ View view = inflater.inflate(layout, null);
+
+ if (! (view instanceof ViewGroup)) {
+ throw new IllegalArgumentException(
+ "Inflated TextEdit suggestion window is not a ViewGroup: " + view);
+ }
+
+ viewGroup = (ViewGroup) view;
+
+ // Inflate the suggestion items once and for all.
+ for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
+ View childView = inflater.inflate(mTextEditSuggestionItemLayout, viewGroup,
+ false);
+
+ if (! (childView instanceof TextView)) {
+ throw new IllegalArgumentException(
+ "Inflated TextEdit suggestion item is not a TextView: " + childView);
+ }
+
+ viewGroup.addView(childView);
+ childView.setOnClickListener(this);
+ }
+
+ mSuggestionViews[viewIndex] = viewGroup;
+ }
+
+ return viewGroup;
+ }
+
+ public void show() {
+ if (!(mText instanceof Editable)) return;
+
+ final int pos = TextView.this.getSelectionStart();
+ Spannable spannable = (Spannable)TextView.this.mText;
+ SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
+ final int nbSpans = suggestionSpans.length;
+
+ ViewGroup viewGroup = getViewGroup(true);
+ mContainer.setContentView(viewGroup);
+
+ int totalNbSuggestions = 0;
+ for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
+ SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
+ final int spanStart = spannable.getSpanStart(suggestionSpan);
+ final int spanEnd = spannable.getSpanEnd(suggestionSpan);
+ final Long spanRange = packRangeInLong(spanStart, spanEnd);
+
+ String[] suggestions = suggestionSpan.getSuggestions();
+ int nbSuggestions = suggestions.length;
+ for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
+ TextView textView = (TextView) viewGroup.getChildAt(totalNbSuggestions);
+ textView.setText(suggestions[suggestionIndex]);
+ textView.setTag(spanRange);
+
+ totalNbSuggestions++;
+ if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
+ spanIndex = nbSpans;
+ break;
+ }
+ }
+ }
+
+ if (totalNbSuggestions == 0) {
+ // TODO Replace by final text, use a dedicated layout, add a fade out timer...
+ TextView textView = (TextView) viewGroup.getChildAt(0);
+ textView.setText("No suggestions available");
+ textView.setTag(NO_SUGGESTIONS);
+ totalNbSuggestions++;
+ }
+
+ for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
+ viewGroup.getChildAt(i).setVisibility(i < totalNbSuggestions ? VISIBLE : GONE);
+ }
+
+ final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ viewGroup.measure(size, size);
+
+ positionAtCursor();
+ }
+
+ public void hide() {
+ mContainer.dismiss();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ Long range = ((Long) view.getTag());
+ if (range != NO_SUGGESTIONS) {
+ final int spanStart = extractRangeStartFromLong(range);
+ final int spanEnd = extractRangeEndFromLong(range);
+ ((Editable) mText).replace(spanStart, spanEnd, textView.getText());
+ }
+ }
+ hide();
+ }
+
+ void positionAtCursor() {
+ View contentView = mContainer.getContentView();
+ int width = contentView.getMeasuredWidth();
+ int height = contentView.getMeasuredHeight();
+ final int offset = TextView.this.getSelectionStart();
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineBottom = mLayout.getLineBottom(line);
+ float primaryHorizontal = mLayout.getPrimaryHorizontal(offset);
+
+ final Rect bounds = sCursorControllerTempRect;
+ bounds.left = (int) (primaryHorizontal - width / 2.0f);
+ bounds.top = lineBottom;
+
+ bounds.right = bounds.left + width;
+ bounds.bottom = bounds.top + height;
+
+ convertFromViewportToContentCoordinates(bounds);
+
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationInWindow(coords);
+ coords[0] += bounds.left;
+ coords[1] += bounds.top;
+
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ final int screenHeight = displayMetrics.heightPixels;
+
+ // Vertical clipping
+ if (coords[1] + height > screenHeight) {
+ // Try to position above current line instead
+ // TODO use top layout instead, reverse suggestion order,
+ // try full screen vertical down if it still does not fit. TBD with designers.
+
+ // Update dimensions from new view
+ contentView = mContainer.getContentView();
+ width = contentView.getMeasuredWidth();
+ height = contentView.getMeasuredHeight();
+
+ final int lineTop = mLayout.getLineTop(line);
+ final int lineHeight = lineBottom - lineTop;
+ coords[1] -= height + lineHeight;
+ }
+
+ // Horizontal clipping
+ coords[0] = Math.max(0, coords[0]);
+ coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]);
+
+ mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]);
+ }
+ }
+
+ void showSuggestions() {
+ if (mSuggestionsPopupWindow == null) {
+ mSuggestionsPopupWindow = new SuggestionsPopupWindow();
+ }
+ hideControllers();
+ mSuggestionsPopupWindow.show();
+ }
+
+ void hideSuggestions() {
+ if (mSuggestionsPopupWindow != null) {
+ mSuggestionsPopupWindow.hide();
+ }
+ }
+
/**
* If provided, this ActionMode.Callback will be used to create the ActionMode when text
* selection is initiated in this View.
@@ -8453,65 +8628,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * A CursorController instance can be used to control a cursor in the text.
- * It is not used outside of {@link TextView}.
- * @hide
- */
- private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
- /**
- * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
- * See also {@link #hide()}.
- */
- public void show();
-
- /**
- * Hide the cursor controller from screen.
- * See also {@link #show()}.
- */
- public void hide();
-
- /**
- * @return true if the CursorController is currently visible
- */
- public boolean isShowing();
-
- /**
- * Update the controller's position.
- */
- public void updatePosition(HandleView handle, int x, int y);
-
- public void updateOffset(HandleView handle, int offset);
-
- public void updatePosition();
-
- public int getCurrentOffset(HandleView handle);
-
- /**
- * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
- * a chance to become active and/or visible.
- * @param event The touch event
- */
- public boolean onTouchEvent(MotionEvent event);
-
- /**
- * Called when the view is detached from window. Perform house keeping task, such as
- * stopping Runnable thread that would otherwise keep a reference on the context, thus
- * preventing the activity to be recycled.
- */
- public void onDetached();
- }
-
- private class PastePopupMenu implements OnClickListener {
+ private class PastePopupWindow implements OnClickListener {
private final PopupWindow mContainer;
- private int mPositionX;
- private int mPositionY;
private final View[] mPasteViews = new View[4];
private final int[] mPasteViewLayouts = new int[] {
mTextEditPasteWindowLayout, mTextEditNoPasteWindowLayout,
mTextEditSidePasteWindowLayout, mTextEditSideNoPasteWindowLayout };
- public PastePopupMenu() {
+ public PastePopupWindow() {
mContainer = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
@@ -8594,14 +8718,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
convertFromViewportToContentCoordinates(bounds);
- mPositionX = bounds.left;
- mPositionY = bounds.top;
-
-
final int[] coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
- coords[0] += mPositionX;
- coords[1] += mPositionY;
+ coords[0] += bounds.left;
+ coords[1] += bounds.top;
final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
if (coords[1] < 0) {
@@ -8637,32 +8757,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
- private Drawable mDrawable;
+ private abstract class HandleView extends View implements ViewTreeObserver.OnPreDrawListener {
+ protected Drawable mDrawable;
private final PopupWindow mContainer;
// Position with respect to the parent TextView
private int mPositionX, mPositionY;
- private final CursorController mController;
private boolean mIsDragging;
// Offset from touch position to mPosition
private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
- private float mHotspotX;
+ protected float mHotspotX;
// Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility
private float mIdealVerticalOffset;
- // Parent's (TextView) position in window
+ // Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
- private float mDownPositionX, mDownPositionY;
// PopupWindow container absolute position with respect to the enclosing window
private int mContainerPositionX, mContainerPositionY;
// Visible or not (scrolled off screen), whether or not this handle should be visible
private boolean mIsActive = false;
- // The insertion handle can have an associated PastePopupMenu
- private boolean mIsInsertionHandle = false;
- // Used to detect taps on the insertion handle, which will affect the PastePopupMenu
- private long mTouchTimer;
- private PastePopupMenu mPastePopupWindow;
+
+ public HandleView() {
+ super(TextView.this.mContext);
+ mContainer = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSelectHandleWindowStyle);
+ mContainer.setSplitTouchEnabled(true);
+ mContainer.setClippingEnabled(false);
+ mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ mContainer.setContentView(this);
+
+ initDrawable();
+
+ final int handleHeight = mDrawable.getIntrinsicHeight();
+ mTouchOffsetY = -0.3f * handleHeight;
+ mIdealVerticalOffset = 0.7f * handleHeight;
+ }
+
+ protected abstract void initDrawable();
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
@@ -8703,85 +8834,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (i > 0 && i < iMax &&
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
- mController.updateOffset(this, mPreviousOffsets[index]);
+ updateOffset(mPreviousOffsets[index]);
}
}
- public static final int LEFT = 0;
- public static final int CENTER = 1;
- public static final int RIGHT = 2;
-
- public HandleView(CursorController controller, int pos) {
- super(TextView.this.mContext);
- mController = controller;
- mContainer = new PopupWindow(TextView.this.mContext, null,
- com.android.internal.R.attr.textSelectHandleWindowStyle);
- mContainer.setSplitTouchEnabled(true);
- mContainer.setClippingEnabled(false);
- mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
- mContainer.setContentView(this);
-
- setPosition(pos);
- }
-
- private void setPosition(int pos) {
- int handleWidth;
- switch (pos) {
- case LEFT: {
- if (mSelectHandleLeft == null) {
- mSelectHandleLeft = mContext.getResources().getDrawable(
- mTextSelectHandleLeftRes);
- }
- mDrawable = mSelectHandleLeft;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth * 3.0f / 4.0f;
- break;
- }
-
- case RIGHT: {
- if (mSelectHandleRight == null) {
- mSelectHandleRight = mContext.getResources().getDrawable(
- mTextSelectHandleRightRes);
- }
- mDrawable = mSelectHandleRight;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth / 4.0f;
- break;
- }
-
- case CENTER:
- default: {
- if (mSelectHandleCenter == null) {
- mSelectHandleCenter = mContext.getResources().getDrawable(
- mTextSelectHandleRes);
- }
- mDrawable = mSelectHandleCenter;
- handleWidth = mDrawable.getIntrinsicWidth();
- mHotspotX = handleWidth / 2.0f;
- mIsInsertionHandle = true;
- break;
- }
- }
-
- final int handleHeight = mDrawable.getIntrinsicHeight();
- mTouchOffsetY = -0.3f * handleHeight;
- mIdealVerticalOffset = 0.7f * handleHeight;
-
- invalidate();
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
}
public void show() {
- updateContainerPosition();
if (isShowing()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
-
- hidePastePopupWindow();
} else {
mContainer.showAtLocation(TextView.this, 0,
mContainerPositionX, mContainerPositionY);
@@ -8793,10 +8858,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void dismiss() {
+ protected void dismiss() {
mIsDragging = false;
mContainer.dismiss();
- hidePastePopupWindow();
}
public void hide() {
@@ -8827,24 +8891,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
- final TextView hostView = TextView.this;
+ final TextView textView = TextView.this;
- if (mTempRect == null) {
- mTempRect = new Rect();
- }
+ if (mTempRect == null) mTempRect = new Rect();
final Rect clip = mTempRect;
clip.left = compoundPaddingLeft;
clip.top = extendedPaddingTop;
- clip.right = hostView.getWidth() - compoundPaddingRight;
- clip.bottom = hostView.getHeight() - extendedPaddingBottom;
+ clip.right = textView.getWidth() - compoundPaddingRight;
+ clip.bottom = textView.getHeight() - extendedPaddingBottom;
- final ViewParent parent = hostView.getParent();
- if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
+ final ViewParent parent = textView.getParent();
+ if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
return false;
}
final int[] coords = mTempCoords;
- hostView.getLocationInWindow(coords);
+ textView.getLocationInWindow(coords);
final int posX = coords[0] + mPositionX + (int) mHotspotX;
final int posY = coords[1] + mPositionY;
@@ -8853,45 +8915,52 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
posY >= clip.top && posY <= clip.bottom;
}
- private void moveTo(int x, int y) {
- mPositionX = x - TextView.this.mScrollX;
- mPositionY = y - TextView.this.mScrollY;
+ public abstract int getCurrentCursorOffset();
- if (mIsDragging) {
- TextView.this.getLocationInWindow(mTempCoords);
- if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
- mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
- mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
- mLastParentX = mTempCoords[0];
- mLastParentY = mTempCoords[1];
- }
- // Hide paste popup window as soon as the handle is dragged.
- hidePastePopupWindow();
- }
+ public abstract void updateOffset(int offset);
+
+ public abstract void updatePosition(int x, int y);
+
+ protected void positionAtCursorOffset(int offset) {
+ addPositionToTouchUpFilter(offset);
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineBottom = mLayout.getLineBottom(line);
+
+ mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
+ mPositionY = lineBottom;
+
+ // Take TextView's padding into account.
+ mPositionX += viewportToContentHorizontalOffset();
+ mPositionY += viewportToContentVerticalOffset();
}
- /**
- * Updates the global container's position.
- * @return whether or not the position has actually changed
- */
- private boolean updateContainerPosition() {
- // TODO Prevent this using different HandleView subclasses
- mController.updateOffset(this, mController.getCurrentOffset(this));
+ protected boolean updateContainerPosition() {
+ positionAtCursorOffset(getCurrentCursorOffset());
+
+ final int previousContainerPositionX = mContainerPositionX;
+ final int previousContainerPositionY = mContainerPositionY;
+
TextView.this.getLocationInWindow(mTempCoords);
- final int containerPositionX = mTempCoords[0] + mPositionX;
- final int containerPositionY = mTempCoords[1] + mPositionY;
+ mContainerPositionX = mTempCoords[0] + mPositionX;
+ mContainerPositionY = mTempCoords[1] + mPositionY;
- if (containerPositionX != mContainerPositionX ||
- containerPositionY != mContainerPositionY) {
- mContainerPositionX = containerPositionX;
- mContainerPositionY = containerPositionY;
- return true;
- }
- return false;
+ return (previousContainerPositionX != mContainerPositionX ||
+ previousContainerPositionY != mContainerPositionY);
}
public boolean onPreDraw() {
if (updateContainerPosition()) {
+ if (mIsDragging) {
+ if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
+ mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
+ mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
+ mLastParentX = mTempCoords[0];
+ mLastParentY = mTempCoords[1];
+ }
+ }
+
+ onHandleMoved();
+
if (isPositionVisible()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
@@ -8904,9 +8973,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
dismiss();
}
}
-
- // Hide paste popup as soon as the view is scrolled or moved
- hidePastePopupWindow();
}
return true;
}
@@ -8921,20 +8987,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- startTouchUpFilter(mController.getCurrentOffset(this));
- mDownPositionX = ev.getRawX();
- mDownPositionY = ev.getRawY();
- mTouchToWindowOffsetX = mDownPositionX - mPositionX;
- mTouchToWindowOffsetY = mDownPositionY - mPositionY;
+ startTouchUpFilter(getCurrentCursorOffset());
+ mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
+ mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final int[] coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
mLastParentX = coords[0];
mLastParentY = coords[1];
mIsDragging = true;
- if (mIsInsertionHandle) {
- mTouchTimer = SystemClock.uptimeMillis();
- }
break;
}
@@ -8958,27 +9019,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
- mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
+ updatePosition(Math.round(newPosX), Math.round(newPosY));
break;
}
case MotionEvent.ACTION_UP:
- if (mIsInsertionHandle) {
- long delay = SystemClock.uptimeMillis() - mTouchTimer;
- if (delay < ViewConfiguration.getTapTimeout()) {
- final float deltaX = mDownPositionX - ev.getRawX();
- final float deltaY = mDownPositionY - ev.getRawY();
- final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- if (distanceSquared < mSquaredTouchSlopDistance) {
- if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
- // Tapping on the handle dismisses the displayed paste view,
- mPastePopupWindow.hide();
- } else {
- ((InsertionPointCursorController) mController).show(0);
- }
- }
- }
- }
filterOnTouchUp();
mIsDragging = false;
break;
@@ -8994,60 +9039,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mIsDragging;
}
- void positionAtCursor(int offset) {
- addPositionToTouchUpFilter(offset);
- final int width = mDrawable.getIntrinsicWidth();
- final int height = mDrawable.getIntrinsicHeight();
- final int line = mLayout.getLineForOffset(offset);
- final int lineBottom = mLayout.getLineBottom(line);
-
- final Rect bounds = sCursorControllerTempRect;
- bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX) +
- TextView.this.mScrollX;
- bounds.top = lineBottom + TextView.this.mScrollY;
-
- bounds.right = bounds.left + width;
- bounds.bottom = bounds.top + height;
-
- convertFromViewportToContentCoordinates(bounds);
- moveTo(bounds.left, bounds.top);
- }
-
- void showPastePopupWindow() {
- if (mIsInsertionHandle) {
- if (mPastePopupWindow == null) {
- // Lazy initialisation: create when actually shown only.
- mPastePopupWindow = new PastePopupMenu();
- }
- mPastePopupWindow.show();
- }
+ void onHandleMoved() {
+ // Does nothing by default
}
- void hidePastePopupWindow() {
- if (mPastePopupWindow != null) {
- mPastePopupWindow.hide();
- }
+ public void onDetached() {
+ // Should be overriden to clean possible Runnable
}
}
- private class InsertionPointCursorController implements CursorController {
+ private class InsertionHandleView extends HandleView {
private static final int DELAY_BEFORE_FADE_OUT = 4000;
- private static final int DELAY_BEFORE_PASTE = 2000;
- private static final int RECENT_CUT_COPY_DURATION = 15 * 1000;
+ private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
- // The cursor controller image. Lazily created.
- private HandleView mHandle;
+ // Used to detect taps on the insertion handle, which will affect the PastePopupWindow
+ private long mTouchTimer;
+ private float mDownPositionX, mDownPositionY;
+ private PastePopupWindow mPastePopupWindow;
private Runnable mHider;
private Runnable mPastePopupShower;
+ @Override
public void show() {
- show(DELAY_BEFORE_PASTE);
+ super.show();
+ hideDelayed();
+ hidePastePopupWindow();
}
public void show(int delayBeforePaste) {
- getHandle().show();
- hideDelayed();
- removePastePopupCallback();
+ show();
+
final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
delayBeforePaste = 0;
@@ -9056,81 +9077,256 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mPastePopupShower == null) {
mPastePopupShower = new Runnable() {
public void run() {
- getHandle().showPastePopupWindow();
+ showPastePopupWindow();
}
};
}
- postDelayed(mPastePopupShower, delayBeforePaste);
+ TextView.this.postDelayed(mPastePopupShower, delayBeforePaste);
}
}
- private void removePastePopupCallback() {
- if (mPastePopupShower != null) {
- removeCallbacks(mPastePopupShower);
+ @Override
+ protected void dismiss() {
+ super.dismiss();
+ onDetached();
+ }
+
+ private void hideDelayed() {
+ removeHiderCallback();
+ if (mHider == null) {
+ mHider = new Runnable() {
+ public void run() {
+ hide();
+ }
+ };
}
+ TextView.this.postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
}
private void removeHiderCallback() {
if (mHider != null) {
- removeCallbacks(mHider);
+ TextView.this.removeCallbacks(mHider);
}
}
- public void hide() {
- if (mHandle != null) {
- mHandle.hide();
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleCenter == null) {
+ mSelectHandleCenter = mContext.getResources().getDrawable(
+ mTextSelectHandleRes);
}
- removeHiderCallback();
- removePastePopupCallback();
+ mDrawable = mSelectHandleCenter;
+ mHotspotX = mDrawable.getIntrinsicWidth() / 2.0f;
}
- private void hideDelayed() {
- removeHiderCallback();
- if (mHider == null) {
- mHider = new Runnable() {
- public void run() {
- hide();
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final boolean result = super.onTouchEvent(ev);
+
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownPositionX = ev.getRawX();
+ mDownPositionY = ev.getRawY();
+ mTouchTimer = SystemClock.uptimeMillis();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ long delay = SystemClock.uptimeMillis() - mTouchTimer;
+ if (delay < ViewConfiguration.getTapTimeout()) {
+ final float deltaX = mDownPositionX - ev.getRawX();
+ final float deltaY = mDownPositionY - ev.getRawY();
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+ if (distanceSquared < mSquaredTouchSlopDistance) {
+ if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) {
+ // Tapping on the handle dismisses the displayed paste view,
+ mPastePopupWindow.hide();
+ } else {
+ show(0);
+ }
+ }
}
- };
+ hideDelayed();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ hideDelayed();
+ break;
+
+ default:
+ break;
}
- postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
+
+ return result;
}
- public boolean isShowing() {
- return mHandle != null && mHandle.isShowing();
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionStart();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, offset);
}
- public void updatePosition(HandleView handle, int x, int y) {
- final int previousOffset = getSelectionStart();
- final int newOffset = getOffset(x, y);
+ @Override
+ public void updatePosition(int x, int y) {
+ updateOffset(getOffset(x, y));
+ }
- if (newOffset != previousOffset) {
- updateOffset(handle, newOffset);
- removePastePopupCallback();
+ void showPastePopupWindow() {
+ if (mPastePopupWindow == null) {
+ mPastePopupWindow = new PastePopupWindow();
}
- hideDelayed();
+ mPastePopupWindow.show();
}
- public void updateOffset(HandleView handle, int offset) {
- Selection.setSelection((Spannable) mText, offset);
- updatePosition();
+ @Override
+ void onHandleMoved() {
+ removeHiderCallback();
+ hidePastePopupWindow();
}
- public void updatePosition() {
- final int offset = getSelectionStart();
+ void hidePastePopupWindow() {
+ if (mPastePopupShower != null) {
+ TextView.this.removeCallbacks(mPastePopupShower);
+ }
+ if (mPastePopupWindow != null) {
+ mPastePopupWindow.hide();
+ }
+ }
- if (offset < 0) {
- // Should never happen, safety check.
- Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
- hide();
- return;
+ @Override
+ public void onDetached() {
+ removeHiderCallback();
+ hidePastePopupWindow();
+ }
+ }
+
+ private class SelectionStartHandleView extends HandleView {
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = mContext.getResources().getDrawable(
+ mTextSelectHandleLeftRes);
+ }
+ mDrawable = mSelectHandleLeft;
+ mHotspotX = mDrawable.getIntrinsicWidth() * 3.0f / 4.0f;
+ }
+
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionStart();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
+ }
+
+ @Override
+ public void updatePosition(int x, int y) {
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ int offset = getOffset(x, y);
+
+ // No need to redraw when the offset is unchanged
+ if (offset == selectionStart) return;
+ // Handles can not cross and selection is at least one character
+ if (offset >= selectionEnd) offset = selectionEnd - 1;
+
+ Selection.setSelection((Spannable) mText, offset, selectionEnd);
+ }
+ }
+
+ private class SelectionEndHandleView extends HandleView {
+ @Override
+ protected void initDrawable() {
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = mContext.getResources().getDrawable(
+ mTextSelectHandleRightRes);
}
+ mDrawable = mSelectHandleRight;
+ mHotspotX = mDrawable.getIntrinsicWidth() / 4.0f;
+ }
+
+ @Override
+ public int getCurrentCursorOffset() {
+ return TextView.this.getSelectionEnd();
+ }
+
+ @Override
+ public void updateOffset(int offset) {
+ Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
+ }
+
+ @Override
+ public void updatePosition(int x, int y) {
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ int offset = getOffset(x, y);
+
+ // No need to redraw when the offset is unchanged
+ if (offset == selectionEnd) return;
+ // Handles can not cross and selection is at least one character
+ if (offset <= selectionStart) offset = selectionStart + 1;
+
+ Selection.setSelection((Spannable) mText, selectionStart, offset);
+ }
+ }
+
+ /**
+ * A CursorController instance can be used to control a cursor in the text.
+ * It is not used outside of {@link TextView}.
+ * @hide
+ */
+ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
+ /**
+ * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+ * See also {@link #hide()}.
+ */
+ public void show();
+
+ /**
+ * Hide the cursor controller from screen.
+ * See also {@link #show()}.
+ */
+ public void hide();
+
+ /**
+ * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+ * a chance to become active and/or visible.
+ * @param event The touch event
+ */
+ public boolean onTouchEvent(MotionEvent event);
+
+ /**
+ * Called when the view is detached from window. Perform house keeping task, such as
+ * stopping Runnable thread that would otherwise keep a reference on the context, thus
+ * preventing the activity from being recycled.
+ */
+ public void onDetached();
+ }
- getHandle().positionAtCursor(offset);
+ private class InsertionPointCursorController implements CursorController {
+ private static final int DELAY_BEFORE_PASTE = 2000;
+
+ private InsertionHandleView mHandle;
+
+ public void show() {
+ ((InsertionHandleView) getHandle()).show(DELAY_BEFORE_PASTE);
+ }
+
+ public void showWithPaste() {
+ ((InsertionHandleView) getHandle()).show(0);
}
- public int getCurrentOffset(HandleView handle) {
- return getSelectionStart();
+ public void hide() {
+ if (mHandle != null) {
+ mHandle.hide();
+ }
}
public boolean onTouchEvent(MotionEvent ev) {
@@ -9145,30 +9341,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private HandleView getHandle() {
if (mHandle == null) {
- mHandle = new HandleView(this, HandleView.CENTER);
+ mHandle = new InsertionHandleView();
}
return mHandle;
}
@Override
public void onDetached() {
- removeHiderCallback();
- removePastePopupCallback();
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnTouchModeChangeListener(this);
+
+ if (mHandle != null) mHandle.onDetached();
}
}
private class SelectionModifierCursorController implements CursorController {
- // The cursor controller images, lazily created when shown.
- private HandleView mStartHandle, mEndHandle;
+ // The cursor controller handles, lazily created when shown.
+ private SelectionStartHandleView mStartHandle;
+ private SelectionEndHandleView mEndHandle;
// The offsets of that last touch down event. Remembered to start selection there.
private int mMinTouchOffset, mMaxTouchOffset;
- // Whether selection anchors are active
- private boolean mIsShowing;
// Double tap detection
private long mPreviousTapUpTime = 0;
- private int mPreviousTapPositionX;
- private int mPreviousTapPositionY;
+ private int mPreviousTapPositionX, mPreviousTapPositionY;
SelectionModifierCursorController() {
resetTouchOffsets();
@@ -9180,96 +9376,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Lazy object creation has to be done before updatePosition() is called.
- if (mStartHandle == null) mStartHandle = new HandleView(this, HandleView.LEFT);
- if (mEndHandle == null) mEndHandle = new HandleView(this, HandleView.RIGHT);
-
- mIsShowing = true;
+ if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
+ if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
mStartHandle.show();
mEndHandle.show();
hideInsertionPointCursorController();
+ hideSuggestions();
}
public void hide() {
if (mStartHandle != null) mStartHandle.hide();
if (mEndHandle != null) mEndHandle.hide();
- mIsShowing = false;
- }
-
- public boolean isShowing() {
- return mIsShowing;
- }
-
- public void updatePosition(HandleView handle, int x, int y) {
- int selectionStart = getSelectionStart();
- int selectionEnd = getSelectionEnd();
-
- int offset = getOffset(x, y);
-
- // Handle the case where start and end are swapped, making sure start <= end
- if (handle == mStartHandle) {
- if (selectionStart == offset || offset > selectionEnd) {
- return; // no change, no need to redraw;
- }
- // If the user "closes" the selection entirely they were probably trying to
- // select a single character. Help them out.
- if (offset == selectionEnd) {
- offset = selectionEnd - 1;
- }
- selectionStart = offset;
- } else {
- if (selectionEnd == offset || offset < selectionStart) {
- return; // no change, no need to redraw;
- }
- // If the user "closes" the selection entirely they were probably trying to
- // select a single character. Help them out.
- if (offset == selectionStart) {
- offset = selectionStart + 1;
- }
- selectionEnd = offset;
- }
-
- Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
- updatePosition();
- }
-
- public void updateOffset(HandleView handle, int offset) {
- int start = getSelectionStart();
- int end = getSelectionEnd();
-
- if (mStartHandle == handle) {
- start = offset;
- } else {
- end = offset;
- }
-
- Selection.setSelection((Spannable) mText, start, end);
- updatePosition();
- }
-
- public void updatePosition() {
- if (!isShowing()) {
- return;
- }
-
- final int selectionStart = getSelectionStart();
- final int selectionEnd = getSelectionEnd();
-
- if ((selectionStart < 0) || (selectionEnd < 0)) {
- // Should never happen, safety check.
- Log.w(LOG_TAG, "Update selection controller position called with no cursor");
- hide();
- return;
- }
-
- // The handles have been created since the controller isShowing().
- mStartHandle.positionAtCursor(selectionStart);
- mEndHandle.positionAtCursor(selectionEnd);
- }
-
- public int getCurrentOffset(HandleView handle) {
- return mStartHandle == handle ? getSelectionStart() : getSelectionEnd();
}
public boolean onTouchEvent(MotionEvent event) {
@@ -9292,7 +9411,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int deltaY = y - mPreviousTapPositionY;
final int distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared < mSquaredTouchSlopDistance) {
- startSelectionActionMode();
+ showSuggestions();
mDiscardNextActionUp = true;
}
}
@@ -9360,7 +9479,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public void onDetached() {}
+ public void onDetached() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnTouchModeChangeListener(this);
+
+ if (mStartHandle != null) mStartHandle.onDetached();
+ if (mEndHandle != null) mEndHandle.onDetached();
+ }
}
private void hideInsertionPointCursorController() {
@@ -9376,6 +9501,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void hideControllers() {
hideInsertionPointCursorController();
stopSelectionActionMode();
+ hideSuggestions();
}
/**
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 029d690..423e735 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -409,7 +409,9 @@ public class TimePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
int flags = DateUtils.FORMAT_SHOW_TIME;
if (mIs24HourView) {
flags |= DateUtils.FORMAT_24HOUR;
@@ -421,7 +423,6 @@ public class TimePicker extends FrameLayout {
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
private void updateHourControl() {
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 8f1354b..dccfa6c 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -25,13 +25,13 @@ import com.android.internal.widget.ActionBarView;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
-import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -42,9 +42,10 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.SpinnerAdapter;
@@ -59,6 +60,7 @@ import java.util.ArrayList;
* which is normally hidden.
*/
public class ActionBarImpl extends ActionBar {
+ private static final String TAG = "ActionBarImpl";
private static final int NORMAL_VIEW = 0;
private static final int CONTEXT_VIEW = 1;
@@ -71,6 +73,7 @@ public class ActionBarImpl extends ActionBar {
private ActionBarContextView mUpperContextView;
private LinearLayout mLowerContextView;
private View mContentView;
+ private ViewGroup mExternalTabView;
private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
@@ -92,60 +95,34 @@ public class ActionBarImpl extends ActionBar {
final Handler mHandler = new Handler();
- private Animator mCurrentAnim;
+ private Animator mCurrentShowAnim;
+ private Animator mCurrentModeAnim;
private boolean mShowHideAnimationEnabled;
+ boolean mWasHiddenBeforeMode;
private static final TimeInterpolator sFadeOutInterpolator = new DecelerateInterpolator();
final AnimatorListener[] mAfterAnimation = new AnimatorListener[] {
- new AnimatorListener() { // NORMAL_VIEW
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ new AnimatorListenerAdapter() { // NORMAL_VIEW
@Override
public void onAnimationEnd(Animator animation) {
if (mLowerContextView != null) {
mLowerContextView.removeAllViews();
}
- mCurrentAnim = null;
+ mCurrentModeAnim = null;
hideAllExcept(NORMAL_VIEW);
}
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
},
- new AnimatorListener() { // CONTEXT_VIEW
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ new AnimatorListenerAdapter() { // CONTEXT_VIEW
@Override
public void onAnimationEnd(Animator animation) {
- mCurrentAnim = null;
+ mCurrentModeAnim = null;
hideAllExcept(CONTEXT_VIEW);
}
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
}
};
- final AnimatorListener mHideListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mContentView != null) {
@@ -153,36 +130,16 @@ public class ActionBarImpl extends ActionBar {
}
mContainerView.setVisibility(View.GONE);
mContainerView.setTransitioning(false);
- mCurrentAnim = null;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
+ mCurrentShowAnim = null;
}
};
- final AnimatorListener mShowListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
+ final AnimatorListener mShowListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mCurrentAnim = null;
+ mCurrentShowAnim = null;
mContainerView.requestLayout();
}
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
};
public ActionBarImpl(Activity activity) {
@@ -218,6 +175,18 @@ public class ActionBarImpl extends ActionBar {
mActionView.setContextView(mUpperContextView);
mContextDisplayMode = mLowerContextView == null ?
CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
+
+ if (!mActionView.hasEmbeddedTabs()) {
+ HorizontalScrollView tabScroller = new HorizontalScrollView(mContext);
+ ViewGroup tabContainer = mActionView.createTabContainer();
+ tabScroller.setHorizontalFadingEdgeEnabled(true);
+ tabScroller.addView(tabContainer);
+ tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ?
+ View.VISIBLE : View.GONE);
+ mActionView.setExternalTabLayout(tabContainer);
+ mContainerView.setTabContainer(tabScroller);
+ mExternalTabView = tabScroller;
+ }
}
/**
@@ -229,8 +198,8 @@ public class ActionBarImpl extends ActionBar {
*/
public void setShowHideAnimationEnabled(boolean enabled) {
mShowHideAnimationEnabled = enabled;
- if (!enabled && mCurrentAnim != null) {
- mCurrentAnim.end();
+ if (!enabled && mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
}
@@ -285,6 +254,11 @@ public class ActionBarImpl extends ActionBar {
}
@Override
+ public void setDisplayDisableHomeEnabled(boolean disableHome) {
+ setDisplayOptions(disableHome ? DISPLAY_DISABLE_HOME : 0, DISPLAY_DISABLE_HOME);
+ }
+
+ @Override
public void setTitle(int resId) {
setTitle(mContext.getString(resId));
}
@@ -370,6 +344,7 @@ public class ActionBarImpl extends ActionBar {
mUpperContextView.killMode();
ActionMode mode = new ActionModeImpl(callback);
if (callback.onCreateActionMode(mode, mode.getMenu())) {
+ mWasHiddenBeforeMode = !isShowing();
mode.invalidate();
mUpperContextView.initForMode(mode);
animateTo(CONTEXT_VIEW);
@@ -378,7 +353,6 @@ public class ActionBarImpl extends ActionBar {
mLowerContextView.setVisibility(View.VISIBLE);
}
mActionMode = mode;
- show();
return mode;
}
return null;
@@ -498,10 +472,15 @@ public class ActionBarImpl extends ActionBar {
@Override
public void show() {
- if (mCurrentAnim != null) {
- mCurrentAnim.end();
+ show(true);
+ }
+
+ void show(boolean markHiddenBeforeMode) {
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
if (mContainerView.getVisibility() == View.VISIBLE) {
+ if (markHiddenBeforeMode) mWasHiddenBeforeMode = false;
return;
}
mContainerView.setVisibility(View.VISIBLE);
@@ -517,17 +496,19 @@ public class ActionBarImpl extends ActionBar {
b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0));
}
anim.addListener(mShowListener);
- mCurrentAnim = anim;
+ mCurrentShowAnim = anim;
anim.start();
} else {
+ mContainerView.setAlpha(1);
+ mContainerView.setTranslationY(0);
mShowListener.onAnimationEnd(null);
}
}
@Override
public void hide() {
- if (mCurrentAnim != null) {
- mCurrentAnim.end();
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.end();
}
if (mContainerView.getVisibility() == View.GONE) {
return;
@@ -545,7 +526,7 @@ public class ActionBarImpl extends ActionBar {
-mContainerView.getHeight()));
}
anim.addListener(mHideListener);
- mCurrentAnim = anim;
+ mCurrentShowAnim = anim;
anim.start();
} else {
mHideListener.onAnimationEnd(null);
@@ -556,19 +537,23 @@ public class ActionBarImpl extends ActionBar {
return mContainerView.getVisibility() == View.VISIBLE;
}
- private long animateTo(int viewIndex) {
- show();
+ long animateTo(int viewIndex) {
+ show(false);
+ if (mCurrentModeAnim != null) {
+ mCurrentModeAnim.end();
+ }
AnimatorSet set = new AnimatorSet();
final View targetChild = mContainerView.getChildAt(viewIndex);
targetChild.setVisibility(View.VISIBLE);
+ targetChild.setAlpha(0);
AnimatorSet.Builder b = set.play(ObjectAnimator.ofFloat(targetChild, "alpha", 1));
final int count = mContainerView.getChildCount();
for (int i = 0; i < count; i++) {
final View child = mContainerView.getChildAt(i);
- if (i == viewIndex) {
+ if (i == viewIndex || child == mContainerView.getTabContainer()) {
continue;
}
@@ -581,7 +566,7 @@ public class ActionBarImpl extends ActionBar {
set.addListener(mAfterAnimation[viewIndex]);
- mCurrentAnim = set;
+ mCurrentModeAnim = set;
set.start();
return set.getDuration();
}
@@ -636,6 +621,10 @@ public class ActionBarImpl extends ActionBar {
mLowerContextView.setVisibility(View.GONE);
}
mActionMode = null;
+
+ if (mWasHiddenBeforeMode) {
+ hide();
+ }
}
@Override
@@ -718,7 +707,7 @@ public class ActionBarImpl extends ActionBar {
return;
}
invalidate();
- mUpperContextView.openOverflowMenu();
+ mUpperContextView.showOverflowMenu();
}
}
@@ -871,11 +860,17 @@ public class ActionBarImpl extends ActionBar {
case NAVIGATION_MODE_TABS:
mSavedTabPosition = getSelectedNavigationIndex();
selectTab(null);
+ if (!mActionView.hasEmbeddedTabs()) {
+ mExternalTabView.setVisibility(View.GONE);
+ }
break;
}
mActionView.setNavigationMode(mode);
switch (mode) {
case NAVIGATION_MODE_TABS:
+ if (!mActionView.hasEmbeddedTabs()) {
+ mExternalTabView.setVisibility(View.VISIBLE);
+ }
if (mSavedTabPosition != INVALID_POSITION) {
setSelectedNavigationItem(mSavedTabPosition);
mSavedTabPosition = INVALID_POSITION;
@@ -889,23 +884,24 @@ public class ActionBarImpl extends ActionBar {
return mTabs.get(index);
}
- /**
- * This fragment is added when we're keeping a back stack in a tab switch
- * transaction. We use it to change the selected tab in the action bar view
- * when we back out.
- */
- private class SwitchSelectedTabViewFragment extends Fragment {
- private int mSelectedTabIndex;
- public SwitchSelectedTabViewFragment(int oldSelectedTab) {
- mSelectedTabIndex = oldSelectedTab;
- }
+ @Override
+ public void setIcon(int resId) {
+ mActionView.setIcon(resId);
+ }
- @Override
- public void onDetach() {
- if (mSelectedTabIndex >= 0 && mSelectedTabIndex < getTabCount()) {
- mActionView.setTabSelected(mSelectedTabIndex);
- }
- }
+ @Override
+ public void setIcon(Drawable icon) {
+ mActionView.setIcon(icon);
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ mActionView.setLogo(resId);
+ }
+
+ @Override
+ public void setLogo(Drawable logo) {
+ mActionView.setLogo(logo);
}
}
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index aee1626..dd22e25 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -27,8 +27,9 @@ interface IMediaContainerService {
String key, String resFileName);
boolean copyResource(in Uri packageURI,
in ParcelFileDescriptor outStream);
- PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags);
- boolean checkFreeStorage(boolean external, in Uri fileUri);
+ PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold);
+ boolean checkInternalFreeStorage(in Uri fileUri, in long threshold);
+ boolean checkExternalFreeStorage(in Uri fileUri);
ObbInfo getObbInfo(in String filename);
long calculateDirectorySize(in String directory);
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 2e56996..ba2f5d4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -30,7 +30,6 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.PatternMatcher;
-import android.util.Config;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -238,7 +237,7 @@ public class ResolverActivity extends AlertActivity implements
ResolveInfo r0 = rList.get(0);
for (int i=1; i<N; i++) {
ResolveInfo ri = rList.get(i);
- if (Config.LOGV) Log.v(
+ if (false) Log.v(
"ResolveListActivity",
r0.activityInfo.name + "=" +
r0.priority + "/" + r0.isDefault + " vs " +
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 4ae55fc..9ae7def 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -4,7 +4,6 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.FileUtils;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -176,7 +175,7 @@ public class NativeLibraryHelper {
continue;
}
- if (Config.LOGD) {
+ if (false) {
Log.d(TAG, "Found gdbserver: " + entry.getName());
}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index d6c43f9..b57046c 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -56,18 +56,13 @@ public class PackageHelper {
return null;
}
- public static String createSdDir(long sizeBytes, String cid,
+ public static String createSdDir(int sizeMb, String cid,
String sdEncKey, int uid) {
// Create mount point via MountService
IMountService mountService = getMountService();
- int sizeMb = (int) (sizeBytes >> 20);
- if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
- sizeMb++;
- }
- // Add buffer size
- sizeMb++;
+
if (localLOGV)
- Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes");
+ Log.i(TAG, "Size of container " + sizeMb + " MB");
try {
int rc = mountService.createSecureContainer(
diff --git a/core/java/com/android/internal/net/DomainNameValidator.java b/core/java/com/android/internal/net/DomainNameValidator.java
index 36973f1..3950655 100644
--- a/core/java/com/android/internal/net/DomainNameValidator.java
+++ b/core/java/com/android/internal/net/DomainNameValidator.java
@@ -16,7 +16,6 @@
package com.android.internal.net;
import android.net.NetworkUtils;
-import android.util.Config;
import android.util.Log;
import java.net.InetAddress;
@@ -35,7 +34,7 @@ public class DomainNameValidator {
private final static String TAG = "DomainNameValidator";
private static final boolean DEBUG = false;
- private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOG_ENABLED = false;
private static final int ALT_DNS_NAME = 2;
private static final int ALT_IPA_NAME = 7;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index d86504d..7cf33fc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -36,6 +36,7 @@ import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.util.LogWriter;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -70,7 +71,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 54;
+ private static final int VERSION = 60;
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -154,11 +155,27 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mHaveBatteryLevel = false;
boolean mRecordingHistory = true;
int mNumHistoryItems;
+
+ static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
+ static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+ final Parcel mHistoryBuffer = Parcel.obtain();
+ final HistoryItem mHistoryLastWritten = new HistoryItem();
+ final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+ final HistoryItem mHistoryReadTmp = new HistoryItem();
+ int mHistoryBufferLastPos = -1;
+ boolean mHistoryOverflow = false;
+ long mLastHistoryTime = 0;
+
+ final HistoryItem mHistoryCur = new HistoryItem();
+
HistoryItem mHistory;
HistoryItem mHistoryEnd;
HistoryItem mHistoryLastEnd;
HistoryItem mHistoryCache;
- final HistoryItem mHistoryCur = new HistoryItem();
+
+ private HistoryItem mHistoryIterator;
+ private boolean mReadOverflow;
+ private boolean mIteratingHistory;
int mStartCount;
@@ -1189,9 +1206,84 @@ public final class BatteryStatsImpl extends BatteryStats {
mBtHeadset = headset;
}
+ int mChangedBufferStates = 0;
+
+ void addHistoryBufferLocked(long curTime) {
+ if (!mHaveBatteryLevel || !mRecordingHistory) {
+ return;
+ }
+
+ final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+ if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiff < 2000
+ && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
+ // If the current is the same as the one before, then we no
+ // longer need the entry.
+ mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+ mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+ mHistoryBufferLastPos = -1;
+ if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) {
+ // If this results in us returning to the state written
+ // prior to the last one, then we can just delete the last
+ // written one and drop the new one. Nothing more to do.
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+ return;
+ }
+ mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
+ curTime = mHistoryLastWritten.time - mHistoryBaseTime;
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ } else {
+ mChangedBufferStates = 0;
+ }
+
+ final int dataSize = mHistoryBuffer.dataSize();
+ if (dataSize >= MAX_HISTORY_BUFFER) {
+ if (!mHistoryOverflow) {
+ mHistoryOverflow = true;
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ }
+
+ // Once we've reached the maximum number of items, we only
+ // record changes to the battery level and the most interesting states.
+ // Once we've reached the maximum maximum number of items, we only
+ // record changes to the battery level.
+ if (mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel &&
+ (dataSize >= MAX_MAX_HISTORY_BUFFER
+ || ((mHistoryEnd.states^mHistoryCur.states)
+ & HistoryItem.MOST_INTERESTING_STATES) == 0)) {
+ return;
+ }
+ }
+
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+ }
+
+ void addHistoryBufferLocked(long curTime, byte cmd) {
+ int origPos = 0;
+ if (mIteratingHistory) {
+ origPos = mHistoryBuffer.dataPosition();
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ }
+ mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+ mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+ mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
+ mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten);
+ mLastHistoryTime = curTime;
+ if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ + " now " + mHistoryBuffer.dataPosition()
+ + " size is now " + mHistoryBuffer.dataSize());
+ if (mIteratingHistory) {
+ mHistoryBuffer.setDataPosition(origPos);
+ }
+ }
+
int mChangedStates = 0;
void addHistoryRecordLocked(long curTime) {
+ addHistoryBufferLocked(curTime);
+
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
}
@@ -1206,6 +1298,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// If the current is the same as the one before, then we no
// longer need the entry.
if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+ && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)
&& mHistoryLastEnd.same(mHistoryCur)) {
mHistoryLastEnd.next = null;
mHistoryEnd.next = mHistoryCache;
@@ -1268,6 +1361,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void clearHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
if (mHistory != null) {
mHistoryEnd.next = mHistoryCache;
mHistoryCache = mHistory;
@@ -1275,6 +1369,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mNumHistoryItems = 0;
mHistoryBaseTime = 0;
+ mLastHistoryTime = 0;
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
+ mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryBufferLastPos = -1;
+ mHistoryOverflow = false;
}
public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
@@ -3910,11 +4013,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeUnplugLevel = 0;
mDischargeCurrentLevel = 0;
initDischarge();
+ clearHistoryLocked();
}
public BatteryStatsImpl(Parcel p) {
mFile = null;
mHandler = null;
+ clearHistoryLocked();
readFromParcel(p);
}
@@ -3932,25 +4037,84 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- private HistoryItem mHistoryIterator;
-
- public boolean startIteratingHistoryLocked() {
+ @Override
+ public boolean startIteratingOldHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ + " pos=" + mHistoryBuffer.dataPosition());
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryReadTmp.clear();
+ mReadOverflow = false;
+ mIteratingHistory = true;
return (mHistoryIterator = mHistory) != null;
}
- public boolean getNextHistoryLocked(HistoryItem out) {
+ @Override
+ public boolean getNextOldHistoryLocked(HistoryItem out) {
+ boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
+ if (!end) {
+ mHistoryReadTmp.readDelta(mHistoryBuffer);
+ mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
+ }
HistoryItem cur = mHistoryIterator;
if (cur == null) {
+ if (!mReadOverflow && !end) {
+ Slog.w(TAG, "Old history ends before new history!");
+ }
return false;
}
out.setTo(cur);
mHistoryIterator = cur.next;
+ if (!mReadOverflow) {
+ if (end) {
+ Slog.w(TAG, "New history ends before old history!");
+ } else if (!out.same(mHistoryReadTmp)) {
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+ PrintWriter pw = new PrintWriter(new LogWriter(android.util.Log.WARN, TAG));
+ pw.println("Histories differ!");
+ pw.println("Old history:");
+ (new HistoryPrinter()).printNextItem(pw, out, now);
+ pw.println("New history:");
+ (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now);
+ }
+ }
return true;
}
@Override
- public HistoryItem getHistory() {
- return mHistory;
+ public void finishIteratingOldHistoryLocked() {
+ mIteratingHistory = false;
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ }
+
+ @Override
+ public boolean startIteratingHistoryLocked() {
+ if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ + " pos=" + mHistoryBuffer.dataPosition());
+ mHistoryBuffer.setDataPosition(0);
+ mReadOverflow = false;
+ mIteratingHistory = true;
+ return mHistoryBuffer.dataSize() > 0;
+ }
+
+ @Override
+ public boolean getNextHistoryLocked(HistoryItem out) {
+ final int pos = mHistoryBuffer.dataPosition();
+ if (pos == 0) {
+ out.clear();
+ }
+ boolean end = pos >= mHistoryBuffer.dataSize();
+ if (end) {
+ return false;
+ }
+
+ out.readDelta(mHistoryBuffer);
+ return true;
+ }
+
+ @Override
+ public void finishIteratingHistoryLocked() {
+ mIteratingHistory = false;
+ mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
}
@Override
@@ -4697,7 +4861,9 @@ public final class BatteryStatsImpl extends BatteryStats {
Slog.e("BatteryStats", "Error reading battery statistics", e);
}
- addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START);
+ long now = SystemClock.elapsedRealtime();
+ addHistoryRecordLocked(now, HistoryItem.CMD_START);
+ addHistoryBufferLocked(now, HistoryItem.CMD_START);
}
public int describeContents() {
@@ -4705,30 +4871,54 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void readHistory(Parcel in) {
- mHistory = mHistoryEnd = mHistoryCache = null;
- mHistoryBaseTime = 0;
- long time;
- while ((time=in.readLong()) >= 0) {
- HistoryItem rec = new HistoryItem(time, in);
- addHistoryRecordLocked(rec);
- if (rec.time > mHistoryBaseTime) {
- mHistoryBaseTime = rec.time;
- }
+ mHistoryBaseTime = in.readLong();
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+
+ int bufSize = in.readInt();
+ int curPos = in.dataPosition();
+ if (bufSize >= (MAX_MAX_HISTORY_BUFFER*3)) {
+ Slog.w(TAG, "File corrupt: history data buffer too large " + bufSize);
+ } else if ((bufSize&~3) != bufSize) {
+ Slog.w(TAG, "File corrupt: history data buffer not aligned " + bufSize);
+ } else {
+ if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+ + " bytes at " + curPos);
+ mHistoryBuffer.appendFrom(in, curPos, bufSize);
+ in.setDataPosition(curPos + bufSize);
}
- long oldnow = SystemClock.elapsedRealtime() - (5*60*100);
+ long oldnow = SystemClock.elapsedRealtime() - (5*60*1000);
if (oldnow > 0) {
// If the system process has restarted, but not the entire
// system, then the mHistoryBaseTime already accounts for
// much of the elapsed time. We thus want to adjust it back,
// to avoid large gaps in the data. We determine we are
// in this case by arbitrarily saying it is so if at this
- // point in boot the elapsed time is already more than 5 seconds.
+ // point in boot the elapsed time is already more than 5 minutes.
mHistoryBaseTime -= oldnow;
}
}
+ void readOldHistory(Parcel in) {
+ mHistory = mHistoryEnd = mHistoryCache = null;
+ long time;
+ while ((time=in.readLong()) >= 0) {
+ HistoryItem rec = new HistoryItem(time, in);
+ addHistoryRecordLocked(rec);
+ }
+ }
+
void writeHistory(Parcel out) {
+ out.writeLong(mLastHistoryTime);
+ out.writeInt(mHistoryBuffer.dataSize());
+ if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+ + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+ out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ }
+
+ void writeOldHistory(Parcel out) {
HistoryItem rec = mHistory;
while (rec != null) {
if (rec.time >= 0) rec.writeToParcel(out, 0);
@@ -4746,6 +4936,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
readHistory(in);
+ readOldHistory(in);
mStartCount = in.readInt();
mBatteryUptime = in.readLong();
@@ -4935,6 +5126,9 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
+ // Need to update with current kernel wake lock counts.
+ updateKernelWakelocksLocked();
+
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
final long NOW = getBatteryUptimeLocked(NOW_SYS);
@@ -4943,6 +5137,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(VERSION);
writeHistory(out);
+ writeOldHistory(out);
out.writeInt(mStartCount);
out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
@@ -5256,6 +5451,9 @@ public final class BatteryStatsImpl extends BatteryStats {
@SuppressWarnings("unused")
void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
+ // Need to update with current kernel wake lock counts.
+ updateKernelWakelocksLocked();
+
final long uSecUptime = SystemClock.uptimeMillis() * 1000;
final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
@@ -5358,6 +5556,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
};
+ public void prepareForDumpLocked() {
+ // Need to retrieve current kernel wake lock stats before printing.
+ updateKernelWakelocksLocked();
+ }
+
public void dumpLocked(PrintWriter pw) {
if (DEBUG) {
Printer pr = new PrintWriterPrinter(pw);
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index ba0bf0d..f54a3e9 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -19,7 +19,6 @@ package com.android.internal.os;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f58f261..0f086f6 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -23,7 +23,6 @@ import android.os.Debug;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.Log;
import android.util.Slog;
@@ -90,14 +89,14 @@ public class RuntimeInit {
}
private static final void commonInit() {
- if (Config.LOGV) Slog.d(TAG, "Entered RuntimeInit!");
+ if (false) Slog.d(TAG, "Entered RuntimeInit!");
/* set default handler; this applies to all threads in the VM */
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
int hasQwerty = getQwertyKeyboard();
- if (Config.LOGV) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
+ if (false) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
@@ -234,7 +233,7 @@ public class RuntimeInit {
*/
finishInit();
- if (Config.LOGV) Slog.d(TAG, "Leaving RuntimeInit!");
+ if (false) Slog.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 8c256e0..df0fcd9 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -20,12 +20,15 @@ import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
-import dalvik.system.SamplingProfiler;
+import dalvik.system.profiler.BinaryHprofWriter;
+import dalvik.system.profiler.SamplingProfiler;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintStream;
+import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -80,7 +83,8 @@ public class SamplingProfilerIntegration {
}
}
- private static SamplingProfiler INSTANCE;
+ private static SamplingProfiler samplingProfiler;
+ private static long startMillis;
/**
* Is profiling enabled?
@@ -96,10 +100,16 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
+ if (samplingProfiler != null) {
+ Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis));
+ return;
+ }
+
ThreadGroup group = Thread.currentThread().getThreadGroup();
SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group);
- INSTANCE = new SamplingProfiler(samplingProfilerDepth, threadSet);
- INSTANCE.start(samplingProfilerMilliseconds);
+ samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet);
+ samplingProfiler.start(samplingProfilerMilliseconds);
+ startMillis = System.currentTimeMillis();
}
/**
@@ -109,6 +119,10 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
+ if (samplingProfiler == null) {
+ Log.e(TAG, "SamplingProfilerIntegration is not started");
+ return;
+ }
/*
* If we're already writing a snapshot, don't bother enqueueing another
@@ -137,8 +151,9 @@ public class SamplingProfilerIntegration {
return;
}
writeSnapshotFile("zygote", null);
- INSTANCE.shutdown();
- INSTANCE = null;
+ samplingProfiler.shutdown();
+ samplingProfiler = null;
+ startMillis = 0;
}
/**
@@ -148,40 +163,44 @@ public class SamplingProfilerIntegration {
if (!enabled) {
return;
}
- INSTANCE.stop();
+ samplingProfiler.stop();
/*
- * We use the current time as a unique ID. We can't use a counter
- * because processes restart. This could result in some overlap if
- * we capture two snapshots in rapid succession.
+ * We use the global start time combined with the process name
+ * as a unique ID. We can't use a counter because processes
+ * restart. This could result in some overlap if we capture
+ * two snapshots in rapid succession.
*/
- long start = System.currentTimeMillis();
String name = processName.replaceAll(":", ".");
- String path = SNAPSHOT_DIR + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
- PrintStream out = null;
+ String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot";
+ long start = System.currentTimeMillis();
+ OutputStream outputStream = null;
try {
- out = new PrintStream(new BufferedOutputStream(new FileOutputStream(path)));
+ outputStream = new BufferedOutputStream(new FileOutputStream(path));
+ PrintStream out = new PrintStream(outputStream);
generateSnapshotHeader(name, packageInfo, out);
- new SamplingProfiler.AsciiHprofWriter(INSTANCE.getHprofData(), out).write();
if (out.checkError()) {
throw new IOException();
}
+ BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream);
} catch (IOException e) {
Log.e(TAG, "Error writing snapshot to " + path, e);
return;
} finally {
- IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(outputStream);
}
// set file readable to the world so that SamplingProfilerService
// can put it to dropbox
new File(path).setReadable(true, false);
long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "Wrote snapshot for " + name + " in " + elapsed + "ms.");
+ Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms.");
+ samplingProfiler.start(samplingProfilerMilliseconds);
}
/**
- * generate header for snapshots, with the following format (like http header):
+ * generate header for snapshots, with the following format
+ * (like an HTTP header but without the \r):
*
* Version: <version number of profiler>\n
* Process: <process name>\n
@@ -194,7 +213,7 @@ public class SamplingProfilerIntegration {
private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
PrintStream out) {
// profiler version
- out.println("Version: 2");
+ out.println("Version: 3");
out.println("Process: " + processName);
if (packageInfo != null) {
out.println("Package: " + packageInfo.packageName);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index dea53bf..fbe66e5 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -25,13 +25,13 @@ import android.os.Debug;
import android.os.FileUtils;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import dalvik.system.VMRuntime;
import dalvik.system.Zygote;
-import dalvik.system.SamplingProfiler;
+
+import libcore.io.IoUtils;
import java.io.BufferedReader;
import java.io.FileDescriptor;
@@ -99,25 +99,6 @@ public class ZygoteInit {
private static final boolean PRELOAD_RESOURCES = true;
/**
- * List of methods we "warm up" in the register map cache. These were
- * chosen because they appeared on the stack in GCs in multiple
- * applications.
- *
- * This is in a VM-ready format, to minimize string processing. If a
- * class is not already loaded, or a method is not found, the entry
- * will be skipped.
- *
- * This doesn't really merit a separately-generated input file at this
- * time. The list is fairly short, and the consequences of failure
- * are minor.
- */
- private static final String[] REGISTER_MAP_METHODS = {
- // (currently not doing any)
- //"Landroid/app/Activity;.setContentView:(I)V",
- };
-
-
- /**
* Invokes a static "main(argv[]) method on class "className".
* Converts various failing exceptions into RuntimeExceptions, with
* the assumption that they will then cause the VM instance to exit.
@@ -274,7 +255,7 @@ public class ZygoteInit {
runtime.setTargetHeapUtilization(0.8f);
// Start with a clean slate.
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.startAllocCounting();
@@ -292,16 +273,16 @@ public class ZygoteInit {
}
try {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading " + line + "...");
}
Class.forName(line);
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG,
" GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
@@ -325,6 +306,7 @@ public class ZygoteInit {
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
+ IoUtils.closeQuietly(is);
// Restore default.
runtime.setTargetHeapUtilization(defaultUtilization);
@@ -338,45 +320,6 @@ public class ZygoteInit {
}
/**
- * Pre-caches register maps for methods that are commonly used.
- */
- private static void cacheRegisterMaps() {
- String failed = null;
- int failure;
- long startTime = System.nanoTime();
-
- failure = 0;
-
- for (int i = 0; i < REGISTER_MAP_METHODS.length; i++) {
- String str = REGISTER_MAP_METHODS[i];
-
- if (!Debug.cacheRegisterMap(str)) {
- if (failed == null)
- failed = str;
- failure++;
- }
- }
-
- long delta = System.nanoTime() - startTime;
-
- if (failure == REGISTER_MAP_METHODS.length) {
- if (REGISTER_MAP_METHODS.length > 0) {
- Log.i(TAG,
- "Register map caching failed (precise GC not enabled?)");
- }
- return;
- }
-
- Log.i(TAG, "Register map cache: found " +
- (REGISTER_MAP_METHODS.length - failure) + " of " +
- REGISTER_MAP_METHODS.length + " methods in " +
- (delta / 1000000L) + "ms");
- if (failure > 0) {
- Log.i(TAG, " First failure: " + failed);
- }
- }
-
- /**
* Load in commonly used resources, so they can be shared across
* processes.
*
@@ -388,7 +331,7 @@ public class ZygoteInit {
Debug.startAllocCounting();
try {
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
mResources = Resources.getSystem();
mResources.startPreloading();
@@ -421,15 +364,15 @@ public class ZygoteInit {
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
@@ -444,15 +387,15 @@ public class ZygoteInit {
int N = ar.length();
for (int i=0; i<N; i++) {
if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
}
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
Debug.resetGlobalAllocSize();
}
int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
+ if (false) {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
@@ -478,11 +421,11 @@ public class ZygoteInit {
/* runFinalizationSync() lets finalizers be called in Zygote,
* which doesn't have a HeapWorker thread.
*/
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
- runtime.gcSoftReferences();
+ System.gc();
runtime.runFinalizationSync();
}
@@ -564,7 +507,6 @@ public class ZygoteInit {
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preloadClasses();
- //cacheRegisterMaps();
preloadResources();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 101dd91..4d656c0 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -135,6 +135,8 @@ public class AsyncChannel {
* channel is forcibly disconnected by the system or as a reply to CMD_CHANNEL_DISCONNECT.
*
* msg.arg1 == 0 : STATUS_SUCCESSFUL
+ * 1 : STATUS_BINDING_UNSUCCESSFUL
+ * 2 : STATUS_SEND_UNSUCCESSFUL
* : All other values signify failure and the channel state is indeterminate
* msg.obj == the AsyncChannel
* msg.replyTo = messenger disconnecting or null if it was never connected.
@@ -147,6 +149,9 @@ public class AsyncChannel {
/** Error attempting to bind on a connect */
public static final int STATUS_BINDING_UNSUCCESSFUL = 1;
+ /** Error attempting to send a message */
+ public static final int STATUS_SEND_UNSUCCESSFUL = 2;
+
/** Service connection */
private AsyncChannelConnection mConnection;
@@ -345,11 +350,7 @@ public class AsyncChannel {
mSrcContext.unbindService(mConnection);
}
if (mSrcHandler != null) {
- Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
- msg.arg1 = STATUS_SUCCESSFUL;
- msg.obj = this;
- msg.replyTo = mDstMessenger;
- mSrcHandler.sendMessage(msg);
+ replyDisconnected(STATUS_SUCCESSFUL);
}
}
@@ -363,7 +364,7 @@ public class AsyncChannel {
try {
mDstMessenger.send(msg);
} catch (RemoteException e) {
- log("TODO: handle sendMessage RemoteException" + e);
+ replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
}
}
@@ -712,6 +713,7 @@ public class AsyncChannel {
/**
* Reply to the src handler that we're half connected.
+ * see: CMD_CHANNEL_HALF_CONNECTED for message contents
*
* @param status to be stored in msg.arg1
*/
@@ -724,6 +726,21 @@ public class AsyncChannel {
}
/**
+ * Reply to the src handler that we are disconnected
+ * see: CMD_CHANNEL_DISCONNECTED for message contents
+ *
+ * @param status to be stored in msg.arg1
+ */
+ private void replyDisconnected(int status) {
+ Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
+ msg.arg1 = status;
+ msg.obj = this;
+ msg.replyTo = mDstMessenger;
+ mSrcHandler.sendMessage(msg);
+ }
+
+
+ /**
* ServiceConnection to receive call backs.
*/
class AsyncChannelConnection implements ServiceConnection {
@@ -736,11 +753,7 @@ public class AsyncChannel {
}
public void onServiceDisconnected(ComponentName className) {
- Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
- msg.arg1 = STATUS_SUCCESSFUL;
- msg.obj = AsyncChannel.this;
- msg.replyTo = mDstMessenger;
- mSrcHandler.sendMessage(msg);
+ replyDisconnected(STATUS_SUCCESSFUL);
}
}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index b5df812..c792d78 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -174,7 +174,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
public void performPrivateCommand(String action, Bundle data) {
dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
}
-
+
void dispatchMessage(Message msg) {
// If we are calling this from the main thread, then we can call
// right through. Otherwise, we need to send the message to the
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index e00dd4e..719a24f 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -72,4 +72,5 @@ import com.android.internal.view.IInputContextCallback;
void setComposingRegion(int start, int end);
void getSelectedText(int flags, int seq, IInputContextCallback callback);
+
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 611d987..4ffa4e1 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,6 +33,7 @@ interface IInputMethodManager {
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes);
+ InputMethodSubtype getLastInputMethodSubtype();
// TODO: We should change the return type from List to List<Parcelable>
// Currently there is a bug that aidl doesn't accept List<Parcelable>
List getShortcutInputMethodsAndSubtypes();
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index b13118a..a235d9a 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -251,7 +251,7 @@ public class InputConnectionWrapper implements InputConnection {
}
return value;
}
-
+
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
ExtractedText value = null;
try {
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index 2d067da..b54daba 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -135,6 +135,6 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
public void onMenuModeChange(MenuBuilder menu) {
invalidate();
- mContextView.openOverflowMenu();
+ mContextView.showOverflowMenu();
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 3325df6..beacf75 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -28,7 +28,7 @@ import android.widget.LinearLayout;
* @hide
*/
public class ActionMenuItemView extends LinearLayout
- implements MenuView.ItemView, View.OnClickListener {
+ implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
@@ -56,6 +56,7 @@ public class ActionMenuItemView extends LinearLayout
mTextButton = (Button) findViewById(com.android.internal.R.id.textButton);
mImageButton.setOnClickListener(this);
mTextButton.setOnClickListener(this);
+ setOnClickListener(this);
}
public MenuItemImpl getItemData() {
@@ -136,4 +137,12 @@ public class ActionMenuItemView extends LinearLayout
public boolean showsIcon() {
return true;
}
+
+ public boolean needsDividerBefore() {
+ return hasText() && mItemData.getIcon() == null;
+ }
+
+ public boolean needsDividerAfter() {
+ return hasText();
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 0000000..a05fa53
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter {
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+
+ public ActionMenuPresenter() {
+ super(com.android.internal.R.layout.action_menu_layout,
+ com.android.internal.R.layout.action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+ final int screen = res.getConfiguration().screenLayout;
+ // TODO Use the no-buttons specifier instead here
+ mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+ Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+
+ // Measure for initial configuration
+ mMaxItems = res.getInteger(com.android.internal.R.integer.max_action_buttons);
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ OverflowMenuButton button = new OverflowMenuButton(mContext);
+ mOverflowButton = button;
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ final View actionView = item.getActionView();
+ return actionView != null ? actionView : super.getItemView(item, convertView, parent);
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+ ((ActionMenuItemView) itemView).setItemInvoker((ActionMenuView) mMenuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ super.updateMenuView(cleared);
+
+ if (mReserveOverflow && mMenu.getNonActionItems().size() > 0) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mContext);
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ((ViewGroup) mMenuView).addView(mOverflowButton);
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) return false;
+
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenuView != null &&
+ mPostedOpenRunnable == null) {
+ Log.d("ActionMenuPresenter", "showOverflowMenu");
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = item.getActionView();
+ if (v == null) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
+ maxActions--;
+
+ if (isAction) {
+ View v = item.getActionView();
+ if (v == null) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ // Did this push the entire first item past halfway?
+ if (widthLimit + firstActionWidth <= 0) {
+ isAction = false;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+ public OverflowMenuButton(Context context) {
+ super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return true;
+ }
+
+ public boolean needsDividerAfter() {
+ return false;
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mSubMenu.close();
+ mActionButtonPopup = null;
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ if (mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ mPostedOpenRunnable = null;
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index 7775f00..0ea9c89 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -17,63 +17,22 @@ package com.android.internal.view.menu;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import java.util.ArrayList;
-
/**
* @hide
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
-
- // TODO Theme/style this.
- private static final int DIVIDER_PADDING = 12; // dips
private MenuBuilder mMenu;
- private int mMaxItems;
- private int mWidthLimit;
private boolean mReserveOverflow;
- private OverflowMenuButton mOverflowButton;
- private MenuPopupHelper mOverflowPopup;
-
- private float mDividerPadding;
-
- private Drawable mDivider;
-
- private final Runnable mShowOverflow = new Runnable() {
- public void run() {
- showOverflowMenu();
- }
- };
-
- private class OpenOverflowRunnable implements Runnable {
- private MenuPopupHelper mPopup;
-
- public OpenOverflowRunnable(MenuPopupHelper popup) {
- mPopup = popup;
- }
-
- public void run() {
- if (mPopup.tryShow()) {
- mOverflowPopup = mPopup;
- mPostedOpenRunnable = null;
- }
- }
- }
-
- private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPresenter mPresenter;
public ActionMenuView(Context context) {
this(context, null);
@@ -81,60 +40,28 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- final Resources res = getResources();
-
- // Measure for initial configuration
- mMaxItems = getMaxActionButtons();
-
- // TODO There has to be a better way to indicate that we don't have a hard menu key.
- final int screen = res.getConfiguration().screenLayout;
- mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
- Configuration.SCREENLAYOUT_SIZE_XLARGE;
- mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
-
- TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
- mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
- a.recycle();
-
- mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
-
setBaselineAligned(false);
}
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- final int screen = newConfig.screenLayout;
- mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
- Configuration.SCREENLAYOUT_SIZE_XLARGE;
- mMaxItems = getMaxActionButtons();
- mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
- if (mMenu != null) {
- mMenu.setMaxActionItems(mMaxItems);
- updateChildren(false);
- }
+ mPresenter.updateMenuView(false);
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
- post(mShowOverflow);
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
- }
- removeCallbacks(mShowOverflow);
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
- }
- }
-
- private int getMaxActionButtons() {
- return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
+ mPresenter.dismissPopupMenus();
}
public boolean isOverflowReserved() {
@@ -144,10 +71,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
-
- public View getOverflowButton() {
- return mOverflowButton;
- }
@Override
protected LayoutParams generateDefaultLayoutParams() {
@@ -169,6 +92,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return generateDefaultLayoutParams();
}
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
@@ -177,243 +105,26 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return 0;
}
- public void initialize(MenuBuilder menu, int menuType) {
- int width = mWidthLimit;
- if (mReserveOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- mOverflowButton.measure(spec, spec);
- width -= mOverflowButton.getMeasuredWidth();
- }
-
- menu.setActionWidthLimit(width);
-
- menu.setMaxActionItems(mMaxItems);
- final boolean cleared = mMenu != menu;
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(cleared);
- }
-
- public void updateChildren(boolean cleared) {
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
- final int itemCount = itemsToShow.size();
-
- boolean needsDivider = false;
- int childIndex = 0;
- for (int i = 0; i < itemCount; i++) {
- final MenuItemImpl itemData = itemsToShow.get(i);
- boolean hasDivider = false;
-
- if (needsDivider) {
- if (!isDivider(getChildAt(childIndex))) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- hasDivider = true;
- childIndex++;
- }
-
- View childToAdd = itemData.getActionView();
- boolean needsPreDivider = false;
- if (childToAdd != null) {
- childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
- } else {
- ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
- MenuBuilder.TYPE_ACTION_BUTTON, this);
- view.setItemInvoker(this);
- needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
- itemData.getIcon() == null;
- needsDivider = view.hasText();
- childToAdd = view;
- }
-
- boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
-
- if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- if (needsPreDivider) childIndex++;
-
- if (getChildAt(childIndex) != childToAdd) {
- addView(childToAdd, childIndex);
- }
- childIndex++;
- }
-
- final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
- final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
-
- if (hasOverflow != needsOverflow) {
- if (needsOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- childIndex++;
- }
- addView(mOverflowButton, childIndex);
- childIndex++;
- } else {
- removeView(mOverflowButton);
- }
- } else {
- if (needsOverflow) {
- boolean overflowDivider = itemCount > 0;
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
- overflowDivider);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- if (overflowDivider) {
- childIndex += 2;
- } else {
- childIndex++;
- }
- }
- }
-
- while (getChildCount() > childIndex) {
- removeViewAt(childIndex);
- }
- }
-
- private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
- final int childCount = getChildCount();
- boolean found = false;
- for (int i = start; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child == targetChild) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return needsPreDivider;
- }
-
- for (int i = start; i < getChildCount(); ) {
- final View child = getChildAt(i);
- if (needsPreDivider && isDivider(child)) {
- needsPreDivider = false;
- i++;
- continue;
- }
- if (child == targetChild) break;
- removeViewAt(i);
- }
-
- return needsPreDivider;
- }
-
- private static boolean isDivider(View v) {
- return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
- }
-
- public boolean showOverflowMenu() {
- if (mOverflowButton != null && !isOverflowMenuShowing()) {
- mMenu.getCallback().onMenuModeChange(mMenu);
- return true;
- }
- return false;
- }
-
- public void openOverflowMenu() {
- OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
- mPostedOpenRunnable = new OpenOverflowRunnable(popup);
- // Post this for later; we might still need a layout for the anchor to be right.
- post(mPostedOpenRunnable);
- }
-
- public boolean isOverflowMenuShowing() {
- return mOverflowPopup != null && mOverflowPopup.isShowing();
- }
-
- public boolean isOverflowMenuOpen() {
- return mOverflowPopup != null;
}
- public boolean hideOverflowMenu() {
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
- return true;
- }
-
- MenuPopupHelper popup = mOverflowPopup;
- if (popup != null) {
- popup.dismiss();
- return true;
+ @Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
}
- return false;
- }
-
- private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
- view.setItemInvoker(this);
- boolean hasText = view.hasText();
-
- if (hasText && needsDivider) {
- addView(makeDividerView(), makeDividerLayoutParams());
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
}
- addView(view);
- return hasText;
- }
-
- private ImageView makeDividerView() {
- ImageView result = new ImageView(mContext);
- result.setImageDrawable(mDivider);
- result.setScaleType(ImageView.ScaleType.FIT_XY);
- result.setId(com.android.internal.R.id.action_menu_divider);
return result;
}
- private LayoutParams makeDividerLayoutParams() {
- LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT);
- params.topMargin = (int) mDividerPadding;
- params.bottomMargin = (int) mDividerPadding;
- return params;
- }
-
- private LayoutParams makeActionViewLayoutParams(View view) {
- return generateLayoutParams(view.getLayoutParams());
- }
-
- private class OverflowMenuButton extends ImageButton {
- public OverflowMenuButton(Context context) {
- super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
-
- setClickable(true);
- setFocusable(true);
- setVisibility(VISIBLE);
- setEnabled(true);
- }
-
- @Override
- public boolean performClick() {
- if (super.performClick()) {
- return true;
- }
-
- playSoundEffect(SoundEffectConstants.CLICK);
- showOverflowMenu();
- return true;
- }
- }
-
- private class OverflowPopup extends MenuPopupHelper {
- public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly) {
- super(context, menu, anchorView, overflowOnly);
- }
-
- @Override
- public void onDismiss() {
- super.onDismiss();
- mMenu.getCallback().onCloseMenu(mMenu, true);
- mOverflowPopup = null;
- }
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
}
}
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 0000000..16f51fd
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) {
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ mMenu.flagActionItems();
+ ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ int childIndex = 0;
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final View itemView = getItemView(item, convertView, parent);
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ final ViewGroup currentParent = (ViewGroup) itemView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(itemView);
+ }
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Indended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 9e4b4ce..723ece4 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -17,17 +17,15 @@
package com.android.internal.view.menu;
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+import android.widget.ListView;
/**
* The expanded menu view is a list-like menu with all of the available menu items. It is opened
@@ -53,23 +51,8 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setOnItemClickListener(this);
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
-
- setAdapter(menu.new MenuAdapter(menuType));
- }
-
- public void updateChildren(boolean cleared) {
- ListAdapter adapter = getAdapter();
- // Tell adapter of the change, it will notify the mListView
- if (adapter != null) {
- if (cleared) {
- ((BaseAdapter)adapter).notifyDataSetInvalidated();
- }
- else {
- ((BaseAdapter)adapter).notifyDataSetChanged();
- }
- }
}
@Override
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3c5b422..afa8a01 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -112,6 +112,10 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
setEnabled(itemData.isEnabled());
}
+ public void setItemData(MenuItemImpl data) {
+ mItemData = data;
+ }
+
@Override
public boolean performClick() {
// Let the view's click listener have top priority (the More button relies on this)
diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
new file mode 100644
index 0000000..f717904
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for the classic "six-pack" icon menu.
+ */
+public class IconMenuPresenter extends BaseMenuPresenter {
+ private IconMenuItemView mMoreView;
+ private int mMaxItems = -1;
+
+ private static final String VIEWS_TAG = "android:menu:icon";
+
+ public IconMenuPresenter() {
+ super(com.android.internal.R.layout.icon_menu_layout,
+ com.android.internal.R.layout.icon_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu);
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ mMaxItems = -1;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, ItemView itemView) {
+ final IconMenuItemView view = (IconMenuItemView) itemView;
+ view.setItemData(item);
+
+ view.initialize(item.getTitleForItemView(view), item.getIcon());
+
+ view.setVisibility(item.isVisible() ? View.VISIBLE : View.GONE);
+ view.setEnabled(view.isEnabled());
+ view.setLayoutParams(view.getTextAppropriateLayoutParams());
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ boolean fits = (itemsToShow.size() == mMaxItems && childIndex < mMaxItems) ||
+ childIndex < mMaxItems - 1;
+ return fits && !item.isActionButton();
+ }
+
+ @Override
+ protected void addItemView(View itemView, int childIndex) {
+ final IconMenuItemView v = (IconMenuItemView) itemView;
+ final IconMenuView parent = (IconMenuView) mMenuView;
+
+ v.setIconMenuView(parent);
+ v.setItemInvoker(parent);
+ v.setBackgroundDrawable(parent.getItemBackgroundDrawable());
+ super.addItemView(itemView, childIndex);
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ final IconMenuView menuView = (IconMenuView) mMenuView;
+ if (mMaxItems < 0) mMaxItems = menuView.getMaxItems();
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ final boolean needsMore = itemsToShow.size() > mMaxItems;
+ super.updateMenuView(cleared);
+
+ if (needsMore && (mMoreView == null || mMoreView.getParent() != menuView)) {
+ if (mMoreView == null) {
+ mMoreView = menuView.createMoreItemView();
+ mMoreView.setBackgroundDrawable(menuView.getItemBackgroundDrawable());
+ }
+ menuView.addView(mMoreView);
+ } else if (!needsMore && mMoreView != null) {
+ menuView.removeView(mMoreView);
+ }
+
+ menuView.setNumActualItemsShown(needsMore ? mMaxItems - 1 : itemsToShow.size());
+ }
+
+ @Override
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) != mMoreView) {
+ return super.filterLeftoverView(parent, childIndex);
+ }
+ return false;
+ }
+
+ public int getNumActualItemsShown() {
+ return ((IconMenuView) mMenuView).getNumActualItemsShown();
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ if (viewStates != null) {
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index d18c9727..dab43eb 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -80,10 +80,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
/** Icon for the 'More' button */
private Drawable mMoreIcon;
-
- /** Item view for the 'More' button */
- private IconMenuItemView mMoreItemView;
-
+
/** Background of each item (should contain the selected and focused states) */
private Drawable mItemBackground;
@@ -172,6 +169,10 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}
+ int getMaxItems() {
+ return mMaxItems;
+ }
+
/**
* Figures out the layout for the menu items.
*
@@ -277,23 +278,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
return true;
}
- /**
- * Adds an IconMenuItemView to this icon menu view.
- * @param itemView The item's view to add
- */
- private void addItemView(IconMenuItemView itemView) {
- // Set ourselves on the item view
- itemView.setIconMenuView(this);
-
- // Apply the background to the item view
- itemView.setBackgroundDrawable(
- mItemBackground.getConstantState().newDrawable(
- getContext().getResources()));
-
- // This class is the invoker for all its item views
- itemView.setItemInvoker(this);
-
- addView(itemView, itemView.getTextAppropriateLayoutParams());
+ Drawable getItemBackgroundDrawable() {
+ return mItemBackground.getConstantState().newDrawable(getContext().getResources());
}
/**
@@ -302,25 +288,23 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
* have a MenuItemData backing it.
* @return The IconMenuItemView for the 'More' button
*/
- private IconMenuItemView createMoreItemView() {
- LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
+ IconMenuItemView createMoreItemView() {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
com.android.internal.R.layout.icon_menu_item_layout, null);
- Resources r = getContext().getResources();
+ Resources r = context.getResources();
itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
// Set up a click listener on the view since there will be no invocation sequence
// due to the lack of a MenuItemData this view
itemView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- // Switches the menu to expanded mode
- MenuBuilder.Callback cb = mMenu.getCallback();
- if (cb != null) {
- // Call callback
- cb.onMenuModeChange(mMenu);
- }
+ // Switches the menu to expanded mode. Requires support from
+ // the menu's active callback.
+ mMenu.changeMenuMode();
}
});
@@ -328,51 +312,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(true);
- }
-
- public void updateChildren(boolean cleared) {
- // This method does a clear refresh of children
- removeAllViews();
-
- // IconMenuView never wants content sorted for an overflow action button, since
- // it is never used in the presence of an overflow button.
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
- final int numItems = itemsToShow.size();
- final int numItemsThatCanFit = mMaxItems;
- // Minimum of the num that can fit and the num that we have
- final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
-
- MenuItemImpl itemData;
- // Traverse through all but the last item that can fit since that last item can either
- // be a 'More' button or a sixth item
- for (int i = 0; i < minFitMinus1AndNumItems; i++) {
- itemData = itemsToShow.get(i);
- addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
- }
-
- if (numItems > numItemsThatCanFit) {
- // If there are more items than we can fit, show the 'More' button to
- // switch to expanded mode
- if (mMoreItemView == null) {
- mMoreItemView = createMoreItemView();
- }
-
- addItemView(mMoreItemView);
-
- // The last view is the more button, so the actual number of items is one less than
- // the number that can fit
- mNumActualItemsShown = numItemsThatCanFit - 1;
- } else if (numItems == numItemsThatCanFit) {
- // There are exactly the number we can show, so show the last item
- final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
- addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
-
- // The items shown fit exactly
- mNumActualItemsShown = numItemsThatCanFit;
- }
}
/**
@@ -463,13 +404,6 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mHasStaleChildren) {
- mHasStaleChildren = false;
-
- // If we have stale data, resync with the menu
- updateChildren(false);
- }
-
int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
calculateItemFittingMetadata(measuredWidth);
layoutItems(measuredWidth);
@@ -564,6 +498,9 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
return mNumActualItemsShown;
}
+ void setNumActualItemsShown(int count) {
+ mNumActualItemsShown = count;
+ }
public int getWindowAnimations() {
return mAnimations;
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 02584b6..0c3c605 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -48,6 +48,8 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
private int mMenuType;
+ private LayoutInflater mInflater;
+
public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
@@ -187,7 +189,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
public void setIcon(Drawable icon) {
- final boolean showIcon = mItemData.shouldShowIcon(mMenuType);
+ final boolean showIcon = mItemData.shouldShowIcon();
if (!showIcon && !mPreserveIconSpacing) {
return;
}
@@ -212,14 +214,14 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
private void insertIconView() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addView(mIconView, 0);
}
private void insertRadioButton() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mRadioButton =
(RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
this, false);
@@ -227,7 +229,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
private void insertCheckBox() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mCheckBox =
(CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
this, false);
@@ -242,4 +244,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
return false;
}
+ private LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(mContext);
+ }
+ return mInflater;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
new file mode 100644
index 0000000..2cb2a10
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for list-style menus.
+ */
+public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
+ Context mContext;
+ LayoutInflater mInflater;
+ MenuBuilder mMenu;
+
+ ExpandedMenuView mMenuView;
+
+ private int mItemIndexOffset;
+ int mThemeRes;
+ int mItemLayoutRes;
+
+ private Callback mCallback;
+ private MenuAdapter mAdapter;
+
+ public static final String VIEWS_TAG = "android:menu:list";
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param context Context to use for theming. This will supersede the context provided
+ * to initForMenu when this presenter is added.
+ * @param itemLayoutRes Layout resource for individual item views.
+ */
+ public ListMenuPresenter(Context context, int itemLayoutRes) {
+ this(itemLayoutRes, 0);
+ mContext = context;
+ }
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param itemLayoutRes Layout resource for individual item views.
+ * @param themeRes Resource ID of a theme to use for views.
+ */
+ public ListMenuPresenter(int itemLayoutRes, int themeRes) {
+ mItemLayoutRes = itemLayoutRes;
+ mThemeRes = themeRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ if (mThemeRes != 0) {
+ mContext = new ContextThemeWrapper(context, mThemeRes);
+ } else if (mContext == null) {
+ mContext = context;
+ }
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (ExpandedMenuView) mInflater.inflate(
+ com.android.internal.R.layout.expanded_menu_layout, root, false);
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ mMenuView.setAdapter(mAdapter);
+ mMenuView.setOnItemClickListener(this);
+ }
+ return mMenuView;
+ }
+
+ /**
+ * Call this instead of getMenuView if you want to manage your own ListView.
+ * For proper operation, the ListView hosting this adapter should add
+ * this presenter as an OnItemClickListener.
+ *
+ * @return A ListAdapter containing the items in the menu.
+ */
+ public ListAdapter getAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ return mAdapter;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ if (mCallback != null) {
+ mCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ int getItemIndexOffset() {
+ return mItemIndexOffset;
+ }
+
+ public void setItemIndexOffset(int offset) {
+ mItemIndexOffset = offset;
+ if (mMenuView != null) {
+ updateMenuView(false);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mMenu.performItemAction(mAdapter.getItem(position), 0);
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.size() - mItemIndexOffset;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.get(position + mItemIndexOffset);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 14d0ac5..7fba5ca 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -25,29 +25,22 @@ import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.os.Parcelable;
+import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ContextThemeWrapper;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
-import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of the {@link android.view.Menu} interface for creating a
@@ -55,60 +48,6 @@ import java.util.Vector;
*/
public class MenuBuilder implements Menu {
private static final String LOGTAG = "MenuBuilder";
-
- /** The number of different menu types */
- public static final int NUM_TYPES = 5;
- /** The menu type that represents the icon menu view */
- public static final int TYPE_ICON = 0;
- /** The menu type that represents the expanded menu view */
- public static final int TYPE_EXPANDED = 1;
- /**
- * The menu type that represents a menu dialog. Examples are context and sub
- * menus. This menu type will not have a corresponding MenuView, but it will
- * have an ItemView.
- */
- public static final int TYPE_DIALOG = 2;
- /**
- * The menu type that represents a button in the application's action bar.
- */
- public static final int TYPE_ACTION_BUTTON = 3;
- /**
- * The menu type that represents a menu popup.
- */
- public static final int TYPE_POPUP = 4;
-
- private static final String VIEWS_TAG = "android:views";
-
- private static final int THEME_SYSTEM_DEFAULT = 0;
- private static final int THEME_APPLICATION = -1;
- private static final int THEME_ALERT_DIALOG = -2;
-
- // Order must be the same order as the TYPE_*
- static final int THEME_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.style.Theme_IconMenu,
- com.android.internal.R.style.Theme_ExpandedMenu,
- THEME_ALERT_DIALOG,
- THEME_APPLICATION,
- THEME_APPLICATION,
- };
-
- // Order must be the same order as the TYPE_*
- static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_layout,
- com.android.internal.R.layout.expanded_menu_layout,
- 0,
- com.android.internal.R.layout.action_menu_layout,
- 0,
- };
-
- // Order must be the same order as the TYPE_*
- static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.action_menu_item_layout,
- com.android.internal.R.layout.popup_menu_item_layout,
- };
private static final int[] sCategoryToOrder = new int[] {
1, /* No category */
@@ -160,14 +99,7 @@ public class MenuBuilder implements Menu {
* Contains items that should NOT appear in the Action Bar, if present.
*/
private ArrayList<MenuItemImpl> mNonActionItems;
- /**
- * The number of visible action buttons permitted in this menu
- */
- private int mMaxActionItems;
- /**
- * The total width limit in pixels for all action items within a menu
- */
- private int mActionWidthLimit;
+
/**
* Whether or not the items (or any one item's action state) has changed since it was
* last fetched.
@@ -175,12 +107,6 @@ public class MenuBuilder implements Menu {
private boolean mIsActionItemsStale;
/**
- * Whether the process of granting space as action items should reserve a space for
- * an overflow option in the action list.
- */
- private boolean mReserveActionOverflow;
-
- /**
* Default value for how added items should show in the action list.
*/
private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
@@ -210,100 +136,19 @@ public class MenuBuilder implements Menu {
* that may individually call onItemsChanged.
*/
private boolean mPreventDispatchingItemsChanged = false;
+ private boolean mItemsChangedWhileDispatchPrevented = false;
private boolean mOptionalIconsVisible = false;
- private ViewGroup mMeasureActionButtonParent;
-
- private final WeakReference<MenuAdapter>[] mAdapterCache =
- new WeakReference[NUM_TYPES];
- private final WeakReference<OverflowMenuAdapter>[] mOverflowAdapterCache =
- new WeakReference[NUM_TYPES];
-
- // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
- private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
-
- private static int getAlertDialogTheme(Context context) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
- outValue, true);
- return outValue.resourceId;
- }
-
- private MenuType[] mMenuTypes;
- class MenuType {
- private int mMenuType;
-
- /** The layout inflater that uses the menu type's theme */
- private LayoutInflater mInflater;
+ private boolean mIsClosing = false;
- /** The lazily loaded {@link MenuView} */
- private WeakReference<MenuView> mMenuView;
+ private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
- MenuType(int menuType) {
- mMenuType = menuType;
- }
-
- LayoutInflater getInflater() {
- // Create an inflater that uses the given theme for the Views it inflates
- if (mInflater == null) {
- Context wrappedContext;
- int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
- switch (themeResForType) {
- case THEME_APPLICATION:
- wrappedContext = mContext;
- break;
- case THEME_ALERT_DIALOG:
- wrappedContext = new ContextThemeWrapper(mContext,
- getAlertDialogTheme(mContext));
- break;
- default:
- wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
- break;
- }
- mInflater = (LayoutInflater) wrappedContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- return mInflater;
- }
-
- MenuView getMenuView(ViewGroup parent) {
- if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
- return null;
- }
-
- synchronized (this) {
- MenuView menuView = mMenuView != null ? mMenuView.get() : null;
-
- if (menuView == null) {
- menuView = (MenuView) getInflater().inflate(
- LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
- menuView.initialize(MenuBuilder.this, mMenuType);
-
- // Cache the view
- mMenuView = new WeakReference<MenuView>(menuView);
-
- if (mFrozenViewStates != null) {
- View view = (View) menuView;
- view.restoreHierarchyState(mFrozenViewStates);
-
- // Clear this menu type's frozen state, since we just restored it
- mFrozenViewStates.remove(view.getId());
- }
- }
-
- return menuView;
- }
- }
-
- boolean hasMenuView() {
- return mMenuView != null && mMenuView.get() != null;
- }
- }
+ private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
+ new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
/**
- * Called by menu to notify of close and selection changes
+ * Called by menu to notify of close and selection changes.
*/
public interface Callback {
/**
@@ -315,30 +160,6 @@ public class MenuBuilder implements Menu {
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
/**
- * Called when a menu is closed.
- * @param menu The menu that was closed.
- * @param allMenusAreClosing Whether the menus are completely closing (true),
- * or whether there is another menu opening shortly
- * (false). For example, if the menu is closing because a
- * sub menu is about to be shown, <var>allMenusAreClosing</var>
- * is false.
- */
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
-
- /**
- * Called when a sub menu is selected. This is a cue to open the given sub menu's decor.
- * @param subMenu the sub menu that is being opened
- * @return whether the sub menu selection was handled by the callback
- */
- public boolean onSubMenuSelected(SubMenuBuilder subMenu);
-
- /**
- * Called when a sub menu is closed
- * @param menu the sub menu that was closed
- */
- public void onCloseSubMenu(SubMenuBuilder menu);
-
- /**
* Called when the mode of the menu changes (for example, from icon to expanded).
*
* @param menu the menu that has changed modes
@@ -354,8 +175,6 @@ public class MenuBuilder implements Menu {
}
public MenuBuilder(Context context) {
- mMenuTypes = new MenuType[NUM_TYPES];
-
mContext = context;
mResources = context.getResources();
@@ -375,82 +194,66 @@ public class MenuBuilder implements Menu {
mDefaultShowAsAction = defaultShowAsAction;
return this;
}
-
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
- MenuType getMenuType(int menuType) {
- if (mMenuTypes[menuType] == null) {
- mMenuTypes[menuType] = new MenuType(menuType);
- }
-
- return mMenuTypes[menuType];
+ /**
+ * Add a presenter to this menu. This will only hold a WeakReference;
+ * you do not need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
+ *
+ * @param presenter The presenter to add
+ */
+ public void addMenuPresenter(MenuPresenter presenter) {
+ mPresenters.add(new WeakReference<MenuPresenter>(presenter));
+ presenter.initForMenu(mContext, this);
+ mIsActionItemsStale = true;
}
-
+
/**
- * Gets a menu View that contains this menu's items.
- *
- * @param menuType The type of menu to get a View for (must be one of
- * {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
- * {@link #TYPE_DIALOG}).
- * @param parent The ViewGroup that provides a set of LayoutParams values
- * for this menu view
- * @return A View for the menu of type <var>menuType</var>
+ * Remove a presenter from this menu. That presenter will no longer
+ * receive notifications of updates to this menu's data.
+ *
+ * @param presenter The presenter to remove
*/
- public View getMenuView(int menuType, ViewGroup parent) {
- // The expanded menu depends on the number if items shown in the icon menu (which
- // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
- // wanting to show more icons]). If, for example, the activity goes through
- // an orientation change while the expanded menu is open, the icon menu's view
- // won't have an instance anymore; so here we make sure we have an icon menu view (matching
- // the same parent so the layout parameters from the XML are used). This
- // will create the icon menu view and cache it (if it doesn't already exist).
- if (menuType == TYPE_EXPANDED
- && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
- getMenuType(TYPE_ICON).getMenuView(parent);
+ public void removeMenuPresenter(MenuPresenter presenter) {
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter item = ref.get();
+ if (item == null || item == presenter) {
+ mPresenters.remove(ref);
+ }
}
-
- return (View) getMenuType(menuType).getMenuView(parent);
}
- private int getNumIconMenuItemsShown() {
- ViewGroup parent = null;
-
- if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
- /*
- * There isn't an icon menu view instantiated, so when we get it
- * below, it will lazily instantiate it. We should pass a proper
- * parent so it uses the layout_ attributes present in the XML
- * layout file.
- */
- if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
- View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
- parent = (ViewGroup) expandedMenuView.getParent();
+ private void dispatchPresenterUpdate(boolean cleared) {
+ if (mPresenters.isEmpty()) return;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.updateMenuView(cleared);
}
}
-
- return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown();
}
- /**
- * Clears the cached menu views. Call this if the menu views need to another
- * layout (for example, if the screen size has changed).
- */
- public void clearMenuViews() {
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (mMenuTypes[i] != null) {
- mMenuTypes[i].mMenuView = null;
- }
- }
-
- for (int i = mItems.size() - 1; i >= 0; i--) {
- MenuItemImpl item = mItems.get(i);
- if (item.hasSubMenu()) {
- ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
+ private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean result = false;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if (!result) {
+ result = presenter.onSubMenuSelected(subMenu);
}
- item.clearItemViews();
}
+ return result;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
}
/**
@@ -468,7 +271,7 @@ public class MenuBuilder implements Menu {
}
mItems.add(findInsertIndex(mItems, ordering), item);
- onItemsChanged(false);
+ onItemsChanged(true);
return item;
}
@@ -554,7 +357,7 @@ public class MenuBuilder implements Menu {
}
// Notify menu views
- onItemsChanged(false);
+ onItemsChanged(true);
}
}
@@ -573,7 +376,7 @@ public class MenuBuilder implements Menu {
mItems.remove(index);
- if (updateChildrenOnMenuViews) onItemsChanged(false);
+ if (updateChildrenOnMenuViews) onItemsChanged(true);
}
public void removeItemAt(int index) {
@@ -585,6 +388,7 @@ public class MenuBuilder implements Menu {
clear();
clearHeader();
mPreventDispatchingItemsChanged = false;
+ mItemsChangedWhileDispatchPrevented = false;
onItemsChanged(true);
}
@@ -725,19 +529,14 @@ public class MenuBuilder implements Menu {
return mItems.get(index);
}
- public MenuItem getOverflowItem(int index) {
- flagActionItems(true);
- return mNonActionItems.get(index);
- }
-
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return findItemWithShortcutForKey(keyCode, event) != null;
}
public void setQwertyMode(boolean isQwerty) {
mQwertyMode = isQwerty;
-
- refreshShortcuts(isShortcutsVisible(), isQwerty);
+
+ onItemsChanged(false);
}
/**
@@ -751,8 +550,7 @@ public class MenuBuilder implements Menu {
* @return An ordering integer that can be used to order this item across
* all the items (even from other categories).
*/
- private static int getOrdering(int categoryOrder)
- {
+ private static int getOrdering(int categoryOrder) {
final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
if (index < 0 || index >= sCategoryToOrder.length) {
@@ -770,23 +568,6 @@ public class MenuBuilder implements Menu {
}
/**
- * Refreshes the shortcut labels on each of the displayed items. Passes the arguments
- * so submenus don't need to call their parent menu for the same values.
- */
- private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
- MenuItemImpl item;
- for (int i = mItems.size() - 1; i >= 0; i--) {
- item = mItems.get(i);
-
- if (item.hasSubMenu()) {
- ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
- }
-
- item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
- }
- }
-
- /**
* Sets whether the shortcuts should be visible on menus. Devices without hardware
* key input will never make shortcuts visible even if this method is passed 'true'.
*
@@ -798,7 +579,7 @@ public class MenuBuilder implements Menu {
if (mShortcutsVisible == shortcutsVisible) return;
setShortcutsVisibleInner(shortcutsVisible);
- refreshShortcuts(mShortcutsVisible, isQwertyMode());
+ onItemsChanged(false);
}
private void setShortcutsVisibleInner(boolean shortcutsVisible) {
@@ -818,15 +599,24 @@ public class MenuBuilder implements Menu {
Resources getResources() {
return mResources;
}
-
- public Callback getCallback() {
- return mCallback;
- }
public Context getContext() {
return mContext;
}
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+ }
+
+ /**
+ * Dispatch a mode change event to this menu's callback.
+ */
+ public void changeMenuMode() {
+ if (mCallback != null) {
+ mCallback.onMenuModeChange(this);
+ }
+ }
+
private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
for (int i = items.size() - 1; i >= 0; i--) {
MenuItemImpl item = items.get(i);
@@ -860,7 +650,7 @@ public class MenuBuilder implements Menu {
* (the ALT-enabled char corresponds to the shortcut) associated
* with the keyCode.
*/
- List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
+ void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
final boolean qwerty = isQwertyMode();
final int metaState = event.getMetaState();
final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
@@ -868,18 +658,15 @@ public class MenuBuilder implements Menu {
final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
// The delete key is not mapped to '\b' so we treat it specially
if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
- return null;
+ return;
}
- Vector<MenuItemImpl> items = new Vector();
// Look for an item whose shortcut is this key.
final int N = mItems.size();
for (int i = 0; i < N; i++) {
MenuItemImpl item = mItems.get(i);
if (item.hasSubMenu()) {
- List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
- .findItemsWithShortcutForKey(keyCode, event);
- items.addAll(subMenuItems);
+ ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
}
final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
@@ -892,7 +679,6 @@ public class MenuBuilder implements Menu {
items.add(item);
}
}
- return items;
}
/*
@@ -908,9 +694,11 @@ public class MenuBuilder implements Menu {
*/
MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
// Get all items that can be associated directly or indirectly with the keyCode
- List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
+ ArrayList<MenuItemImpl> items = mTempShortcutItemList;
+ items.clear();
+ findItemsWithShortcutForKey(items, keyCode, event);
- if (items == null) {
+ if (items.isEmpty()) {
return null;
}
@@ -920,15 +708,18 @@ public class MenuBuilder implements Menu {
event.getKeyData(possibleChars);
// If we have only one element, we can safely returns it
- if (items.size() == 1) {
+ final int size = items.size();
+ if (size == 1) {
return items.get(0);
}
final boolean qwerty = isQwertyMode();
// If we found more than one item associated with the key,
// we have to return the exact match
- for (MenuItemImpl item : items) {
- final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+ for (int i = 0; i < size; i++) {
+ final MenuItemImpl item = items.get(i);
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
if ((shortcutChar == possibleChars.meta[0] &&
(metaState & KeyEvent.META_ALT_ON) == 0)
|| (shortcutChar == possibleChars.meta[2] &&
@@ -958,11 +749,8 @@ public class MenuBuilder implements Menu {
if (item.hasSubMenu()) {
close(false);
- if (mCallback != null) {
- // Return true if the sub menu was invoked or the item was invoked previously
- invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
- || invoked;
- }
+ invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
+ if (!invoked) close(true);
} else {
if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
close(true);
@@ -982,10 +770,18 @@ public class MenuBuilder implements Menu {
* is false.
*/
final void close(boolean allMenusAreClosing) {
- Callback callback = getCallback();
- if (callback != null) {
- callback.onCloseMenu(this, allMenusAreClosing);
+ if (mIsClosing) return;
+
+ mIsClosing = true;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.onCloseMenu(this, allMenusAreClosing);
+ }
}
+ mIsClosing = false;
}
/** {@inheritDoc} */
@@ -996,26 +792,40 @@ public class MenuBuilder implements Menu {
/**
* Called when an item is added or removed.
*
- * @param cleared Whether the items were cleared or just changed.
+ * @param structureChanged true if the menu structure changed,
+ * false if only item properties changed.
*/
- private void onItemsChanged(boolean cleared) {
+ void onItemsChanged(boolean structureChanged) {
if (!mPreventDispatchingItemsChanged) {
- if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
- if (mIsActionItemsStale == false) mIsActionItemsStale = true;
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = 0; i < NUM_TYPES; i++) {
- if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
- MenuView menuView = menuTypes[i].mMenuView.get();
- menuView.updateChildren(cleared);
- }
+ if (structureChanged) {
+ mIsVisibleItemsStale = true;
+ mIsActionItemsStale = true;
+ }
- MenuAdapter adapter = mAdapterCache[i] == null ? null : mAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
+ dispatchPresenterUpdate(structureChanged);
+ } else {
+ mItemsChangedWhileDispatchPrevented = true;
+ }
+ }
- adapter = mOverflowAdapterCache[i] == null ? null : mOverflowAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
- }
+ /**
+ * Stop dispatching item changed events to presenters until
+ * {@link #startDispatchingItemsChanged()} is called. Useful when
+ * many menu operations are going to be performed as a batch.
+ */
+ public void stopDispatchingItemsChanged() {
+ if (!mPreventDispatchingItemsChanged) {
+ mPreventDispatchingItemsChanged = true;
+ mItemsChangedWhileDispatchPrevented = false;
+ }
+ }
+
+ public void startDispatchingItemsChanged() {
+ mPreventDispatchingItemsChanged = false;
+
+ if (mItemsChangedWhileDispatchPrevented) {
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
}
}
@@ -1025,6 +835,7 @@ public class MenuBuilder implements Menu {
*/
void onItemVisibleChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsVisibleItemsStale = true;
onItemsChanged(false);
}
@@ -1034,6 +845,7 @@ public class MenuBuilder implements Menu {
*/
void onItemActionRequestChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsActionItemsStale = true;
onItemsChanged(false);
}
@@ -1055,17 +867,6 @@ public class MenuBuilder implements Menu {
return mVisibleItems;
}
-
- /**
- * @return A fake action button parent view for obtaining child views.
- */
- private ViewGroup getMeasureActionButtonParent() {
- if (mMeasureActionButtonParent == null) {
- mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
- .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
- }
- return mMeasureActionButtonParent;
- }
/**
* This method determines which menu items get to be 'action items' that will appear
@@ -1090,147 +891,56 @@ public class MenuBuilder implements Menu {
* <p>The space freed by demoting a full group cannot be consumed by future menu items.
* Once items begin to overflow, all future items become overflow items as well. This is
* to avoid inadvertent reordering that may break the app's intended design.
- *
- * @param reserveActionOverflow true if an overflow button should consume one space
- * in the available item count
*/
- private void flagActionItems(boolean reserveActionOverflow) {
- if (reserveActionOverflow != mReserveActionOverflow) {
- mReserveActionOverflow = reserveActionOverflow;
- mIsActionItemsStale = true;
- }
-
+ public void flagActionItems() {
if (!mIsActionItemsStale) {
return;
}
- final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
- final int itemsSize = visibleItems.size();
- int maxActions = mMaxActionItems;
- int widthLimit = mActionWidthLimit;
- final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final ViewGroup parent = getMeasureActionButtonParent();
-
- int requiredItems = 0;
- int requestedItems = 0;
- int firstActionWidth = 0;
- boolean hasOverflow = false;
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.requiresActionButton()) {
- requiredItems++;
- } else if (item.requestsActionButton()) {
- requestedItems++;
+ // Presenters flag action items as needed.
+ boolean flagged = false;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
} else {
- hasOverflow = true;
+ flagged |= presenter.flagActionItems();
}
}
- // Reserve a spot for the overflow item if needed.
- if (reserveActionOverflow &&
- (hasOverflow || requiredItems + requestedItems > maxActions)) {
- maxActions--;
- }
- maxActions -= requiredItems;
-
- final SparseBooleanArray seenGroups = mActionButtonGroups;
- seenGroups.clear();
-
- // Flag as many more requested items as will fit.
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
-
- if (item.requiresActionButton()) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
- }
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
- }
- final int groupId = item.getGroupId();
- if (groupId != 0) {
- seenGroups.put(groupId, true);
- }
- } else if (item.requestsActionButton()) {
- // Items in a group with other items that already have an action slot
- // can break the max actions rule, but not the width limit.
- final int groupId = item.getGroupId();
- final boolean inGroup = seenGroups.get(groupId);
- boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
- maxActions--;
-
- if (isAction) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
- }
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
- }
-
- // Did this push the entire first item past halfway?
- if (widthLimit + firstActionWidth <= 0) {
- isAction = false;
- }
- }
-
- if (isAction && groupId != 0) {
- seenGroups.put(groupId, true);
- } else if (inGroup) {
- // We broke the width limit. Demote the whole group, they all overflow now.
- seenGroups.put(groupId, false);
- for (int j = 0; j < i; j++) {
- MenuItemImpl areYouMyGroupie = visibleItems.get(j);
- if (areYouMyGroupie.getGroupId() == groupId) {
- areYouMyGroupie.setIsActionButton(false);
- }
- }
+ if (flagged) {
+ mActionItems.clear();
+ mNonActionItems.clear();
+ ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.isActionButton()) {
+ mActionItems.add(item);
+ } else {
+ mNonActionItems.add(item);
}
-
- item.setIsActionButton(isAction);
}
+ } else if (mActionItems.size() + mNonActionItems.size() != getVisibleItems().size()) {
+ // Nobody flagged anything, but if something doesn't add up then treat everything
+ // as non-action items.
+ // (This happens during a first pass with no action-item presenters.)
+ mActionItems.clear();
+ mNonActionItems.clear();
+ mNonActionItems.addAll(getVisibleItems());
}
-
- mActionItems.clear();
- mNonActionItems.clear();
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.isActionButton()) {
- mActionItems.add(item);
- } else {
- mNonActionItems.add(item);
- }
- }
-
mIsActionItemsStale = false;
}
- ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getActionItems() {
+ flagActionItems();
return mActionItems;
}
- ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getNonActionItems() {
+ flagActionItems();
return mNonActionItems;
}
-
- void setMaxActionItems(int maxActionItems) {
- mMaxActionItems = maxActionItems;
- mIsActionItemsStale = true;
- }
-
- void setActionWidthLimit(int widthLimit) {
- mActionWidthLimit = widthLimit;
- mIsActionItemsStale = true;
- }
public void clearHeader() {
mHeaderIcon = null;
@@ -1362,38 +1072,6 @@ public class MenuBuilder implements Menu {
mCurrentMenuInfo = menuInfo;
}
- /**
- * Gets an adapter for providing items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getMenuAdapter(int menuType) {
- MenuAdapter adapter = mAdapterCache[menuType] == null ?
- null : mAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new MenuAdapter(menuType);
- mAdapterCache[menuType] = new WeakReference<MenuAdapter>(adapter);
- return adapter;
- }
-
- /**
- * Gets an adapter for providing overflow (non-action) items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getOverflowMenuAdapter(int menuType) {
- OverflowMenuAdapter adapter = mOverflowAdapterCache[menuType] == null ?
- null : mOverflowAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new OverflowMenuAdapter(menuType);
- mOverflowAdapterCache[menuType] = new WeakReference<OverflowMenuAdapter>(adapter);
- return adapter;
- }
-
void setOptionalIconsVisible(boolean visible) {
mOptionalIconsVisible = visible;
}
@@ -1401,109 +1079,4 @@ public class MenuBuilder implements Menu {
boolean getOptionalIconsVisible() {
return mOptionalIconsVisible;
}
-
- public void saveHierarchyState(Bundle outState) {
- SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
- }
-
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
- }
- }
-
- outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
- }
-
- public void restoreHierarchyState(Bundle inState) {
- // Save this for menu views opened later
- SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
- .getSparseParcelableArray(VIEWS_TAG);
-
- // Thaw those menu views already open
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
- }
-
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
- }
- }
- }
-
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source. This adapter will use only the visible/shown items from the menu.
- */
- public class MenuAdapter extends BaseAdapter {
- private int mMenuType;
-
- public MenuAdapter(int menuType) {
- mMenuType = menuType;
- }
-
- public int getOffset() {
- if (mMenuType == TYPE_EXPANDED) {
- return getNumIconMenuItemsShown();
- } else {
- return 0;
- }
- }
-
- public int getCount() {
- return getVisibleItems().size() - getOffset();
- }
-
- public MenuItemImpl getItem(int position) {
- return getVisibleItems().get(position + getOffset());
- }
-
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView != null) {
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- itemView.getItemData().setItemView(mMenuType, null);
-
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- itemView.initialize(item, mMenuType);
- item.setItemView(mMenuType, itemView);
- return convertView;
- } else {
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- item.setItemView(mMenuType, null);
- return item.getItemView(mMenuType, parent);
- }
- }
- }
-
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source for overflow menu items that do not fit in the list of action items.
- */
- private class OverflowMenuAdapter extends MenuAdapter {
- public OverflowMenuAdapter(int menuType) {
- super(menuType);
- }
-
- @Override
- public MenuItemImpl getItem(int position) {
- return getNonActionItems(true).get(position);
- }
-
- @Override
- public int getCount() {
- return getNonActionItems(true).size();
- }
- }
}
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index d7438d6..6387c9b 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -24,17 +24,19 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.widget.ListAdapter;
/**
* Helper for menus that appear as Dialogs (context and submenus).
*
* @hide
*/
-public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener {
+public class MenuDialogHelper implements DialogInterface.OnKeyListener,
+ DialogInterface.OnClickListener,
+ DialogInterface.OnDismissListener,
+ MenuPresenter.Callback {
private MenuBuilder mMenu;
- private ListAdapter mAdapter;
private AlertDialog mDialog;
+ ListMenuPresenter mPresenter;
public MenuDialogHelper(MenuBuilder menu) {
mMenu = menu;
@@ -49,12 +51,15 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
// Many references to mMenu, create local reference
final MenuBuilder menu = mMenu;
- // Get an adapter for the menu item views
- mAdapter = menu.getMenuAdapter(MenuBuilder.TYPE_DIALOG);
-
// Get the builder for the dialog
- final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext())
- .setAdapter(mAdapter, this);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
+
+ mPresenter = new ListMenuPresenter(builder.getContext(),
+ com.android.internal.R.layout.list_menu_item_layout);
+
+ mPresenter.setCallback(this);
+ mMenu.addMenuPresenter(mPresenter);
+ builder.setAdapter(mPresenter.getAdapter(), this);
// Set the title
final View headerView = menu.getHeaderView();
@@ -68,13 +73,10 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
// Set the key listener
builder.setOnKeyListener(this);
-
- // Since this is for a menu, disable the recycling of views
- // This is done by the menu framework anyway
- builder.setRecycleOnMeasureEnabled(false);
// Show the menu
mDialog = builder.create();
+ mDialog.setOnDismissListener(this);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -132,9 +134,25 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
mDialog.dismiss();
}
}
-
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mPresenter.onCloseMenu(mMenu, true);
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (allMenusAreClosing || menu == mMenu) {
+ dismiss();
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ return false;
+ }
+
public void onClick(DialogInterface dialog, int which) {
- mMenu.performItemAction((MenuItemImpl) mAdapter.getItem(which), 0);
+ mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0);
}
-
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 305115f..c6d386d 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,21 +16,18 @@
package com.android.internal.view.menu;
-import java.lang.ref.WeakReference;
+import com.android.internal.view.menu.MenuView.ItemView;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-
-import com.android.internal.view.menu.MenuView.ItemView;
/**
* @hide
@@ -60,9 +57,6 @@ public final class MenuItemImpl implements MenuItem {
* needed).
*/
private int mIconResId = NO_ICON;
-
- /** The (cached) menu item views for this item */
- private WeakReference<ItemView> mItemViews[];
/** The menu to which this item belongs */
private MenuBuilder mMenu;
@@ -128,7 +122,6 @@ public final class MenuItemImpl implements MenuItem {
com.android.internal.R.string.menu_space_shortcut_label);
}
- mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
mMenu = menu;
mId = id;
mGroup = group;
@@ -149,9 +142,7 @@ public final class MenuItemImpl implements MenuItem {
return true;
}
- MenuBuilder.Callback callback = mMenu.getCallback();
- if (callback != null &&
- callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+ if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
@@ -172,10 +163,6 @@ public final class MenuItemImpl implements MenuItem {
return false;
}
- private boolean hasItemView(int menuType) {
- return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
- }
-
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@@ -187,13 +174,7 @@ public final class MenuItemImpl implements MenuItem {
mFlags &= ~ENABLED;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (hasItemView(i)) {
- mItemViews[i].get().setEnabled(enabled);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
@@ -242,7 +223,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -256,7 +237,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutNumericChar = numericChar;
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -265,7 +246,7 @@ public final class MenuItemImpl implements MenuItem {
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -322,38 +303,6 @@ public final class MenuItemImpl implements MenuItem {
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
- /**
- * Refreshes the shortcut shown on the ItemViews. This method retrieves current
- * shortcut state (mode and shown) from the menu that contains this item.
- */
- private void refreshShortcutOnItemViews() {
- refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
- }
-
- /**
- * Refreshes the shortcut shown on the ItemViews. This is usually called by
- * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
- * views, so it passes arguments rather than each item calling a method on the menu to get
- * the same values.
- *
- * @param menuShortcutShown The menu's shortcut shown mode. In addition,
- * this method will ensure this item has a shortcut before it
- * displays the shortcut.
- * @param isQwertyMode Whether the shortcut mode is qwerty mode
- */
- void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
- final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
-
- // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
- final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
-
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
- }
- }
- }
-
public SubMenu getSubMenu() {
return mSubMenu;
}
@@ -394,18 +343,7 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setTitle(CharSequence title) {
mTitle = title;
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (!hasItemView(i)) {
- continue;
- }
-
- ItemView itemView = mItemViews[i].get();
- if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
- itemView.setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
@@ -430,18 +368,12 @@ public final class MenuItemImpl implements MenuItem {
title = mTitle;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that prefer a condensed title
- if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
- mItemViews[i].get().setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
public Drawable getIcon() {
-
if (mIconDrawable != null) {
return mIconDrawable;
}
@@ -456,7 +388,7 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
- setIconOnViews(icon);
+ mMenu.onItemsChanged(false);
return this;
}
@@ -466,33 +398,10 @@ public final class MenuItemImpl implements MenuItem {
mIconResId = iconResId;
// If we have a view, we need to push the Drawable to them
- if (haveAnyOpenedIconCapableItemViews()) {
- Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
- : null;
- setIconOnViews(drawable);
- }
+ mMenu.onItemsChanged(false);
return this;
}
-
- private void setIconOnViews(Drawable icon) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that are able to display an icon
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- mItemViews[i].get().setIcon(icon);
- }
- }
- }
-
- private boolean haveAnyOpenedIconCapableItemViews() {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- return true;
- }
- }
-
- return false;
- }
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
@@ -502,19 +411,14 @@ public final class MenuItemImpl implements MenuItem {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setCheckable(checkable);
- }
- }
+ mMenu.onItemsChanged(false);
}
return this;
}
- public void setExclusiveCheckable(boolean exclusive)
- {
- mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ public void setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
public boolean isExclusiveCheckable() {
@@ -541,11 +445,7 @@ public final class MenuItemImpl implements MenuItem {
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setChecked(checked);
- }
- }
+ mMenu.onItemsChanged(false);
}
}
@@ -581,39 +481,6 @@ public final class MenuItemImpl implements MenuItem {
mClickListener = clickListener;
return this;
}
-
- View getItemView(int menuType, ViewGroup parent) {
- if (!hasItemView(menuType)) {
- mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
- }
-
- return (View) mItemViews[menuType].get();
- }
-
- void setItemView(int menuType, ItemView view) {
- mItemViews[menuType] = new WeakReference<ItemView>(view);
- }
-
- /**
- * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
- * @param menuType The type of menu to get a View for (must be one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
- * @return The inflated {@link MenuView.ItemView} that is ready for use
- */
- private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
- // Create the MenuView
- MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
- .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
- itemView.initialize(this, menuType);
- return itemView;
- }
-
- void clearItemViews() {
- for (int i = mItemViews.length - 1; i >= 0; i--) {
- mItemViews[i] = null;
- }
- }
@Override
public String toString() {
@@ -627,24 +494,12 @@ public final class MenuItemImpl implements MenuItem {
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
-
- /**
- * Returns a LayoutInflater that is themed for the given menu type.
- *
- * @param menuType The type of menu.
- * @return A LayoutInflater.
- */
- public LayoutInflater getLayoutInflater(int menuType) {
- return mMenu.getMenuType(menuType).getInflater();
- }
/**
- * @return Whether the given menu type should show icons for menu items.
+ * @return Whether the menu should show icons for menu items.
*/
- public boolean shouldShowIcon(int menuType) {
- return menuType == MenuBuilder.TYPE_ICON ||
- menuType == MenuBuilder.TYPE_ACTION_BUTTON ||
- mMenu.getOptionalIconsVisible();
+ public boolean shouldShowIcon() {
+ return mMenu.getOptionalIconsVisible();
}
public boolean isActionButton() {
@@ -668,7 +523,9 @@ public final class MenuItemImpl implements MenuItem {
}
public boolean showsTextAsAction() {
- return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
+ return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT &&
+ mMenu.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.allow_action_menu_item_text_with_icon);
}
public void setShowAsAction(int actionEnum) {
@@ -696,8 +553,8 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setActionView(int resId) {
LayoutInflater inflater = LayoutInflater.from(mMenu.getContext());
- ViewGroup parent = (ViewGroup) mMenu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- setActionView(inflater.inflate(resId, parent, false));
+ // TODO - Fix for proper parent. Lazily inflate in the presenter.
+ setActionView(inflater.inflate(resId, null));
return this;
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 04a059e..38cec29 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -16,31 +16,35 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
import android.content.Context;
-import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
import android.widget.ListPopupWindow;
import android.widget.PopupWindow;
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
+ * Presents a menu as a small, simple popup anchored to another view.
* @hide
*/
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
- View.OnAttachStateChangeListener {
+ View.OnAttachStateChangeListener, MenuPresenter {
private static final String TAG = "MenuPopupHelper";
+ static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
private Context mContext;
+ private LayoutInflater mInflater;
private ListPopupWindow mPopup;
private MenuBuilder mMenu;
private int mPopupMaxWidth;
@@ -48,7 +52,9 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
private boolean mOverflowOnly;
private ViewTreeObserver mTreeObserver;
- private final Handler mHandler = new Handler();
+ private MenuAdapter mAdapter;
+
+ private Callback mPresenterCallback;
public MenuPopupHelper(Context context, MenuBuilder menu) {
this(context, menu, null, false);
@@ -61,6 +67,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
public MenuPopupHelper(Context context, MenuBuilder menu,
View anchorView, boolean overflowOnly) {
mContext = context;
+ mInflater = LayoutInflater.from(context);
mMenu = menu;
mOverflowOnly = overflowOnly;
@@ -68,6 +75,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mPopupMaxWidth = metrics.widthPixels / 2;
mAnchorView = anchorView;
+
+ menu.addMenuPresenter(this);
}
public void setAnchorView(View anchor) {
@@ -82,23 +91,14 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
public boolean tryShow() {
mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
- mPopup.setOnItemClickListener(this);
mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
- final MenuAdapter adapter = mOverflowOnly ?
- mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
- mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
- mPopup.setAdapter(adapter);
+ mAdapter = new MenuAdapter(mMenu);
+ mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
View anchor = mAnchorView;
- if (anchor == null && mMenu instanceof SubMenuBuilder) {
- SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
- final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
- anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- mAnchorView = anchor;
- }
-
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
@@ -109,7 +109,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return false;
}
- mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
+ mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
mPopup.show();
mPopup.getListView().setOnKeyListener(this);
@@ -136,23 +136,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return mPopup != null && mPopup.isShowing();
}
+ @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (!isShowing()) return;
-
- MenuItem item = null;
- if (mOverflowOnly) {
- item = mMenu.getOverflowItem(position);
- } else {
- item = mMenu.getVisibleItems().get(position);
- }
- dismiss();
-
- final MenuItem performItem = item;
- mHandler.post(new Runnable() {
- public void run() {
- mMenu.performItemAction(performItem, 0);
- }
- });
+ MenuAdapter adapter = mAdapter;
+ adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -163,7 +150,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return false;
}
- private int measureContentWidth(MenuAdapter adapter) {
+ private int measureContentWidth(ListAdapter adapter) {
// Menus don't tend to be long, so this is more sane than it looks.
int width = 0;
View itemView = null;
@@ -211,4 +198,91 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
}
v.removeOnAttachStateChangeListener(this);
}
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+ subPopup.setCallback(mPresenterCallback);
+ if (subPopup.tryShow()) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ private MenuBuilder mAdapterMenu;
+
+ public MenuAdapter(MenuBuilder menu) {
+ mAdapterMenu = menu;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.size();
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
new file mode 100644
index 0000000..5baf419
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+}
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 5090400..407caae 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -22,7 +22,7 @@ import com.android.internal.view.menu.MenuItemImpl;
import android.graphics.drawable.Drawable;
/**
- * Minimal interface for a menu view. {@link #initialize(MenuBuilder, int)} must be called for the
+ * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
* menu to be functional.
*
* @hide
@@ -33,18 +33,8 @@ public interface MenuView {
* view is inflated.
*
* @param menu The menu that this MenuView should display.
- * @param menuType The type of this menu, one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_DIALOG}).
*/
- public void initialize(MenuBuilder menu, int menuType);
-
- /**
- * Forces the menu view to update its view to reflect the new state of the menu.
- *
- * @param cleared Whether the menu was cleared or just modified.
- */
- public void updateChildren(boolean cleared);
+ public void initialize(MenuBuilder menu);
/**
* Returns the default animations to be used for this menu when entering/exiting.
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index af1b996..ad773ee 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -67,11 +67,6 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu {
}
@Override
- public Callback getCallback() {
- return mParentMenu.getCallback();
- }
-
- @Override
public void setCallback(Callback callback) {
mParentMenu.setCallback(callback);
}
@@ -110,5 +105,4 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu {
public SubMenu setHeaderView(View view) {
return (SubMenu) super.setHeaderViewInt(view);
}
-
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index c9b0ec9..3deb036 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.FrameLayout;
/**
@@ -29,6 +30,7 @@ import android.widget.FrameLayout;
*/
public class ActionBarContainer extends FrameLayout {
private boolean mIsTransitioning;
+ private View mTabContainer;
public ActionBarContainer(Context context) {
this(context, null);
@@ -65,6 +67,44 @@ public class ActionBarContainer extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
+
+ // An action bar always eats touch events.
return true;
}
+
+ public void setTabContainer(View tabView) {
+ if (mTabContainer != null) {
+ removeView(mTabContainer);
+ }
+ mTabContainer = tabView;
+ addView(tabView);
+ }
+
+ public View getTabContainer() {
+ return mTabContainer;
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ final int mode = MeasureSpec.getMode(heightMeasureSpec);
+ if (mode == MeasureSpec.AT_MOST) {
+ final int measuredHeight = getMeasuredHeight();
+ final int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(getMeasuredWidth(),
+ Math.min(measuredHeight + mTabContainer.getMeasuredHeight(), maxHeight));
+ }
+ }
+ }
+
+ @Override
+ public void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ final int containerHeight = getMeasuredHeight();
+ mTabContainer.layout(l, containerHeight - mTabContainer.getMeasuredHeight(),
+ r, containerHeight);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 71af115..70fb3b2 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
@@ -53,6 +54,7 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
private int mTitleStyleRes;
private int mSubtitleStyleRes;
private ActionMenuView mMenuView;
+ private ActionMenuPresenter mPresenter;
private Animator mCurrentAnimation;
private boolean mAnimateInOnLayout;
@@ -176,9 +178,9 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
});
final MenuBuilder menu = (MenuBuilder) mode.getMenu();
- mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
- mMenuView.setOverflowReserved(true);
- mMenuView.updateChildren(false);
+ mPresenter = new ActionMenuPresenter();
+ menu.addMenuPresenter(mPresenter);
+ mMenuView = (ActionMenuView) mPresenter.getMenuView(this);
addView(mMenuView);
mAnimateInOnLayout = true;
@@ -217,28 +219,22 @@ public class ActionBarContextView extends ViewGroup implements AnimatorListener
}
public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
+ if (mPresenter != null) {
+ return mPresenter.showOverflowMenu();
}
return false;
}
- public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
- }
- }
-
public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
+ if (mPresenter != null) {
+ return mPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
+ if (mPresenter != null) {
+ return mPresenter.isOverflowMenuShowing();
}
return false;
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 81d02ee..a572e11 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -18,8 +18,10 @@ package com.android.internal.widget;
import com.android.internal.R;
import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPresenter;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -28,11 +30,13 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ActionMode;
import android.view.Gravity;
@@ -83,7 +87,6 @@ public class ActionBarView extends ViewGroup {
private CharSequence mSubtitle;
private Drawable mIcon;
private Drawable mLogo;
- private Drawable mDivider;
private View mHomeLayout;
private View mHomeAsUpView;
@@ -94,7 +97,7 @@ public class ActionBarView extends ViewGroup {
private Spinner mSpinner;
private LinearLayout mListNavLayout;
private HorizontalScrollView mTabScrollView;
- private LinearLayout mTabLayout;
+ private ViewGroup mTabLayout;
private View mCustomNavView;
private ProgressBar mProgressView;
private ProgressBar mIndeterminateProgressView;
@@ -109,9 +112,11 @@ public class ActionBarView extends ViewGroup {
private boolean mShowMenu;
private boolean mUserTitle;
+ private boolean mIncludeTabs;
private MenuBuilder mOptionsMenu;
private ActionMenuView mMenuView;
+ private ActionMenuPresenter mActionMenuPresenter;
private ActionBarContextView mContextView;
@@ -197,6 +202,8 @@ public class ActionBarView extends ViewGroup {
mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
+ mIncludeTabs = a.getBoolean(R.styleable.ActionBar_embeddedTabs, true);
+
setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
@@ -208,8 +215,6 @@ public class ActionBarView extends ViewGroup {
mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
- mDivider = a.getDrawable(R.styleable.ActionBar_divider);
-
a.recycle();
mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
@@ -240,6 +245,14 @@ public class ActionBarView extends ViewGroup {
addView(mIndeterminateProgressView);
}
+ public boolean hasEmbeddedTabs() {
+ return mIncludeTabs;
+ }
+
+ public void setExternalTabLayout(ViewGroup tabLayout) {
+ mTabLayout = tabLayout;
+ }
+
@Override
public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
// No starting an action mode for an action bar child! (Where would it go?)
@@ -250,16 +263,24 @@ public class ActionBarView extends ViewGroup {
mCallback = callback;
}
- public void setMenu(Menu menu) {
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
if (menu == mOptionsMenu) return;
+ if (mOptionsMenu != null) {
+ mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+ }
+
MenuBuilder builder = (MenuBuilder) menu;
mOptionsMenu = builder;
if (mMenuView != null) {
removeView(mMenuView);
}
- final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
- MenuBuilder.TYPE_ACTION_BUTTON, null);
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter();
+ mActionMenuPresenter.setCallback(cb);
+ builder.addMenuPresenter(mActionMenuPresenter);
+ }
+ final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
menuView.setLayoutParams(layoutParams);
@@ -268,15 +289,15 @@ public class ActionBarView extends ViewGroup {
}
public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ showOverflowMenu();
}
}
@@ -289,28 +310,27 @@ public class ActionBarView extends ViewGroup {
}
public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
- public boolean isOverflowMenuOpen() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuOpen();
- }
- return false;
+ public boolean isOverflowReserved() {
+ return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
}
- public boolean isOverflowReserved() {
- return mMenuView != null && mMenuView.isOverflowReserved();
+ public void dismissPopupMenus() {
+ if (mActionMenuPresenter != null) {
+ mActionMenuPresenter.dismissPopupMenus();
+ }
}
public void setCustomNavigationView(View view) {
@@ -380,6 +400,12 @@ public class ActionBarView extends ViewGroup {
public void setDisplayOptions(int options) {
final int flagsChanged = options ^ mDisplayOptions;
mDisplayOptions = options;
+
+ if ((flagsChanged & ActionBar.DISPLAY_DISABLE_HOME) != 0) {
+ final boolean disableHome = (options & ActionBar.DISPLAY_DISABLE_HOME) != 0;
+ mHomeLayout.setEnabled(!disableHome);
+ }
+
if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
final int vis = (options & ActionBar.DISPLAY_SHOW_HOME) != 0 ? VISIBLE : GONE;
mHomeLayout.setVisibility(vis);
@@ -416,6 +442,48 @@ public class ActionBarView extends ViewGroup {
}
}
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ if (icon != null &&
+ ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) {
+ mIconView.setImageDrawable(icon);
+ }
+ }
+
+ public void setIcon(int resId) {
+ setIcon(mContext.getResources().getDrawableForDensity(resId, getPreferredIconDensity()));
+ }
+
+ public void setLogo(Drawable logo) {
+ mLogo = logo;
+ if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
+ mIconView.setImageDrawable(logo);
+ }
+ }
+
+ public void setLogo(int resId) {
+ mContext.getResources().getDrawable(resId);
+ }
+
+ /**
+ * @return Drawable density to load that will best fit the available height.
+ */
+ private int getPreferredIconDensity() {
+ final Resources res = mContext.getResources();
+ final int availableHeight = getLayoutParams().height -
+ mIconView.getPaddingTop() - mIconView.getPaddingBottom();
+ int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
+
+ if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
+ return DisplayMetrics.DENSITY_LOW;
+ } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) {
+ return DisplayMetrics.DENSITY_MEDIUM;
+ } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) {
+ return DisplayMetrics.DENSITY_HIGH;
+ }
+ return DisplayMetrics.DENSITY_XHIGH;
+ }
+
public void setNavigationMode(int mode) {
final int oldMode = mNavigationMode;
if (mode != oldMode) {
@@ -426,7 +494,7 @@ public class ActionBarView extends ViewGroup {
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
- if (mTabLayout != null) {
+ if (mTabScrollView != null) {
removeView(mTabScrollView);
}
}
@@ -451,7 +519,9 @@ public class ActionBarView extends ViewGroup {
break;
case ActionBar.NAVIGATION_MODE_TABS:
ensureTabsExist();
- addView(mTabScrollView);
+ if (mTabScrollView != null) {
+ addView(mTabScrollView);
+ }
break;
}
mNavigationMode = mode;
@@ -460,15 +530,24 @@ public class ActionBarView extends ViewGroup {
}
private void ensureTabsExist() {
+ if (!mIncludeTabs) return;
+
if (mTabScrollView == null) {
mTabScrollView = new HorizontalScrollView(getContext());
mTabScrollView.setHorizontalFadingEdgeEnabled(true);
- mTabLayout = new LinearLayout(getContext(), null,
- com.android.internal.R.attr.actionBarTabBarStyle);
+ mTabLayout = createTabContainer();
mTabScrollView.addView(mTabLayout);
}
}
+ public ViewGroup createTabContainer() {
+ ViewGroup result = new LinearLayout(getContext(), null,
+ com.android.internal.R.attr.actionBarTabBarStyle);
+ result.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ mContentHeight));
+ return result;
+ }
+
public void setDropdownAdapter(SpinnerAdapter adapter) {
mSpinnerAdapter = adapter;
if (mSpinner != null) {
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 9f9f020..0d32d4b 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import android.os.Bundle;
+import android.os.IBinder;
import android.text.Editable;
import android.text.method.KeyListener;
import android.util.Log;
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 5857acb..18076c4 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -22,7 +22,7 @@ import android.widget.RemoteViews;
/** {@hide} */
interface IRemoteViewsFactory {
void onDataSetChanged();
- void onDestroy(in Intent intent);
+ oneway void onDestroy(in Intent intent);
int getCount();
RemoteViews getViewAt(int position);
RemoteViews getLoadingView();
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 076a1cb..c34cb9e 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -357,6 +357,12 @@ public class PointerLocationView extends View {
case MotionEvent.ACTION_HOVER_MOVE:
prefix = "HOVER MOVE";
break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ prefix = "HOVER ENTER";
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ prefix = "HOVER EXIT";
+ break;
case MotionEvent.ACTION_SCROLL:
prefix = "SCROLL";
break;
diff --git a/core/java/com/google/android/mms/pdu/EncodedStringValue.java b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
index a27962d..9495c1c 100644
--- a/core/java/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/core/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -17,7 +17,6 @@
package com.google.android.mms.pdu;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -31,7 +30,7 @@ import java.util.ArrayList;
public class EncodedStringValue implements Cloneable {
private static final String TAG = "EncodedStringValue";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
/**
* The Char-set value.
diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java
index 3f185aa..f7f71ed 100755
--- a/core/java/com/google/android/mms/pdu/PduParser.java
+++ b/core/java/com/google/android/mms/pdu/PduParser.java
@@ -20,7 +20,6 @@ package com.google.android.mms.pdu;
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayInputStream;
@@ -86,7 +85,7 @@ public class PduParser {
*/
private static final String LOG_TAG = "PduParser";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
/**
* Constructor.
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 9fdd204..4d2d535 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -39,7 +39,6 @@ import android.provider.Telephony.Mms.Addr;
import android.provider.Telephony.Mms.Part;
import android.provider.Telephony.MmsSms.PendingMessages;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -63,7 +62,7 @@ import com.google.android.mms.pdu.EncodedStringValue;
public class PduPersister {
private static final String TAG = "PduPersister";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
diff --git a/core/java/com/google/android/mms/util/AbstractCache.java b/core/java/com/google/android/mms/util/AbstractCache.java
index 670439c..39b2abf 100644
--- a/core/java/com/google/android/mms/util/AbstractCache.java
+++ b/core/java/com/google/android/mms/util/AbstractCache.java
@@ -17,7 +17,6 @@
package com.google.android.mms.util;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -25,7 +24,7 @@ import java.util.HashMap;
public abstract class AbstractCache<K, V> {
private static final String TAG = "AbstractCache";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final int MAX_CACHED_ITEMS = 500;
diff --git a/core/java/com/google/android/mms/util/PduCache.java b/core/java/com/google/android/mms/util/PduCache.java
index 866ca1e..059af72 100644
--- a/core/java/com/google/android/mms/util/PduCache.java
+++ b/core/java/com/google/android/mms/util/PduCache.java
@@ -21,7 +21,6 @@ import android.content.ContentUris;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.Telephony.Mms;
-import android.util.Config;
import android.util.Log;
import java.util.HashMap;
@@ -30,7 +29,7 @@ import java.util.HashSet;
public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
private static final String TAG = "PduCache";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean LOCAL_LOGV = false;
private static final int MMS_ALL = 0;
private static final int MMS_ALL_ID = 1;