summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java13
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java24
-rw-r--r--core/java/android/app/Activity.java338
-rw-r--r--core/java/android/app/ActivityManager.java148
-rw-r--r--core/java/android/app/ActivityManagerNative.java182
-rw-r--r--core/java/android/app/ActivityOptions.java141
-rw-r--r--core/java/android/app/ActivityThread.java13
-rw-r--r--core/java/android/app/ApplicationThreadNative.java13
-rw-r--r--core/java/android/app/ContextImpl.java25
-rw-r--r--core/java/android/app/Fragment.java43
-rw-r--r--core/java/android/app/IActivityManager.java71
-rw-r--r--core/java/android/app/IApplicationThread.java2
-rw-r--r--core/java/android/app/Instrumentation.java29
-rw-r--r--core/java/android/app/PendingIntent.java94
-rw-r--r--core/java/android/content/ContentProvider.java180
-rw-r--r--core/java/android/content/ContentProviderNative.java24
-rw-r--r--core/java/android/content/Context.java73
-rw-r--r--core/java/android/content/ContextWrapper.java18
-rw-r--r--core/java/android/content/SyncStorageEngine.java2
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl3
-rw-r--r--core/java/android/content/pm/PackageManager.java5
-rw-r--r--core/java/android/content/pm/PackageParser.java45
-rw-r--r--core/java/android/database/AbstractCursor.java61
-rw-r--r--core/java/android/database/BulkCursorDescriptor.java78
-rw-r--r--core/java/android/database/BulkCursorNative.java91
-rw-r--r--core/java/android/database/BulkCursorToCursorAdaptor.java37
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java51
-rw-r--r--core/java/android/database/DatabaseUtils.java14
-rw-r--r--core/java/android/database/IBulkCursor.java32
-rw-r--r--core/java/android/database/SQLException.java13
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java99
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteException.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java18
-rw-r--r--core/java/android/hardware/Camera.java147
-rw-r--r--core/java/android/hardware/GeomagneticField.java2
-rw-r--r--core/java/android/net/NetworkIdentity.java20
-rw-r--r--core/java/android/net/NetworkTemplate.java10
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/StrictMode.java2
-rw-r--r--core/java/android/os/Trace.java94
-rwxr-xr-xcore/java/android/provider/Telephony.java28
-rw-r--r--core/java/android/text/DynamicLayout.java145
-rw-r--r--core/java/android/text/Layout.java92
-rw-r--r--core/java/android/text/SpanSet.java111
-rw-r--r--core/java/android/text/TextLine.java74
-rw-r--r--core/java/android/util/LocaleUtil.java7
-rw-r--r--core/java/android/view/DisplayList.java202
-rw-r--r--core/java/android/view/GLES20DisplayList.java245
-rw-r--r--core/java/android/view/Gravity.java61
-rw-r--r--core/java/android/view/HardwareLayer.java19
-rw-r--r--core/java/android/view/HardwareRenderer.java5
-rw-r--r--core/java/android/view/View.java582
-rw-r--r--core/java/android/view/ViewGroup.java97
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java21
-rw-r--r--core/java/android/view/ViewRootImpl.java109
-rw-r--r--core/java/android/webkit/AutoCompletePopup.java223
-rw-r--r--core/java/android/webkit/BrowserFrame.java3
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java11
-rw-r--r--core/java/android/webkit/FindListener.java32
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java13
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java6
-rw-r--r--core/java/android/webkit/WebView.java29
-rw-r--r--core/java/android/webkit/WebViewClassic.java349
-rw-r--r--core/java/android/webkit/WebViewCore.java67
-rw-r--r--core/java/android/webkit/WebViewProvider.java4
-rw-r--r--core/java/android/widget/AbsListView.java82
-rw-r--r--core/java/android/widget/DatePicker.java37
-rw-r--r--core/java/android/widget/EdgeEffect.java63
-rw-r--r--core/java/android/widget/FrameLayout.java15
-rw-r--r--core/java/android/widget/GridLayout.java36
-rw-r--r--core/java/android/widget/GridView.java2
-rw-r--r--core/java/android/widget/LinearLayout.java19
-rw-r--r--core/java/android/widget/NumberPicker.java1300
-rw-r--r--core/java/android/widget/RelativeLayout.java14
-rw-r--r--core/java/android/widget/TableLayout.java4
-rw-r--r--core/java/android/widget/TextView.java115
-rw-r--r--core/java/android/widget/TimePicker.java31
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java44
-rw-r--r--core/java/com/android/internal/util/BitwiseOutputStream.java1
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java5
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java48
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java22
84 files changed, 4911 insertions, 1739 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index adc7d35..cf1e8f3 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1780,7 +1780,7 @@ public class AccountManagerService
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
- if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
}
@@ -1817,7 +1817,16 @@ public class AccountManagerService
// Migrate old file, if it exists, to the new location
File oldFile = new File(systemDir, DATABASE_NAME);
if (oldFile.exists()) {
- oldFile.renameTo(databaseFile);
+ // Check for use directory; create if it doesn't exist, else renameTo will fail
+ File userDir = new File(systemDir, "users/" + userId);
+ if (!userDir.exists()) {
+ if (!userDir.mkdirs()) {
+ throw new IllegalStateException("User dir cannot be created: " + userDir);
+ }
+ }
+ if (!oldFile.renameTo(databaseFile)) {
+ throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+ }
}
}
return databaseFile.getPath();
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f69120a..e2b8ce4 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -557,12 +557,22 @@ public class ValueAnimator extends Animator {
public void handleMessage(Message msg) {
switch (msg.what) {
case ANIMATION_START:
- doAnimationStart();
+ // If there are already active animations, or if another ANIMATION_START
+ // message was processed during this frame, then the pending list may already
+ // have been cleared. If that's the case, we've already processed the
+ // active animations for this frame - don't do it again.
+ if (mPendingAnimations.size() > 0) {
+ doAnimationFrame();
+ }
break;
}
}
- private void doAnimationStart() {
+ private void doAnimationFrame() {
+ // currentTime holds the common time for all animations processed
+ // during this frame
+ long currentTime = AnimationUtils.currentAnimationTimeMillis();
+
// mPendingAnimations holds any animations that have requested to be started
// We're going to clear mPendingAnimations, but starting animation may
// cause more to be added to the pending list (for example, if one animation
@@ -583,15 +593,7 @@ public class ValueAnimator extends Animator {
}
}
}
- doAnimationFrame();
- }
-
- private void doAnimationFrame() {
- // currentTime holds the common time for all animations processed
- // during this frame
- long currentTime = AnimationUtils.currentAnimationTimeMillis();
-
- // First, process animations currently sitting on the delayed queue, adding
+ // Next, process animations currently sitting on the delayed queue, adding
// them to the active animations if they are ready
int numDelayedAnims = mDelayedAnims.size();
for (int i = 0; i < numDelayedAnims; ++i) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 599487d..ea32745 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -32,7 +32,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -55,7 +54,6 @@ import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -3166,42 +3164,61 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ */
+ public void startActivityForResult(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode, null);
+ }
+
+ /**
* Launch an activity for which you would like a result when it finished.
* When this activity exits, your
* onActivityResult() method will be called with the given requestCode.
* Using a negative requestCode is the same as calling
* {@link #startActivity} (the activity is not launched as a sub-activity).
- *
+ *
* <p>Note that this method should only be used with Intent protocols
* that are defined to return a result. In other protocols (such as
* {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
* not get the result when you expect. For example, if the activity you
* are launching uses the singleTask launch mode, it will not run in your
* task and thus you will immediately receive a cancel result.
- *
+ *
* <p>As a special case, if you call startActivityForResult() with a requestCode
* >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
* activity, then your window will not be displayed until a result is
* returned back from the started activity. This is to avoid visible
* flickering when redirecting to another activity.
- *
+ *
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
- *
+ *
* @param intent The intent to start.
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits.
- *
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
* @throws android.content.ActivityNotFoundException
- *
+ *
* @see #startActivity
*/
- public void startActivityForResult(Intent intent, int requestCode) {
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, requestCode);
+ intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
@@ -3218,11 +3235,39 @@ public class Activity extends ContextThemeWrapper
mStartedActivity = true;
}
} else {
- mParent.startActivityFromChild(this, intent, requestCode);
+ if (options != null) {
+ mParent.startActivityFromChild(this, intent, requestCode, options);
+ } else {
+ // Note we want to go through this method for compatibility with
+ // existing applications that may have overridden it.
+ mParent.startActivityFromChild(this, intent, requestCode);
+ }
}
}
/**
+ * Same as calling {@link #startIntentSenderForResult(IntentSender, int,
+ * Intent, int, int, int, Bundle)} with no options.
+ *
+ * @param intent The IntentSender to launch.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ */
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, null);
+ }
+
+ /**
* Like {@link #startActivityForResult(Intent, int)}, but allowing you
* to use a IntentSender to describe the activity to be started. If
* the IntentSender is for an activity, that activity will be started
@@ -3241,21 +3286,32 @@ public class Activity extends ContextThemeWrapper
* @param flagsValues Desired values for any bits set in
* <var>flagsMask</var>
* @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
*/
public void startIntentSenderForResult(IntentSender intent, int requestCode,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
- throws IntentSender.SendIntentException {
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
if (mParent == null) {
startIntentSenderForResultInner(intent, requestCode, fillInIntent,
- flagsMask, flagsValues, this);
+ flagsMask, flagsValues, this, options);
+ } else if (options != null) {
+ mParent.startIntentSenderFromChild(this, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
} else {
+ // Note we want to go through this call for compatibility with
+ // existing applications that may have overridden the method.
mParent.startIntentSenderFromChild(this, intent, requestCode,
fillInIntent, flagsMask, flagsValues, extraFlags);
}
}
private void startIntentSenderForResultInner(IntentSender intent, int requestCode,
- Intent fillInIntent, int flagsMask, int flagsValues, Activity activity)
+ Intent fillInIntent, int flagsMask, int flagsValues, Activity activity,
+ Bundle options)
throws IntentSender.SendIntentException {
try {
String resolvedType = null;
@@ -3266,8 +3322,8 @@ public class Activity extends ContextThemeWrapper
int result = ActivityManagerNative.getDefault()
.startActivityIntentSender(mMainThread.getApplicationThread(), intent,
fillInIntent, resolvedType, mToken, activity.mEmbeddedID,
- requestCode, flagsMask, flagsValues);
- if (result == IActivityManager.START_CANCELED) {
+ requestCode, flagsMask, flagsValues, options);
+ if (result == ActivityManager.START_CANCELED) {
throw new IntentSender.SendIntentException();
}
Instrumentation.checkStartActivityResult(result, null);
@@ -3286,6 +3342,22 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Same as {@link #startActivity(Intent, Bundle)} with no options
+ * specified.
+ *
+ * @param intent The intent to start.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see {@link #startActivity(Intent, Bundle)}
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivity(Intent intent) {
+ startActivity(intent, null);
+ }
+
+ /**
* Launch a new activity. You will not receive any information about when
* the activity exits. This implementation overrides the base version,
* providing information about
@@ -3298,14 +3370,40 @@ public class Activity extends ContextThemeWrapper
* if there was no Activity found to run the given Intent.
*
* @param intent The intent to start.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
- *
+ *
+ * @see {@link #startActivity(Intent)}
* @see #startActivityForResult
*/
@Override
- public void startActivity(Intent intent) {
- startActivityForResult(intent, -1);
+ public void startActivity(Intent intent, Bundle options) {
+ if (options != null) {
+ startActivityForResult(intent, -1, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ startActivityForResult(intent, -1);
+ }
+ }
+
+ /**
+ * Same as {@link #startActivities(Intent[], Bundle)} with no options
+ * specified.
+ *
+ * @param intents The intents to start.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see {@link #startActivities(Intent[], Bundle)}
+ * @see #startActivityForResult
+ */
+ @Override
+ public void startActivities(Intent[] intents) {
+ startActivities(intents, null);
}
/**
@@ -3321,22 +3419,24 @@ public class Activity extends ContextThemeWrapper
* if there was no Activity found to run the given Intent.
*
* @param intents The intents to start.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
+ * @see {@link #startActivities(Intent[])}
* @see #startActivityForResult
*/
@Override
- public void startActivities(Intent[] intents) {
+ public void startActivities(Intent[] intents, Bundle options) {
mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(),
- mToken, this, intents);
+ mToken, this, intents, options);
}
/**
- * Like {@link #startActivity(Intent)}, but taking a IntentSender
- * to start; see
- * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
- * for more information.
+ * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+ * with no options.
*
* @param intent The IntentSender to launch.
* @param fillInIntent If non-null, this will be provided as the
@@ -3350,8 +3450,61 @@ public class Activity extends ContextThemeWrapper
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
- startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
- flagsValues, extraFlags);
+ startIntentSender(intent, fillInIntent, flagsMask, flagsValues,
+ extraFlags, null);
+ }
+
+ /**
+ * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
+ * to start; see
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)}
+ * for more information.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
+ */
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ if (options != null) {
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+ flagsValues, extraFlags);
+ }
+ }
+
+ /**
+ * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param intent The intent to start.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits, as described in
+ * {@link #startActivityForResult}.
+ *
+ * @return If a new activity was launched then true is returned; otherwise
+ * false is returned and you must handle the Intent yourself.
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ */
+ public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+ return startActivityIfNeeded(intent, requestCode, null);
}
/**
@@ -3374,6 +3527,9 @@ public class Activity extends ContextThemeWrapper
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits, as described in
* {@link #startActivityForResult}.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @return If a new activity was launched then true is returned; otherwise
* false is returned and you must handle the Intent yourself.
@@ -3381,24 +3537,23 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
* @see #startActivityForResult
*/
- public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+ public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) {
if (mParent == null) {
- int result = IActivityManager.START_RETURN_INTENT_TO_CALLER;
+ int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
try {
intent.setAllowFds(false);
result = ActivityManagerNative.getDefault()
.startActivity(mMainThread.getApplicationThread(),
- intent, intent.resolveTypeIfNeeded(
- getContentResolver()),
- null, 0,
- mToken, mEmbeddedID, requestCode, true, false,
- null, null, false);
+ intent, intent.resolveTypeIfNeeded(getContentResolver()),
+ mToken, mEmbeddedID, requestCode,
+ ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null,
+ options);
} catch (RemoteException e) {
// Empty
}
-
+
Instrumentation.checkStartActivityResult(result, intent);
-
+
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
@@ -3409,7 +3564,7 @@ public class Activity extends ContextThemeWrapper
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
- return result != IActivityManager.START_RETURN_INTENT_TO_CALLER;
+ return result != ActivityManager.START_RETURN_INTENT_TO_CALLER;
}
throw new UnsupportedOperationException(
@@ -3417,6 +3572,24 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with
+ * no options.
+ *
+ * @param intent The intent to dispatch to the next activity. For
+ * correct behavior, this must be the same as the Intent that started
+ * your own activity; the only changes you can make are to the extras
+ * inside of it.
+ *
+ * @return Returns a boolean indicating whether there was another Activity
+ * to start: true if there was a next activity to start, false if there
+ * wasn't. In general, if true is returned you will then want to call
+ * finish() on yourself.
+ */
+ public boolean startNextMatchingActivity(Intent intent) {
+ return startNextMatchingActivity(intent, null);
+ }
+
+ /**
* Special version of starting an activity, for use when you are replacing
* other activity components. You can use this to hand the Intent off
* to the next Activity that can handle it. You typically call this in
@@ -3426,18 +3599,21 @@ public class Activity extends ContextThemeWrapper
* correct behavior, this must be the same as the Intent that started
* your own activity; the only changes you can make are to the extras
* inside of it.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @return Returns a boolean indicating whether there was another Activity
* to start: true if there was a next activity to start, false if there
* wasn't. In general, if true is returned you will then want to call
* finish() on yourself.
*/
- public boolean startNextMatchingActivity(Intent intent) {
+ public boolean startNextMatchingActivity(Intent intent, Bundle options) {
if (mParent == null) {
try {
intent.setAllowFds(false);
return ActivityManagerNative.getDefault()
- .startNextMatchingActivity(mToken, intent);
+ .startNextMatchingActivity(mToken, intent, options);
} catch (RemoteException e) {
// Empty
}
@@ -3447,7 +3623,25 @@ public class Activity extends ContextThemeWrapper
throw new UnsupportedOperationException(
"startNextMatchingActivity can only be called from a top-level activity");
}
-
+
+ /**
+ * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param child The activity making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
+ */
+ public void startActivityFromChild(Activity child, Intent intent,
+ int requestCode) {
+ startActivityFromChild(child, intent, requestCode);
+ }
+
/**
* This is called when a child activity of this one calls its
* {@link #startActivity} or {@link #startActivityForResult} method.
@@ -3457,7 +3651,10 @@ public class Activity extends ContextThemeWrapper
*
* @param child The activity making the call.
* @param intent The intent to start.
- * @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
@@ -3465,11 +3662,11 @@ public class Activity extends ContextThemeWrapper
* @see #startActivityForResult
*/
public void startActivityFromChild(Activity child, Intent intent,
- int requestCode) {
+ int requestCode, Bundle options) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
- intent, requestCode);
+ intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, child.mEmbeddedID, requestCode,
@@ -3478,6 +3675,24 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)}
+ * with no options.
+ *
+ * @param fragment The fragment making the call.
+ * @param intent The intent to start.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see Fragment#startActivity
+ * @see Fragment#startActivityForResult
+ */
+ public void startActivityFromFragment(Fragment fragment, Intent intent,
+ int requestCode) {
+ startActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
* This is called when a Fragment in this activity calls its
* {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
* method.
@@ -3488,6 +3703,9 @@ public class Activity extends ContextThemeWrapper
* @param fragment The fragment making the call.
* @param intent The intent to start.
* @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
@@ -3495,11 +3713,11 @@ public class Activity extends ContextThemeWrapper
* @see Fragment#startActivityForResult
*/
public void startActivityFromFragment(Fragment fragment, Intent intent,
- int requestCode) {
+ int requestCode, Bundle options) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, fragment,
- intent, requestCode);
+ intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, fragment.mWho, requestCode,
@@ -3508,6 +3726,18 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender,
+ * int, Intent, int, int, int, Bundle)} with no options.
+ */
+ public void startIntentSenderFromChild(Activity child, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags)
+ throws IntentSender.SendIntentException {
+ startIntentSenderFromChild(child, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, null);
+ }
+
+ /**
* Like {@link #startActivityFromChild(Activity, Intent, int)}, but
* taking a IntentSender; see
* {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
@@ -3515,16 +3745,24 @@ public class Activity extends ContextThemeWrapper
*/
public void startIntentSenderFromChild(Activity child, IntentSender intent,
int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
- int extraFlags)
+ int extraFlags, Bundle options)
throws IntentSender.SendIntentException {
startIntentSenderForResultInner(intent, requestCode, fillInIntent,
- flagsMask, flagsValues, child);
+ flagsMask, flagsValues, child, options);
}
/**
* Call immediately after one of the flavors of {@link #startActivity(Intent)}
* or {@link #finish} to specify an explicit transition animation to
* perform next.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+ * to using this with starting activities is to supply the desired animation
+ * information through a {@link ActivityOptions} bundle to
+ * {@link #startActivity(Intent, Bundle) or a related function. This allows
+ * you to specify a custom animation even when starting an activity from
+ * outside the context of the current top activity.
+ *
* @param enterAnim A resource ID of the animation resource to use for
* the incoming activity. Use 0 for no animation.
* @param exitAnim A resource ID of the animation resource to use for
@@ -3844,9 +4082,9 @@ public class Activity extends ContextThemeWrapper
data.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
- IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
+ ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
mParent == null ? mToken : mParent.mToken,
- mEmbeddedID, requestCode, new Intent[] { data }, null, flags);
+ mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
// Empty
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 59c803e..d056b17 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -59,6 +59,154 @@ public class ActivityManager {
private final Context mContext;
private final Handler mHandler;
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * start had to be canceled.
+ * @hide
+ */
+ public static final int START_CANCELED = -6;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * thing being started is not an activity.
+ * @hide
+ */
+ public static final int START_NOT_ACTIVITY = -5;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * caller does not have permission to start the activity.
+ * @hide
+ */
+ public static final int START_PERMISSION_DENIED = -4;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * caller has requested both to forward a result and to receive
+ * a result.
+ * @hide
+ */
+ public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * requested class is not found.
+ * @hide
+ */
+ public static final int START_CLASS_NOT_FOUND = -2;
+
+ /**
+ * Result for IActivityManager.startActivity: an error where the
+ * given Intent could not be resolved to an activity.
+ * @hide
+ */
+ public static final int START_INTENT_NOT_RESOLVED = -1;
+
+ /**
+ * Result for IActivityManaqer.startActivity: the activity was started
+ * successfully as normal.
+ * @hide
+ */
+ public static final int START_SUCCESS = 0;
+
+ /**
+ * Result for IActivityManaqer.startActivity: the caller asked that the Intent not
+ * be executed if it is the recipient, and that is indeed the case.
+ * @hide
+ */
+ public static final int START_RETURN_INTENT_TO_CALLER = 1;
+
+ /**
+ * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+ * a task was simply brought to the foreground.
+ * @hide
+ */
+ public static final int START_TASK_TO_FRONT = 2;
+
+ /**
+ * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+ * the given Intent was given to the existing top activity.
+ * @hide
+ */
+ public static final int START_DELIVERED_TO_TOP = 3;
+
+ /**
+ * Result for IActivityManaqer.startActivity: request was canceled because
+ * app switches are temporarily canceled to ensure the user's last request
+ * (such as pressing home) is performed.
+ * @hide
+ */
+ public static final int START_SWITCHES_CANCELED = 4;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: do special start mode where
+ * a new activity is launched only if it is needed.
+ * @hide
+ */
+ public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * debugging.
+ * @hide
+ */
+ public static final int START_FLAG_DEBUG = 1<<1;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: launch the app for
+ * OpenGL tracing.
+ * @hide
+ */
+ public static final int START_FLAG_OPENGL_TRACES = 1<<2;
+
+ /**
+ * Flag for IActivityManaqer.startActivity: if the app is being
+ * launched for profiling, automatically stop the profiler once done.
+ * @hide
+ */
+ public static final int START_FLAG_AUTO_STOP_PROFILER = 1<<3;
+
+ /**
+ * Result for IActivityManaqer.broadcastIntent: success!
+ * @hide
+ */
+ public static final int BROADCAST_SUCCESS = 0;
+
+ /**
+ * Result for IActivityManaqer.broadcastIntent: attempt to broadcast
+ * a sticky intent without appropriate permission.
+ * @hide
+ */
+ public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a sendBroadcast operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_BROADCAST = 1;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a startActivity operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_ACTIVITY = 2;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for an activity result operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
+
+ /**
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * for a startService operation.
+ * @hide
+ */
+ public static final int INTENT_SENDER_SERVICE = 4;
+
/*package*/ ActivityManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b952649..a3cc352 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -117,20 +117,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
- int grantedMode = data.readInt();
IBinder resultTo = data.readStrongBinder();
- String resultWho = data.readString();
+ String resultWho = data.readString();
int requestCode = data.readInt();
- boolean onlyIfNeeded = data.readInt() != 0;
- boolean debug = data.readInt() != 0;
+ int startFlags = data.readInt();
String profileFile = data.readString();
ParcelFileDescriptor profileFd = data.readInt() != 0
? data.readFileDescriptor() : null;
- boolean autoStopProfiler = data.readInt() != 0;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivity(app, intent, resolvedType,
- grantedUriPermissions, grantedMode, resultTo, resultWho,
- requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler);
+ resultTo, resultWho, requestCode, startFlags,
+ profileFile, profileFd, options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -143,20 +141,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
- int grantedMode = data.readInt();
IBinder resultTo = data.readStrongBinder();
- String resultWho = data.readString();
+ String resultWho = data.readString();
int requestCode = data.readInt();
- boolean onlyIfNeeded = data.readInt() != 0;
- boolean debug = data.readInt() != 0;
+ int startFlags = data.readInt();
String profileFile = data.readString();
ParcelFileDescriptor profileFd = data.readInt() != 0
? data.readFileDescriptor() : null;
- boolean autoStopProfiler = data.readInt() != 0;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
WaitResult result = startActivityAndWait(app, intent, resolvedType,
- grantedUriPermissions, grantedMode, resultTo, resultWho,
- requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler);
+ resultTo, resultWho, requestCode, startFlags,
+ profileFile, profileFd, options);
reply.writeNoException();
result.writeToParcel(reply, 0);
return true;
@@ -169,17 +165,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
- int grantedMode = data.readInt();
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
- boolean onlyIfNeeded = data.readInt() != 0;
- boolean debug = data.readInt() != 0;
+ int startFlags = data.readInt();
Configuration config = Configuration.CREATOR.createFromParcel(data);
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivityWithConfig(app, intent, resolvedType,
- grantedUriPermissions, grantedMode, resultTo, resultWho,
- requestCode, onlyIfNeeded, debug, config);
+ resultTo, resultWho, requestCode, startFlags, config, options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -201,9 +195,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
int requestCode = data.readInt();
int flagsMask = data.readInt();
int flagsValues = data.readInt();
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivityIntentSender(app, intent,
fillInIntent, resolvedType, resultTo, resultWho,
- requestCode, flagsMask, flagsValues);
+ requestCode, flagsMask, flagsValues, options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -214,7 +210,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder callingActivity = data.readStrongBinder();
Intent intent = Intent.CREATOR.createFromParcel(data);
- boolean result = startNextMatchingActivity(callingActivity, intent);
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ boolean result = startNextMatchingActivity(callingActivity, intent, options);
reply.writeNoException();
reply.writeInt(result ? 1 : 0);
return true;
@@ -837,9 +835,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
requestResolvedTypes = null;
}
int fl = data.readInt();
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
IIntentSender res = getIntentSender(type, packageName, token,
resultWho, requestCode, requestIntents,
- requestResolvedTypes, fl);
+ requestResolvedTypes, fl, options);
reply.writeNoException();
reply.writeStrongBinder(res != null ? res.asBinder() : null);
return true;
@@ -1227,9 +1227,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
- boolean onlyIfNeeded = data.readInt() != 0;
+ int startFlags = data.readInt();
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivityInPackage(uid, intent, resolvedType,
- resultTo, resultWho, requestCode, onlyIfNeeded);
+ resultTo, resultWho, requestCode, startFlags, options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -1408,7 +1410,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Intent[] intents = data.createTypedArray(Intent.CREATOR);
String[] resolvedTypes = data.createStringArray();
IBinder resultTo = data.readStrongBinder();
- int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo);
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int result = startActivitiesInPackage(uid, intents, resolvedTypes,
+ resultTo, options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -1422,7 +1427,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Intent[] intents = data.createTypedArray(Intent.CREATOR);
String[] resolvedTypes = data.createStringArray();
IBinder resultTo = data.readStrongBinder();
- int result = startActivities(app, intents, resolvedTypes, resultTo);
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int result = startActivities(app, intents, resolvedTypes, resultTo,
+ options);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -1607,31 +1615,26 @@ class ActivityManagerProxy implements IActivityManager
{
mRemote = remote;
}
-
+
public IBinder asBinder()
{
return mRemote;
}
-
+
public int startActivity(IApplicationThread caller, Intent intent,
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
- IBinder resultTo, String resultWho,
- int requestCode, boolean onlyIfNeeded,
- boolean debug, String profileFile, ParcelFileDescriptor profileFd,
- boolean autoStopProfiler) throws RemoteException {
+ String resolvedType, IBinder resultTo, String resultWho, int requestCode,
+ int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
- data.writeTypedArray(grantedUriPermissions, 0);
- data.writeInt(grantedMode);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
- data.writeInt(onlyIfNeeded ? 1 : 0);
- data.writeInt(debug ? 1 : 0);
+ data.writeInt(startFlags);
data.writeString(profileFile);
if (profileFd != null) {
data.writeInt(1);
@@ -1639,7 +1642,12 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
- data.writeInt(autoStopProfiler ? 1 : 0);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -1648,24 +1656,19 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
- IBinder resultTo, String resultWho,
- int requestCode, boolean onlyIfNeeded,
- boolean debug, String profileFile, ParcelFileDescriptor profileFd,
- boolean autoStopProfiler) throws RemoteException {
+ String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
- data.writeTypedArray(grantedUriPermissions, 0);
- data.writeInt(grantedMode);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
- data.writeInt(onlyIfNeeded ? 1 : 0);
- data.writeInt(debug ? 1 : 0);
+ data.writeInt(startFlags);
data.writeString(profileFile);
if (profileFd != null) {
data.writeInt(1);
@@ -1673,7 +1676,12 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
- data.writeInt(autoStopProfiler ? 1 : 0);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0);
reply.readException();
WaitResult result = WaitResult.CREATOR.createFromParcel(reply);
@@ -1682,24 +1690,26 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
public int startActivityWithConfig(IApplicationThread caller, Intent intent,
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
- IBinder resultTo, String resultWho,
- int requestCode, boolean onlyIfNeeded,
- boolean debug, Configuration config) throws RemoteException {
+ String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int startFlags, Configuration config,
+ Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
- data.writeTypedArray(grantedUriPermissions, 0);
- data.writeInt(grantedMode);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
- data.writeInt(onlyIfNeeded ? 1 : 0);
- data.writeInt(debug ? 1 : 0);
+ data.writeInt(startFlags);
config.writeToParcel(data, 0);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -1710,7 +1720,7 @@ class ActivityManagerProxy implements IActivityManager
public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues) throws RemoteException {
+ int flagsMask, int flagsValues, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -1728,6 +1738,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(requestCode);
data.writeInt(flagsMask);
data.writeInt(flagsValues);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITY_INTENT_SENDER_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -1736,12 +1752,18 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
public boolean startNextMatchingActivity(IBinder callingActivity,
- Intent intent) throws RemoteException {
+ Intent intent, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(callingActivity);
intent.writeToParcel(data, 0);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -2587,8 +2609,8 @@ class ActivityManagerProxy implements IActivityManager
}
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
- int requestCode, Intent[] intents, String[] resolvedTypes, int flags)
- throws RemoteException {
+ int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
+ Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2605,6 +2627,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(0);
}
data.writeInt(flags);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
reply.readException();
IIntentSender res = IIntentSender.Stub.asInterface(
@@ -3069,7 +3097,7 @@ class ActivityManagerProxy implements IActivityManager
public int startActivityInPackage(int uid,
Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, boolean onlyIfNeeded)
+ String resultWho, int requestCode, int startFlags, Bundle options)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3080,7 +3108,13 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
- data.writeInt(onlyIfNeeded ? 1 : 0);
+ data.writeInt(startFlags);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -3333,7 +3367,8 @@ class ActivityManagerProxy implements IActivityManager
}
public int startActivities(IApplicationThread caller,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException {
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+ Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3341,6 +3376,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeTypedArray(intents, 0);
data.writeStringArray(resolvedTypes);
data.writeStrongBinder(resultTo);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -3350,7 +3391,8 @@ class ActivityManagerProxy implements IActivityManager
}
public int startActivitiesInPackage(int uid,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException {
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+ Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3358,6 +3400,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeTypedArray(intents, 0);
data.writeStringArray(resolvedTypes);
data.writeStrongBinder(resultTo);
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
new file mode 100644
index 0000000..03bc338
--- /dev/null
+++ b/core/java/android/app/ActivityOptions.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Helper class for building an options Bundle that can be used with
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)} and related methods.
+ */
+public class ActivityOptions {
+ /**
+ * The package name that created the options.
+ * @hide
+ */
+ public static final String KEY_PACKAGE_NAME = "android:packageName";
+
+ /**
+ * Custom enter animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_ENTER_RES_ID = "android:animEnterRes";
+
+ /**
+ * Custom exit animation resource ID.
+ * @hide
+ */
+ public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
+
+ private String mPackageName;
+ private boolean mIsCustomAnimation;
+ private int mCustomEnterResId;
+ private int mCustomExitResId;
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeCustomAnimation(Context context,
+ int enterResId, int exitResId) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = context.getPackageName();
+ opts.mIsCustomAnimation = true;
+ opts.mCustomEnterResId = enterResId;
+ opts.mCustomExitResId = exitResId;
+ return opts;
+ }
+
+ private ActivityOptions() {
+ }
+
+ /** @hide */
+ public ActivityOptions(Bundle opts) {
+ mPackageName = opts.getString(KEY_PACKAGE_NAME);
+ if (opts.containsKey(KEY_ANIM_ENTER_RES_ID)) {
+ mIsCustomAnimation = true;
+ mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
+ mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+ }
+ }
+
+ /** @hide */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** @hide */
+ public boolean isCustomAnimation() {
+ return mIsCustomAnimation;
+ }
+
+ /** @hide */
+ public int getCustomEnterResId() {
+ return mCustomEnterResId;
+ }
+
+ /** @hide */
+ public int getCustomExitResId() {
+ return mCustomExitResId;
+ }
+
+ /**
+ * Join the values in <var>otherOptions</var> in to this one. Any values
+ * defined in <var>otherOptions</var> replace those in the base options.
+ */
+ public void join(ActivityOptions otherOptions) {
+ if (otherOptions.mPackageName != null) {
+ mPackageName = otherOptions.mPackageName;
+ }
+ if (otherOptions.mIsCustomAnimation) {
+ mIsCustomAnimation = true;
+ mCustomEnterResId = otherOptions.mCustomEnterResId;
+ mCustomExitResId = otherOptions.mCustomExitResId;
+ }
+ }
+
+ /**
+ * Returns the created options as a Bundle, which can be passed to
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)} and related methods.
+ * Note that the returned Bundle is still owned by the ActivityOptions
+ * object; you must not modify it, but can supply it to the startActivity
+ * methods that take an options Bundle.
+ */
+ public Bundle toBundle() {
+ Bundle b = new Bundle();
+ if (mPackageName != null) {
+ b.putString(KEY_PACKAGE_NAME, mPackageName);
+ }
+ if (mIsCustomAnimation) {
+ b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
+ b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+ }
+ return b;
+ }
+}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index aa15f39..2a3e213 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -376,6 +376,7 @@ public final class ActivityThread {
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
int debugMode;
+ boolean enableOpenGlTrace;
boolean restrictedBackupMode;
boolean persistent;
Configuration config;
@@ -676,8 +677,8 @@ public final class ActivityThread {
ComponentName instrumentationName, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, boolean isRestrictedBackupMode, boolean persistent,
- Configuration config, CompatibilityInfo compatInfo,
+ int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode,
+ boolean persistent, Configuration config, CompatibilityInfo compatInfo,
Map<String, IBinder> services, Bundle coreSettings) {
if (services != null) {
@@ -695,6 +696,7 @@ public final class ActivityThread {
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
+ data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
@@ -1800,7 +1802,7 @@ public final class ActivityThread {
if (aInfo == null) {
// Throw an exception.
Instrumentation.checkStartActivityResult(
- IActivityManager.START_CLASS_NOT_FOUND, intent);
+ ActivityManager.START_CLASS_NOT_FOUND, intent);
}
return aInfo;
}
@@ -3912,6 +3914,11 @@ public final class ActivityThread {
}
}
+ // Enable OpenGL tracing if required
+ if (data.enableOpenGlTrace) {
+ GLUtils.enableTracing();
+ }
+
/**
* Initialize the default http proxy in this process for the reasons we set the time zone.
*/
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index e75d7b4..437362b 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -267,6 +267,7 @@ public abstract class ApplicationThreadNative extends Binder
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
int testMode = data.readInt();
+ boolean openGlTrace = data.readInt() != 0;
boolean restrictedBackupMode = (data.readInt() != 0);
boolean persistent = (data.readInt() != 0);
Configuration config = Configuration.CREATOR.createFromParcel(data);
@@ -275,11 +276,11 @@ public abstract class ApplicationThreadNative extends Binder
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName, profileFd, autoStopProfiler,
- testArgs, testWatcher, testMode, restrictedBackupMode, persistent,
- config, compatInfo, services, coreSettings);
+ testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode,
+ persistent, config, compatInfo, services, coreSettings);
return true;
}
-
+
case SCHEDULE_EXIT_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
@@ -849,8 +850,9 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName, String profileName,
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs,
- IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
+ IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace,
+ boolean restrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo,
Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -874,6 +876,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
data.writeInt(debugMode);
+ data.writeInt(openGlTrace ? 1 : 0);
data.writeInt(restrictedBackupMode ? 1 : 0);
data.writeInt(persistent ? 1 : 0);
config.writeToParcel(data, 0);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e348b87..7043a73 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -852,6 +852,11 @@ class ContextImpl extends Context {
@Override
public void startActivity(Intent intent) {
+ startActivity(intent, null);
+ }
+
+ @Override
+ public void startActivity(Intent intent, Bundle options) {
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
@@ -860,11 +865,16 @@ class ContextImpl extends Context {
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
- (Activity)null, intent, -1);
+ (Activity)null, intent, -1, options);
}
@Override
public void startActivities(Intent[] intents) {
+ startActivities(intents, null);
+ }
+
+ @Override
+ public void startActivities(Intent[] intents, Bundle options) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivities() from outside of an Activity "
@@ -873,13 +883,20 @@ class ContextImpl extends Context {
}
mMainThread.getInstrumentation().execStartActivities(
getOuterContext(), mMainThread.getApplicationThread(), null,
- (Activity)null, intents);
+ (Activity)null, intents, options);
}
@Override
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
+ startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, null);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent, Intent fillInIntent,
+ int flagsMask, int flagsValues, int extraFlags, Bundle options)
+ throws IntentSender.SendIntentException {
try {
String resolvedType = null;
if (fillInIntent != null) {
@@ -889,8 +906,8 @@ class ContextImpl extends Context {
int result = ActivityManagerNative.getDefault()
.startActivityIntentSender(mMainThread.getApplicationThread(), intent,
fillInIntent, resolvedType, null, null,
- 0, flagsMask, flagsValues);
- if (result == IActivityManager.START_CANCELED) {
+ 0, flagsMask, flagsValues, options);
+ if (result == ActivityManager.START_CANCELED) {
throw new IntentSender.SendIntentException();
}
Instrumentation.checkStartActivityResult(result, null);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 492fcc7..c493f0f 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -961,27 +961,62 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true);
return mLoaderManager;
}
-
+
/**
* Call {@link Activity#startActivity(Intent)} on the fragment's
* containing Activity.
+ *
+ * @param intent The intent to start.
*/
public void startActivity(Intent intent) {
+ startActivity(intent, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivity(Intent, Bundle)} on the fragment's
+ * containing Activity.
+ *
+ * @param intent The intent to start.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
+ */
+ public void startActivity(Intent intent, Bundle options) {
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mActivity.startActivityFromFragment(this, intent, -1);
+ if (options != null) {
+ mActivity.startActivityFromFragment(this, intent, -1, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ mActivity.startActivityFromFragment(this, intent, -1);
+ }
}
-
+
/**
* Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
* containing Activity.
*/
public void startActivityForResult(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} on the fragment's
+ * containing Activity.
+ */
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mActivity.startActivityFromFragment(this, intent, requestCode);
+ if (options != null) {
+ mActivity.startActivityFromFragment(this, intent, requestCode, options);
+ } else {
+ // Note we want to go through this call for compatibility with
+ // applications that may have overridden the method.
+ mActivity.startActivityFromFragment(this, intent, requestCode, options);
+ }
}
/**
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index ea2545f..31066b5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -50,57 +50,24 @@ import java.util.List;
* {@hide}
*/
public interface IActivityManager extends IInterface {
- /**
- * Returned by startActivity() if the start request was canceled because
- * app switches are temporarily canceled to ensure the user's last request
- * (such as pressing home) is performed.
- */
- public static final int START_SWITCHES_CANCELED = 4;
- /**
- * Returned by startActivity() if an activity wasn't really started, but
- * the given Intent was given to the existing top activity.
- */
- public static final int START_DELIVERED_TO_TOP = 3;
- /**
- * Returned by startActivity() if an activity wasn't really started, but
- * a task was simply brought to the foreground.
- */
- public static final int START_TASK_TO_FRONT = 2;
- /**
- * Returned by startActivity() if the caller asked that the Intent not
- * be executed if it is the recipient, and that is indeed the case.
- */
- public static final int START_RETURN_INTENT_TO_CALLER = 1;
- /**
- * Activity was started successfully as normal.
- */
- public static final int START_SUCCESS = 0;
- public static final int START_INTENT_NOT_RESOLVED = -1;
- public static final int START_CLASS_NOT_FOUND = -2;
- public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3;
- public static final int START_PERMISSION_DENIED = -4;
- public static final int START_NOT_ACTIVITY = -5;
- public static final int START_CANCELED = -6;
public int startActivity(IApplicationThread caller,
- Intent intent, String resolvedType, Uri[] grantedUriPermissions,
- int grantedMode, IBinder resultTo, String resultWho, int requestCode,
- boolean onlyIfNeeded, boolean debug, String profileFile,
- ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
public WaitResult startActivityAndWait(IApplicationThread caller,
- Intent intent, String resolvedType, Uri[] grantedUriPermissions,
- int grantedMode, IBinder resultTo, String resultWho, int requestCode,
- boolean onlyIfNeeded, boolean debug, String profileFile,
- ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
public int startActivityWithConfig(IApplicationThread caller,
- Intent intent, String resolvedType, Uri[] grantedUriPermissions,
- int grantedMode, IBinder resultTo, String resultWho, int requestCode,
- boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException;
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int startFlags, Configuration newConfig,
+ Bundle options) throws RemoteException;
public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues) throws RemoteException;
+ int flagsMask, int flagsValues, Bundle options) throws RemoteException;
public boolean startNextMatchingActivity(IBinder callingActivity,
- Intent intent) throws RemoteException;
+ Intent intent, Bundle options) throws RemoteException;
public boolean finishActivity(IBinder token, int code, Intent data)
throws RemoteException;
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
@@ -109,8 +76,6 @@ public interface IActivityManager extends IInterface {
IIntentReceiver receiver, IntentFilter filter,
String requiredPermission) throws RemoteException;
public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException;
- public static final int BROADCAST_SUCCESS = 0;
- public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
public int broadcastIntent(IApplicationThread caller, Intent intent,
String resolvedType, IIntentReceiver resultTo, int resultCode,
String resultData, Bundle map, String requiredPermission,
@@ -201,14 +166,10 @@ public interface IActivityManager extends IInterface {
public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;
public String getPackageForToken(IBinder token) throws RemoteException;
- public static final int INTENT_SENDER_BROADCAST = 1;
- public static final int INTENT_SENDER_ACTIVITY = 2;
- public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
- public static final int INTENT_SENDER_SERVICE = 4;
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
- int flags) throws RemoteException;
+ int flags, Bundle options) throws RemoteException;
public void cancelIntentSender(IIntentSender sender) throws RemoteException;
public boolean clearApplicationUserData(final String packageName,
final IPackageDataObserver observer, int userId) throws RemoteException;
@@ -302,7 +263,7 @@ public interface IActivityManager extends IInterface {
public int startActivityInPackage(int uid,
Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, boolean onlyIfNeeded)
+ String resultWho, int requestCode, int startFlags, Bundle options)
throws RemoteException;
public void killApplicationWithUid(String pkg, int uid) throws RemoteException;
@@ -342,9 +303,11 @@ public interface IActivityManager extends IInterface {
ParcelFileDescriptor fd) throws RemoteException;
public int startActivities(IApplicationThread caller,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+ Bundle options) throws RemoteException;
public int startActivitiesInPackage(int uid,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+ Bundle options) throws RemoteException;
public int getFrontActivityScreenCompatMode() throws RemoteException;
public void setFrontActivityScreenCompatMode(int mode) throws RemoteException;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 6ad1736..70029d2 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -89,7 +89,7 @@ public interface IApplicationThread extends IInterface {
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher,
- int debugMode, boolean restrictedBackupMode, boolean persistent,
+ int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c037ffb..16299de 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1346,6 +1346,7 @@ public class Instrumentation {
* @param intent The actual Intent to start.
* @param requestCode Identifier for this request's result; less than zero
* if the caller is not expecting a result.
+ * @param options Addition options.
*
* @return To force the return of a particular result, return an
* ActivityResult object containing the desired data; otherwise
@@ -1361,7 +1362,7 @@ public class Instrumentation {
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode) {
+ Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1383,8 +1384,8 @@ public class Instrumentation {
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
- null, 0, token, target != null ? target.mEmbeddedID : null,
- requestCode, false, false, null, null, false);
+ token, target != null ? target.mEmbeddedID : null,
+ requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
@@ -1400,7 +1401,7 @@ public class Instrumentation {
* {@hide}
*/
public void execStartActivities(Context who, IBinder contextThread,
- IBinder token, Activity target, Intent[] intents) {
+ IBinder token, Activity target, Intent[] intents, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1424,7 +1425,7 @@ public class Instrumentation {
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
- .startActivities(whoThread, intents, resolvedTypes, token);
+ .startActivities(whoThread, intents, resolvedTypes, token, options);
checkStartActivityResult(result, intents[0]);
} catch (RemoteException e) {
}
@@ -1459,7 +1460,7 @@ public class Instrumentation {
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Fragment target,
- Intent intent, int requestCode) {
+ Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1481,8 +1482,8 @@ public class Instrumentation {
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
- null, 0, token, target != null ? target.mWho : null,
- requestCode, false, false, null, null, false);
+ token, target != null ? target.mWho : null,
+ requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
@@ -1501,13 +1502,13 @@ public class Instrumentation {
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
- if (res >= IActivityManager.START_SUCCESS) {
+ if (res >= ActivityManager.START_SUCCESS) {
return;
}
switch (res) {
- case IActivityManager.START_INTENT_NOT_RESOLVED:
- case IActivityManager.START_CLASS_NOT_FOUND:
+ case ActivityManager.START_INTENT_NOT_RESOLVED:
+ case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
@@ -1515,13 +1516,13 @@ public class Instrumentation {
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
- case IActivityManager.START_PERMISSION_DENIED:
+ case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
- case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+ case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
- case IActivityManager.START_NOT_ACTIVITY:
+ case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
default:
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index c95066c..aa366b6 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -188,6 +188,35 @@ public final class PendingIntent implements Parcelable {
*/
public static PendingIntent getActivity(Context context, int requestCode,
Intent intent, int flags) {
+ return getActivity(context, requestCode, intent, flags, null);
+ }
+
+ /**
+ * Retrieve a PendingIntent that will start a new activity, like calling
+ * {@link Context#startActivity(Intent) Context.startActivity(Intent)}.
+ * Note that the activity will be started outside of the context of an
+ * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
+ *
+ * @param context The Context in which this PendingIntent should start
+ * the activity.
+ * @param requestCode Private request code for the sender (currently
+ * not used).
+ * @param intent Intent of the activity to be launched.
+ * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
+ * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+ * of the intent that can be supplied when the actual send happens.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options.
+ *
+ * @return Returns an existing or new PendingIntent matching the given
+ * parameters. May return null only if {@link #FLAG_NO_CREATE} has been
+ * supplied.
+ */
+ public static PendingIntent getActivity(Context context, int requestCode,
+ Intent intent, int flags, Bundle options) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
@@ -195,9 +224,10 @@ public final class PendingIntent implements Parcelable {
intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
- IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
null, null, requestCode, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null, flags);
+ resolvedType != null ? new String[] { resolvedType } : null,
+ flags, options);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -247,6 +277,52 @@ public final class PendingIntent implements Parcelable {
*/
public static PendingIntent getActivities(Context context, int requestCode,
Intent[] intents, int flags) {
+ return getActivities(context, requestCode, intents, flags, null);
+ }
+
+ /**
+ * Like {@link #getActivity(Context, int, Intent, int)}, but allows an
+ * array of Intents to be supplied. The first Intent in the array is
+ * taken as the primary key for the PendingIntent, like the single Intent
+ * given to {@link #getActivity(Context, int, Intent, int)}. Upon sending
+ * the resulting PendingIntent, all of the Intents are started in the same
+ * way as they would be by passing them to {@link Context#startActivities(Intent[])}.
+ *
+ * <p class="note">
+ * The <em>first</em> intent in the array will be started outside of the context of an
+ * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. (Activities after
+ * the first in the array are started in the context of the previous activity
+ * in the array, so FLAG_ACTIVITY_NEW_TASK is not needed nor desired for them.)
+ * </p>
+ *
+ * <p class="note">
+ * The <em>last</em> intent in the array represents the key for the
+ * PendingIntent. In other words, it is the significant element for matching
+ * (as done with the single intent given to {@link #getActivity(Context, int, Intent, int)},
+ * its content will be the subject of replacement by
+ * {@link #send(Context, int, Intent)} and {@link #FLAG_UPDATE_CURRENT}, etc.
+ * This is because it is the most specific of the supplied intents, and the
+ * UI the user actually sees when the intents are started.
+ * </p>
+ *
+ * @param context The Context in which this PendingIntent should start
+ * the activity.
+ * @param requestCode Private request code for the sender (currently
+ * not used).
+ * @param intents Array of Intents of the activities to be launched.
+ * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
+ * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+ * of the intent that can be supplied when the actual send happens.
+ *
+ * @return Returns an existing or new PendingIntent matching the given
+ * parameters. May return null only if {@link #FLAG_NO_CREATE} has been
+ * supplied.
+ */
+ public static PendingIntent getActivities(Context context, int requestCode,
+ Intent[] intents, int flags, Bundle options) {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
@@ -256,8 +332,8 @@ public final class PendingIntent implements Parcelable {
try {
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
- IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
- null, null, requestCode, intents, resolvedTypes, flags);
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+ null, null, requestCode, intents, resolvedTypes, flags, options);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -292,9 +368,10 @@ public final class PendingIntent implements Parcelable {
intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
- IActivityManager.INTENT_SENDER_BROADCAST, packageName,
+ ActivityManager.INTENT_SENDER_BROADCAST, packageName,
null, null, requestCode, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null, flags);
+ resolvedType != null ? new String[] { resolvedType } : null,
+ flags, null);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -330,9 +407,10 @@ public final class PendingIntent implements Parcelable {
intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
- IActivityManager.INTENT_SENDER_SERVICE, packageName,
+ ActivityManager.INTENT_SENDER_SERVICE, packageName,
null, null, requestCode, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null, flags);
+ resolvedType != null ? new String[] { resolvedType } : null,
+ flags, null);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 12e3ccf..05ef194 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -30,6 +32,7 @@ import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserId;
import android.util.Log;
import java.io.File;
@@ -267,108 +270,129 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return CancellationSignal.createTransport();
}
- private void enforceReadPermission(Uri uri) {
- final int uid = Binder.getCallingUid();
- if (uid == mMyUid) {
- return;
- }
-
+ private void enforceReadPermission(Uri uri) throws SecurityException {
final Context context = getContext();
- final String rperm = getReadPermission();
final int pid = Binder.getCallingPid();
- if (mExported && (rperm == null
- || context.checkPermission(rperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED)) {
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+
+ if (uid == mMyUid) {
return;
}
-
- PathPermission[] pps = getPathPermissions();
- if (pps != null) {
- final String path = uri.getPath();
- int i = pps.length;
- while (i > 0) {
- i--;
- final PathPermission pp = pps[i];
- final String pprperm = pp.getReadPermission();
- if (pprperm != null && pp.match(path)) {
- if (context.checkPermission(pprperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return;
+
+ if (mExported) {
+ final String componentPerm = getReadPermission();
+ if (componentPerm != null) {
+ if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ } else {
+ missingPerm = componentPerm;
+ }
+ }
+
+ // track if unprotected read is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultRead = (componentPerm == null);
+
+ final PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ for (PathPermission pp : pps) {
+ final String pathPerm = pp.getReadPermission();
+ if (pathPerm != null && pp.match(path)) {
+ if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultRead = false;
+ missingPerm = pathPerm;
+ }
}
}
}
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultRead) return;
}
-
- if (context.checkUriPermission(uri, pid, uid,
- Intent.FLAG_GRANT_READ_URI_PERMISSION)
- == PackageManager.PERMISSION_GRANTED) {
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
return;
}
-
- String msg = "Permission Denial: reading "
- + ContentProvider.this.getClass().getName()
- + " uri " + uri + " from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + rperm;
- throw new SecurityException(msg);
+
+ final String failReason = mExported
+ ? " requires " + missingPerm + ", or grantUriPermission()"
+ : " requires the provider be exported, or grantUriPermission()";
+ throw new SecurityException("Permission Denial: reading "
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid + failReason);
}
- private boolean hasWritePermission(Uri uri) {
- final int uid = Binder.getCallingUid();
- if (uid == mMyUid) {
- return true;
- }
-
+ private void enforceWritePermission(Uri uri) throws SecurityException {
final Context context = getContext();
- final String wperm = getWritePermission();
final int pid = Binder.getCallingPid();
- if (mExported && (wperm == null
- || context.checkPermission(wperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED)) {
- return true;
+ final int uid = Binder.getCallingUid();
+ String missingPerm = null;
+
+ if (uid == mMyUid) {
+ return;
}
-
- PathPermission[] pps = getPathPermissions();
- if (pps != null) {
- final String path = uri.getPath();
- int i = pps.length;
- while (i > 0) {
- i--;
- final PathPermission pp = pps[i];
- final String ppwperm = pp.getWritePermission();
- if (ppwperm != null && pp.match(path)) {
- if (context.checkPermission(ppwperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
+
+ if (mExported) {
+ final String componentPerm = getWritePermission();
+ if (componentPerm != null) {
+ if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ } else {
+ missingPerm = componentPerm;
+ }
+ }
+
+ // track if unprotected write is allowed; any denied
+ // <path-permission> below removes this ability
+ boolean allowDefaultWrite = (componentPerm == null);
+
+ final PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ for (PathPermission pp : pps) {
+ final String pathPerm = pp.getWritePermission();
+ if (pathPerm != null && pp.match(path)) {
+ if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ } else {
+ // any denied <path-permission> means we lose
+ // default <provider> access.
+ allowDefaultWrite = false;
+ missingPerm = pathPerm;
+ }
}
}
}
+
+ // if we passed <path-permission> checks above, and no default
+ // <provider> permission, then allow access.
+ if (allowDefaultWrite) return;
}
-
- if (context.checkUriPermission(uri, pid, uid,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
-
- return false;
- }
-
- private void enforceWritePermission(Uri uri) {
- if (hasWritePermission(uri)) {
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
return;
}
-
- String msg = "Permission Denial: writing "
- + ContentProvider.this.getClass().getName()
- + " uri " + uri + " from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + getWritePermission();
- throw new SecurityException(msg);
+
+ final String failReason = mExported
+ ? " requires " + missingPerm + ", or grantUriPermission()"
+ : " requires the provider be exported, or grantUriPermission()";
+ throw new SecurityException("Permission Denial: writing "
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid + failReason);
}
}
-
/**
* Retrieves the Context this provider is running in. Only available once
* {@link #onCreate} has been called -- this will return null in the
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index eb83dbc..4b31552 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.res.AssetFileDescriptor;
+import android.database.BulkCursorDescriptor;
import android.database.BulkCursorNative;
import android.database.BulkCursorToCursorAdaptor;
import android.database.Cursor;
@@ -113,20 +114,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
if (cursor != null) {
CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
cursor, observer, getProviderName());
- final IBinder binder = adaptor.asBinder();
- final int count = adaptor.count();
- final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex(
- adaptor.getColumnNames());
- final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls();
+ BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
reply.writeNoException();
- reply.writeStrongBinder(binder);
- reply.writeInt(count);
- reply.writeInt(index);
- reply.writeInt(wantsAllOnMoveCalls ? 1 : 0);
+ reply.writeInt(1);
+ d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeNoException();
- reply.writeStrongBinder(null);
+ reply.writeInt(0);
}
return true;
@@ -369,12 +364,9 @@ final class ContentProviderProxy implements IContentProvider
DatabaseUtils.readExceptionFromParcel(reply);
- IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder());
- if (bulkCursor != null) {
- int rowCount = reply.readInt();
- int idColumnPosition = reply.readInt();
- boolean wantsAllOnMoveCalls = reply.readInt() != 0;
- adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls);
+ if (reply.readInt() != 0) {
+ BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
+ adaptor.initialize(d);
} else {
adaptor.close();
adaptor = null;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0e9e256..741a6e9 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -818,6 +818,19 @@ public abstract class Context {
public abstract void clearWallpaper() throws IOException;
/**
+ * Same as {@link #startActivity(Intent, Bundle)} with no options
+ * specified.
+ *
+ * @param intent The description of the activity to start.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see {@link #startActivity(Intent, Bundle)}
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivity(Intent intent);
+
+ /**
* Launch a new activity. You will not receive any information about when
* the activity exits.
*
@@ -832,12 +845,30 @@ public abstract class Context {
* if there was no Activity found to run the given Intent.
*
* @param intent The description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
*
* @throws ActivityNotFoundException
*
+ * @see {@link #startActivity(Intent)}
* @see PackageManager#resolveActivity
*/
- public abstract void startActivity(Intent intent);
+ public abstract void startActivity(Intent intent, Bundle options);
+
+ /**
+ * Same as {@link #startActivities(Intent[], Bundle)} with no options
+ * specified.
+ *
+ * @param intents An array of Intents to be started.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see {@link #startActivities(Intent[], Bundle)}
+ * @see PackageManager#resolveActivity
+ */
+ public abstract void startActivities(Intent[] intents);
/**
* Launch multiple new activities. This is generally the same as calling
@@ -854,15 +885,39 @@ public abstract class Context {
* list may be on it, some not), so you probably want to avoid such situations.
*
* @param intents An array of Intents to be started.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
*
* @throws ActivityNotFoundException
*
+ * @see {@link #startActivities(Intent[])}
* @see PackageManager#resolveActivity
*/
- public abstract void startActivities(Intent[] intents);
+ public abstract void startActivities(Intent[] intents, Bundle options);
+
+ /**
+ * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+ * with no options specified.
+ *
+ * @param intent The IntentSender to launch.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in
+ * <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ *
+ * @see #startActivity(Intent)
+ * @see #startIntentSender(IntentSender, Intent, int, int, int, Bundle)
+ */
+ public abstract void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException;
/**
- * Like {@link #startActivity(Intent)}, but taking a IntentSender
+ * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
* to start. If the IntentSender is for an activity, that activity will be started
* as if you had called the regular {@link #startActivity(Intent)}
* here; otherwise, its associated action will be executed (such as
@@ -877,10 +932,18 @@ public abstract class Context {
* @param flagsValues Desired values for any bits set in
* <var>flagsMask</var>
* @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details. If options
+ * have also been supplied by the IntentSender, options given here will
+ * override any that conflict with those given by the IntentSender.
+ *
+ * @see #startActivity(Intent, Bundle)
+ * @see #startIntentSender(IntentSender, Intent, int, int, int)
*/
public abstract void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
- throws IntentSender.SendIntentException;
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException;
/**
* Broadcast the given intent to all interested BroadcastReceivers. This
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 5ba9dcc..6b950e0 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -277,17 +277,35 @@ public class ContextWrapper extends Context {
}
@Override
+ public void startActivity(Intent intent, Bundle options) {
+ mBase.startActivity(intent, options);
+ }
+
+ @Override
public void startActivities(Intent[] intents) {
mBase.startActivities(intents);
}
@Override
+ public void startActivities(Intent[] intents, Bundle options) {
+ mBase.startActivities(intents, options);
+ }
+
+ @Override
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
mBase.startIntentSender(intent, fillInIntent, flagsMask,
flagsValues, extraFlags);
}
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ mBase.startIntentSender(intent, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
@Override
public void sendBroadcast(Intent intent) {
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 7bb9866..9c81c9e 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -1171,7 +1171,7 @@ public class SyncStorageEngine extends Handler {
syncs = new ArrayList<SyncInfo>();
mCurrentSyncs.put(userId, syncs);
}
- return new ArrayList<SyncInfo>(syncs);
+ return syncs;
}
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 95b6fee..9bd1940 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -370,4 +370,7 @@ interface IPackageManager {
boolean isFirstBoot();
List<UserInfo> getUsers();
+
+ void setPermissionEnforcement(String permission, int enforcement);
+ int getPermissionEnforcement(String permission);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 544bd9c..55426b8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1090,6 +1090,11 @@ public abstract class PackageManager {
public static final String EXTRA_VERIFICATION_INSTALL_FLAGS
= "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
+ /** {@hide} */
+ public static final int ENFORCEMENT_DEFAULT = 0;
+ /** {@hide} */
+ public static final int ENFORCEMENT_YES = 1;
+
/**
* Retrieve overall information about an application package that is
* installed on the system.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e88ee02..207f077 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -89,11 +89,25 @@ public class PackageParser {
this.fileVersion = fileVersion;
}
}
-
+
+ /** @hide */
+ public static class SplitPermissionInfo {
+ public final String rootPerm;
+ public final String[] newPerms;
+
+ public SplitPermissionInfo(String rootPerm, String[] newPerms) {
+ this.rootPerm = rootPerm;
+ this.newPerms = newPerms;
+ }
+ }
+
/**
* List of new permissions that have been added since 1.0.
* NOTE: These must be declared in SDK version order, with permissions
* added to older SDKs appearing before those added to newer SDKs.
+ * If sdkVersion is 0, then this is not a permission that we want to
+ * automatically add to older apps, but we do want to allow it to be
+ * granted during a platform update.
* @hide
*/
public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] =
@@ -104,6 +118,17 @@ public class PackageParser {
android.os.Build.VERSION_CODES.DONUT, 0)
};
+ /**
+ * List of permissions that have been split into more granular or dependent
+ * permissions.
+ * @hide
+ */
+ public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] =
+ new PackageParser.SplitPermissionInfo[] {
+ new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE })
+ };
+
private String mArchiveSourcePath;
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
@@ -1245,7 +1270,23 @@ public class PackageParser {
if (implicitPerms != null) {
Slog.i(TAG, implicitPerms.toString());
}
-
+
+ final int NS = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int is=0; is<NS; is++) {
+ final PackageParser.SplitPermissionInfo spi
+ = PackageParser.SPLIT_PERMISSIONS[is];
+ if (!pkg.requestedPermissions.contains(spi.rootPerm)) {
+ break;
+ }
+ for (int in=0; in<spi.newPerms.length; in++) {
+ final String perm = spi.newPerms[in];
+ if (!pkg.requestedPermissions.contains(perm)) {
+ pkg.requestedPermissions.add(perm);
+ pkg.requestedPermissionsRequired.add(Boolean.TRUE);
+ }
+ }
+ }
+
if (supportsSmallScreens < 0 || (supportsSmallScreens > 0
&& pkg.applicationInfo.targetSdkVersion
>= android.os.Build.VERSION_CODES.DONUT)) {
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b28ed8d..dd6692c 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -33,10 +33,39 @@ import java.util.Map;
public abstract class AbstractCursor implements CrossProcessCursor {
private static final String TAG = "Cursor";
- DataSetObservable mDataSetObservable = new DataSetObservable();
- ContentObservable mContentObservable = new ContentObservable();
+ /**
+ * @deprecated This is never updated by this class and should not be used
+ */
+ @Deprecated
+ protected HashMap<Long, Map<String, Object>> mUpdatedRows;
+
+ protected int mPos;
+
+ /**
+ * This must be set to the index of the row ID column by any
+ * subclass that wishes to support updates.
+ */
+ protected int mRowIdColumnIndex;
+
+ /**
+ * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
+ * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
+ * pointing at.
+ */
+ protected Long mCurrentRowID;
+
+ protected boolean mClosed;
+ protected ContentResolver mContentResolver;
+ private Uri mNotifyUri;
+
+ private final Object mSelfObserverLock = new Object();
+ private ContentObserver mSelfObserver;
+ private boolean mSelfObserverRegistered;
- Bundle mExtras = Bundle.EMPTY;
+ private DataSetObservable mDataSetObservable = new DataSetObservable();
+ private ContentObservable mContentObservable = new ContentObservable();
+
+ private Bundle mExtras = Bundle.EMPTY;
/* -------------------------------------------------------- */
/* These need to be implemented by subclasses */
@@ -415,30 +444,4 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
}
-
- /**
- * @deprecated This is never updated by this class and should not be used
- */
- @Deprecated
- protected HashMap<Long, Map<String, Object>> mUpdatedRows;
-
- /**
- * This must be set to the index of the row ID column by any
- * subclass that wishes to support updates.
- */
- protected int mRowIdColumnIndex;
-
- protected int mPos;
- /**
- * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
- * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
- * pointing at.
- */
- protected Long mCurrentRowID;
- protected ContentResolver mContentResolver;
- protected boolean mClosed = false;
- private Uri mNotifyUri;
- private ContentObserver mSelfObserver;
- final private Object mSelfObserverLock = new Object();
- private boolean mSelfObserverRegistered;
}
diff --git a/core/java/android/database/BulkCursorDescriptor.java b/core/java/android/database/BulkCursorDescriptor.java
new file mode 100644
index 0000000..c1e5e63
--- /dev/null
+++ b/core/java/android/database/BulkCursorDescriptor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the properties of a {@link CursorToBulkCursorAdaptor} that are
+ * needed to initialize its {@link BulkCursorToCursorAdaptor} counterpart on the client's end.
+ *
+ * {@hide}
+ */
+public final class BulkCursorDescriptor implements Parcelable {
+ public static final Parcelable.Creator<BulkCursorDescriptor> CREATOR =
+ new Parcelable.Creator<BulkCursorDescriptor>() {
+ @Override
+ public BulkCursorDescriptor createFromParcel(Parcel in) {
+ BulkCursorDescriptor d = new BulkCursorDescriptor();
+ d.readFromParcel(in);
+ return d;
+ }
+
+ @Override
+ public BulkCursorDescriptor[] newArray(int size) {
+ return new BulkCursorDescriptor[size];
+ }
+ };
+
+ public IBulkCursor cursor;
+ public String[] columnNames;
+ public boolean wantsAllOnMoveCalls;
+ public int count;
+ public CursorWindow window;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(cursor.asBinder());
+ out.writeStringArray(columnNames);
+ out.writeInt(wantsAllOnMoveCalls ? 1 : 0);
+ out.writeInt(count);
+ if (window != null) {
+ out.writeInt(1);
+ window.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ cursor = BulkCursorNative.asInterface(in.readStrongBinder());
+ columnNames = in.readStringArray();
+ wantsAllOnMoveCalls = in.readInt() != 0;
+ count = in.readInt();
+ if (in.readInt() != 0) {
+ window = CursorWindow.CREATOR.createFromParcel(in);
+ }
+ }
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 67cf0f8..d3c11e7 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -72,26 +72,6 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor
return true;
}
- case COUNT_TRANSACTION: {
- data.enforceInterface(IBulkCursor.descriptor);
- int count = count();
- reply.writeNoException();
- reply.writeInt(count);
- return true;
- }
-
- case GET_COLUMN_NAMES_TRANSACTION: {
- data.enforceInterface(IBulkCursor.descriptor);
- String[] columnNames = getColumnNames();
- reply.writeNoException();
- reply.writeInt(columnNames.length);
- int length = columnNames.length;
- for (int i = 0; i < length; i++) {
- reply.writeString(columnNames[i]);
- }
- return true;
- }
-
case DEACTIVATE_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
deactivate();
@@ -125,14 +105,6 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor
return true;
}
- case WANTS_ON_MOVE_TRANSACTION: {
- data.enforceInterface(IBulkCursor.descriptor);
- boolean result = getWantsAllOnMoveCalls();
- reply.writeNoException();
- reply.writeInt(result ? 1 : 0);
- return true;
- }
-
case GET_EXTRAS_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
Bundle extras = getExtras();
@@ -217,52 +189,6 @@ final class BulkCursorProxy implements IBulkCursor {
}
}
- public int count() throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- try {
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- int count;
- if (result == false) {
- count = -1;
- } else {
- count = reply.readInt();
- }
- return count;
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
- public String[] getColumnNames() throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- try {
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- String[] columnNames = null;
- int numColumns = reply.readInt();
- columnNames = new String[numColumns];
- for (int i = 0; i < numColumns; i++) {
- columnNames[i] = reply.readString();
- }
- return columnNames;
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
public void deactivate() throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -317,23 +243,6 @@ final class BulkCursorProxy implements IBulkCursor {
}
}
- public boolean getWantsAllOnMoveCalls() throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- try {
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- int result = reply.readInt();
- return result != 0;
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
public Bundle getExtras() throws RemoteException {
if (mExtras == null) {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 885046b..98c7043 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -30,34 +30,23 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
private SelfContentObserver mObserverBridge = new SelfContentObserver(this);
private IBulkCursor mBulkCursor;
- private int mCount;
private String[] mColumns;
private boolean mWantsAllOnMoveCalls;
+ private int mCount;
/**
* Initializes the adaptor.
* Must be called before first use.
*/
- public void initialize(IBulkCursor bulkCursor, int count, int idIndex,
- boolean wantsAllOnMoveCalls) {
- mBulkCursor = bulkCursor;
- mColumns = null; // lazily retrieved
- mCount = count;
- mRowIdColumnIndex = idIndex;
- mWantsAllOnMoveCalls = wantsAllOnMoveCalls;
- }
-
- /**
- * Returns column index of "_id" column, or -1 if not found.
- */
- public static int findRowIdColumnIndex(String[] columnNames) {
- int length = columnNames.length;
- for (int i = 0; i < length; i++) {
- if (columnNames[i].equals("_id")) {
- return i;
- }
+ public void initialize(BulkCursorDescriptor d) {
+ mBulkCursor = d.cursor;
+ mColumns = d.columnNames;
+ mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
+ mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
+ mCount = d.count;
+ if (d.window != null) {
+ setWindow(d.window);
}
- return -1;
}
/**
@@ -169,14 +158,6 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
public String[] getColumnNames() {
throwIfCursorIsClosed();
- if (mColumns == null) {
- try {
- mColumns = mBulkCursor.getColumnNames();
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to fetch column names because the remote process is dead");
- return null;
- }
- }
return mColumns;
}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 167278a..525be96 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -132,6 +132,25 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
}
+ public BulkCursorDescriptor getBulkCursorDescriptor() {
+ synchronized (mLock) {
+ throwIfCursorIsClosed();
+
+ BulkCursorDescriptor d = new BulkCursorDescriptor();
+ d.cursor = this;
+ d.columnNames = mCursor.getColumnNames();
+ d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls();
+ d.count = mCursor.getCount();
+ d.window = mCursor.getWindow();
+ if (d.window != null) {
+ // Acquire a reference to the window because its reference count will be
+ // decremented when it is returned as part of the binder call reply parcel.
+ d.window.acquireReference();
+ }
+ return d;
+ }
+ }
+
@Override
public CursorWindow getWindow(int position) {
synchronized (mLock) {
@@ -157,10 +176,9 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
mCursor.fillWindow(position, window);
}
- // Acquire a reference before returning from this RPC.
- // The Binder proxy will decrement the reference count again as part of writing
- // the CursorWindow to the reply parcel as a return value.
if (window != null) {
+ // Acquire a reference to the window because its reference count will be
+ // decremented when it is returned as part of the binder call reply parcel.
window.acquireReference();
}
return window;
@@ -177,24 +195,6 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
@Override
- public int count() {
- synchronized (mLock) {
- throwIfCursorIsClosed();
-
- return mCursor.getCount();
- }
- }
-
- @Override
- public String[] getColumnNames() {
- synchronized (mLock) {
- throwIfCursorIsClosed();
-
- return mCursor.getColumnNames();
- }
- }
-
- @Override
public void deactivate() {
synchronized (mLock) {
if (mCursor != null) {
@@ -237,15 +237,6 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
}
- @Override
- public boolean getWantsAllOnMoveCalls() {
- synchronized (mLock) {
- throwIfCursorIsClosed();
-
- return mCursor.getWantsAllOnMoveCalls();
- }
- }
-
/**
* Create a ContentObserver from the observer and register it as an observer on the
* underlying cursor.
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 99d260e..40a54cf 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -1386,4 +1386,18 @@ public class DatabaseUtils {
System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
return result;
}
+
+ /**
+ * Returns column index of "_id" column, or -1 if not found.
+ * @hide
+ */
+ public static int findRowIdColumnIndex(String[] columnNames) {
+ int length = columnNames.length;
+ for (int i = 0; i < length; i++) {
+ if (columnNames[i].equals("_id")) {
+ return i;
+ }
+ }
+ return -1;
+ }
}
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
index 0f4500a..b551116 100644
--- a/core/java/android/database/IBulkCursor.java
+++ b/core/java/android/database/IBulkCursor.java
@@ -43,29 +43,12 @@ public interface IBulkCursor extends IInterface {
*/
public void onMove(int position) throws RemoteException;
- /**
- * Returns the number of rows in the cursor.
- *
- * @return the number of rows in the cursor.
- */
- public int count() throws RemoteException;
-
- /**
- * Returns a string array holding the names of all of the columns in the
- * cursor in the order in which they were listed in the result.
- *
- * @return the names of the columns returned in this query.
- */
- public String[] getColumnNames() throws RemoteException;
-
public void deactivate() throws RemoteException;
public void close() throws RemoteException;
public int requery(IContentObserver observer) throws RemoteException;
- boolean getWantsAllOnMoveCalls() throws RemoteException;
-
Bundle getExtras() throws RemoteException;
Bundle respond(Bundle extras) throws RemoteException;
@@ -74,13 +57,10 @@ public interface IBulkCursor extends IInterface {
static final String descriptor = "android.content.IBulkCursor";
static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
- static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
- static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
- static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
- static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
- static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7;
- static final int WANTS_ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 8;
- static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
- static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
- static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
+ static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+ static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
+ static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
+ static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
+ static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
+ static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
}
diff --git a/core/java/android/database/SQLException.java b/core/java/android/database/SQLException.java
index 0386af0..3402026 100644
--- a/core/java/android/database/SQLException.java
+++ b/core/java/android/database/SQLException.java
@@ -19,12 +19,15 @@ package android.database;
/**
* An exception that indicates there was an error with SQL parsing or execution.
*/
-public class SQLException extends RuntimeException
-{
- public SQLException() {}
+public class SQLException extends RuntimeException {
+ public SQLException() {
+ }
- public SQLException(String error)
- {
+ public SQLException(String error) {
super(error);
}
+
+ public SQLException(String error, Throwable cause) {
+ super(error, cause);
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 0db3e4f..e2c222b 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -99,6 +99,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private final SQLiteDatabaseConfiguration mConfiguration;
private final int mConnectionId;
private final boolean mIsPrimaryConnection;
+ private final boolean mIsReadOnlyConnection;
private final PreparedStatementCache mPreparedStatementCache;
private PreparedStatement mPreparedStatementPool;
@@ -111,7 +112,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private boolean mOnlyAllowReadOnlyOperations;
// The number of times attachCancellationSignal has been called.
- // Because SQLite statement execution can be re-entrant, we keep track of how many
+ // Because SQLite statement execution can be reentrant, we keep track of how many
// times we have attempted to attach a cancellation signal to the connection so that
// we can ensure that we detach the signal at the right time.
private int mCancellationSignalAttachCount;
@@ -121,7 +122,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private static native void nativeClose(int connectionPtr);
private static native void nativeRegisterCustomFunction(int connectionPtr,
SQLiteCustomFunction function);
- private static native void nativeSetLocale(int connectionPtr, String locale);
+ private static native void nativeRegisterLocalizedCollators(int connectionPtr, String locale);
private static native int nativePrepareStatement(int connectionPtr, String sql);
private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr);
private static native int nativeGetParameterCount(int connectionPtr, int statementPtr);
@@ -163,6 +164,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
+ mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
mCloseGuard.open("close");
@@ -237,45 +239,102 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
}
private void setPageSize() {
- if (!mConfiguration.isInMemoryDb()) {
- execute("PRAGMA page_size=" + SQLiteGlobal.getDefaultPageSize(), null, null);
+ if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
+ final long newValue = SQLiteGlobal.getDefaultPageSize();
+ long value = executeForLong("PRAGMA page_size", null, null);
+ if (value != newValue) {
+ execute("PRAGMA page_size=" + newValue, null, null);
+ }
}
}
private void setAutoCheckpointInterval() {
- if (!mConfiguration.isInMemoryDb()) {
- executeForLong("PRAGMA wal_autocheckpoint=" + SQLiteGlobal.getWALAutoCheckpoint(),
- null, null);
+ if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
+ final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
+ long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
+ if (value != newValue) {
+ executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
+ }
}
}
private void setJournalSizeLimit() {
- if (!mConfiguration.isInMemoryDb()) {
- executeForLong("PRAGMA journal_size_limit=" + SQLiteGlobal.getJournalSizeLimit(),
- null, null);
+ if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
+ final long newValue = SQLiteGlobal.getJournalSizeLimit();
+ long value = executeForLong("PRAGMA journal_size_limit", null, null);
+ if (value != newValue) {
+ executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
+ }
}
}
private void setSyncModeFromConfiguration() {
- if (!mConfiguration.isInMemoryDb()) {
- execute("PRAGMA synchronous=" + mConfiguration.syncMode, null, null);
+ if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
+ final String newValue = mConfiguration.syncMode;
+ String value = executeForString("PRAGMA synchronous", null, null);
+ if (!value.equalsIgnoreCase(newValue)) {
+ execute("PRAGMA synchronous=" + newValue, null, null);
+ }
}
}
private void setJournalModeFromConfiguration() {
- if (!mConfiguration.isInMemoryDb()) {
- String result = executeForString("PRAGMA journal_mode=" + mConfiguration.journalMode,
- null, null);
- if (!result.equalsIgnoreCase(mConfiguration.journalMode)) {
- Log.e(TAG, "setting journal_mode to " + mConfiguration.journalMode
- + " failed for db: " + mConfiguration.label
- + " (on pragma set journal_mode, sqlite returned:" + result);
+ if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
+ final String newValue = mConfiguration.journalMode;
+ String value = executeForString("PRAGMA journal_mode", null, null);
+ if (!value.equalsIgnoreCase(newValue)) {
+ value = executeForString("PRAGMA journal_mode=" + newValue, null, null);
+ if (!value.equalsIgnoreCase(newValue)) {
+ Log.e(TAG, "setting journal_mode to " + newValue
+ + " failed for db: " + mConfiguration.label
+ + " (on pragma set journal_mode, sqlite returned:" + value);
+ }
}
}
}
private void setLocaleFromConfiguration() {
- nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString());
+ if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
+ return;
+ }
+
+ // Register the localized collators.
+ final String newLocale = mConfiguration.locale.toString();
+ nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
+
+ // If the database is read-only, we cannot modify the android metadata table
+ // or existing indexes.
+ if (mIsReadOnlyConnection) {
+ return;
+ }
+
+ try {
+ // Ensure the android metadata table exists.
+ execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
+
+ // Check whether the locale was actually changed.
+ final String oldLocale = executeForString("SELECT locale FROM android_metadata "
+ + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
+ if (oldLocale != null && oldLocale.equals(newLocale)) {
+ return;
+ }
+
+ // Go ahead and update the indexes using the new locale.
+ execute("BEGIN", null, null);
+ boolean success = false;
+ try {
+ execute("DELETE FROM android_metadata", null, null);
+ execute("INSERT INTO android_metadata (locale) VALUES(?)",
+ new Object[] { newLocale }, null);
+ execute("REINDEX LOCALIZED", null, null);
+ success = true;
+ } finally {
+ execute(success ? "COMMIT" : "ROLLBACK", null, null);
+ }
+ } catch (RuntimeException ex) {
+ throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
+ + "' to '" + newLocale + "'.", ex);
+ }
}
// Called by SQLiteConnectionPool only.
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 82bb23e..b29897e 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -105,12 +105,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
mQuery = query;
mColumns = query.getColumnNames();
- for (int i = 0; i < mColumns.length; i++) {
- // Make note of the row ID column index for quick access to it
- if ("_id".equals(mColumns[i])) {
- mRowIdColumnIndex = i;
- }
- }
+ mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index d41b484..bf32ea7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1718,7 +1718,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
/**
* Sets the locale for this database. Does nothing if this database has
- * the NO_LOCALIZED_COLLATORS flag set or was opened read only.
+ * the {@link #NO_LOCALIZED_COLLATORS} flag set or was opened read only.
*
* @param locale The new locale.
*
diff --git a/core/java/android/database/sqlite/SQLiteException.java b/core/java/android/database/sqlite/SQLiteException.java
index 3a97bfb..a1d9c9f 100644
--- a/core/java/android/database/sqlite/SQLiteException.java
+++ b/core/java/android/database/sqlite/SQLiteException.java
@@ -22,9 +22,14 @@ import android.database.SQLException;
* A SQLite exception that indicates there was an error with SQL parsing or execution.
*/
public class SQLiteException extends SQLException {
- public SQLiteException() {}
+ public SQLiteException() {
+ }
public SQLiteException(String error) {
super(error);
}
+
+ public SQLiteException(String error, Throwable cause) {
+ super(error, cause);
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 94a23cb..e9b06c6 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -64,20 +64,20 @@ public abstract class SQLiteProgram extends SQLiteClosable {
break;
}
+ if (bindArgs != null && bindArgs.length > mNumParameters) {
+ throw new IllegalArgumentException("Too many bind arguments. "
+ + bindArgs.length + " arguments were provided but the statement needs "
+ + mNumParameters + " arguments.");
+ }
+
if (mNumParameters != 0) {
mBindArgs = new Object[mNumParameters];
+ if (bindArgs != null) {
+ System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
+ }
} else {
mBindArgs = null;
}
-
- if (bindArgs != null) {
- if (bindArgs.length > mNumParameters) {
- throw new IllegalArgumentException("Too many bind arguments. "
- + bindArgs.length + " arguments were provided but the statement needs "
- + mNumParameters + " arguments.");
- }
- System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
- }
}
final SQLiteDatabase getDatabase() {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 2775c7b..83b6986 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -236,6 +236,50 @@ public class Camera {
/**
* Creates a new Camera object to access a particular hardware camera.
*
+ * <p>When <code>force</code> is set to false, this will throw an exception
+ * if the same camera is already opened by other clients. If true, the other
+ * client will be disconnected from the camera they opened. If the device
+ * can only support one camera running at a time, all camera-using clients
+ * will be disconnected from their cameras.
+ *
+ * <p>A camera being held by an application can be taken away by other
+ * applications at any time. Before the camera is taken, applications will
+ * get {@link #CAMERA_ERROR_RELEASED} and have some time to clean up. Apps
+ * receiving this callback must immediately stop video recording and then
+ * call {@link #release()} on their camera object. Otherwise, it will be
+ * released by the frameworks in a short time. After receiving
+ * CAMERA_ERROR_RELEASED, apps should not call any method except <code>
+ * release</code> and {@link #isReleased()}. After a camera is taken away,
+ * all methods will throw exceptions except <code>isReleased</code> and
+ * <code>release</code>. Apps can use <code>isReleased</code> to see if the
+ * camera has been taken away. If the camera is taken away, the apps can
+ * silently finish themselves or show a dialog.
+ *
+ * <p>Applications with android.permission.KEEP_CAMERA can request to keep
+ * the camera. That is, the camera will not be taken by other applications
+ * while it is opened. The permission can only be obtained by trusted
+ * platform applications, such as those implementing lock screen security
+ * features.
+ *
+ * @param cameraId the hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1.
+ * @param force true to take the ownership from the existing client if the
+ * camera has been opened by other clients.
+ * @param keep true if the applications do not want other apps to take the
+ * camera. Only the apps with android.permission.KEEP_CAMERA can keep
+ * the camera.
+ * @return a new Camera object, connected, locked and ready for use.
+ * @hide
+ */
+ public static Camera open(int cameraId, boolean force, boolean keep) {
+ return new Camera(cameraId, force, keep);
+ }
+
+ /**
+ * Creates a new Camera object to access a particular hardware camera. If
+ * the same camera is opened by other applications, this will throw a
+ * RuntimeException.
+ *
* <p>You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
*
@@ -255,13 +299,13 @@ public class Camera {
* @param cameraId the hardware camera to access, between 0 and
* {@link #getNumberOfCameras()}-1.
* @return a new Camera object, connected, locked and ready for use.
- * @throws RuntimeException if connection to the camera service fails (for
- * example, if the camera is in use by another process or device policy
- * manager has disabled the camera).
+ * @throws RuntimeException if opening the camera fails (for example, if the
+ * camera is in use by another process or device policy manager has
+ * disabled the camera).
* @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
*/
public static Camera open(int cameraId) {
- return new Camera(cameraId);
+ return new Camera(cameraId, false, false);
}
/**
@@ -276,13 +320,13 @@ public class Camera {
for (int i = 0; i < numberOfCameras; i++) {
getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- return new Camera(i);
+ return new Camera(i, false, false);
}
}
return null;
}
- Camera(int cameraId) {
+ Camera(int cameraId, boolean force, boolean keep) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -299,7 +343,7 @@ public class Camera {
mEventHandler = null;
}
- native_setup(new WeakReference<Camera>(this), cameraId);
+ native_setup(new WeakReference<Camera>(this), cameraId, force, keep);
}
/**
@@ -312,7 +356,8 @@ public class Camera {
release();
}
- private native final void native_setup(Object camera_this, int cameraId);
+ private native final void native_setup(Object camera_this, int cameraId,
+ boolean force, boolean keep);
private native final void native_release();
@@ -327,6 +372,18 @@ public class Camera {
}
/**
+ * Whether the camera is released. When any camera method throws an
+ * exception, applications can use this to check whether the camera has been
+ * taken by other clients. If true, it means other clients have taken the
+ * camera. The applications can silently finish themselves or show a dialog.
+ *
+ * @return whether the camera is released.
+ * @see #open(int, boolean, boolean)
+ * @hide
+ */
+ public native final boolean isReleased();
+
+ /**
* Unlocks the camera to allow another process to access it.
* Normally, the camera is locked to the process with an active Camera
* object until {@link #release()} is called. To allow rapid handoff
@@ -437,6 +494,13 @@ public class Camera {
* instances of the same camera, or across multiple runs of the same
* program.
*
+ * <p>If you are using the preview data to create video or still images,
+ * strongly consider using {@link android.media.MediaActionSound} to
+ * properly indicate image capture or recording start/stop to the user.</p>
+ *
+ * @see android.media.MediaActionSound
+ * @see android.graphics.SurfaceTexture
+ * @see android.view.TextureView
* @param surfaceTexture the {@link SurfaceTexture} to which the preview
* images are to be sent or null to remove the current preview surface
* texture
@@ -512,13 +576,19 @@ public class Camera {
public native final boolean previewEnabled();
/**
- * Installs a callback to be invoked for every preview frame in addition
+ * <p>Installs a callback to be invoked for every preview frame in addition
* to displaying them on the screen. The callback will be repeatedly called
* for as long as preview is active. This method can be called at any time,
- * even while preview is live. Any other preview callbacks are overridden.
+ * even while preview is live. Any other preview callbacks are
+ * overridden.</p>
+ *
+ * <p>If you are using the preview data to create video or still images,
+ * strongly consider using {@link android.media.MediaActionSound} to
+ * properly indicate image capture or recording start/stop to the user.</p>
*
* @param cb a callback object that receives a copy of each preview frame,
* or null to stop receiving callbacks.
+ * @see android.media.MediaActionSound
*/
public final void setPreviewCallback(PreviewCallback cb) {
mPreviewCallback = cb;
@@ -530,13 +600,18 @@ public class Camera {
}
/**
- * Installs a callback to be invoked for the next preview frame in addition
- * to displaying it on the screen. After one invocation, the callback is
- * cleared. This method can be called any time, even when preview is live.
- * Any other preview callbacks are overridden.
+ * <p>Installs a callback to be invoked for the next preview frame in
+ * addition to displaying it on the screen. After one invocation, the
+ * callback is cleared. This method can be called any time, even when
+ * preview is live. Any other preview callbacks are overridden.</p>
+ *
+ * <p>If you are using the preview data to create video or still images,
+ * strongly consider using {@link android.media.MediaActionSound} to
+ * properly indicate image capture or recording start/stop to the user.</p>
*
* @param cb a callback object that receives a copy of the next preview frame,
* or null to stop receiving callbacks.
+ * @see android.media.MediaActionSound
*/
public final void setOneShotPreviewCallback(PreviewCallback cb) {
mPreviewCallback = cb;
@@ -548,24 +623,30 @@ public class Camera {
private native final void setHasPreviewCallback(boolean installed, boolean manualBuffer);
/**
- * Installs a callback to be invoked for every preview frame, using buffers
- * supplied with {@link #addCallbackBuffer(byte[])}, in addition to
+ * <p>Installs a callback to be invoked for every preview frame, using
+ * buffers supplied with {@link #addCallbackBuffer(byte[])}, in addition to
* displaying them on the screen. The callback will be repeatedly called
- * for as long as preview is active and buffers are available.
- * Any other preview callbacks are overridden.
+ * for as long as preview is active and buffers are available. Any other
+ * preview callbacks are overridden.</p>
*
* <p>The purpose of this method is to improve preview efficiency and frame
* rate by allowing preview frame memory reuse. You must call
* {@link #addCallbackBuffer(byte[])} at some point -- before or after
- * calling this method -- or no callbacks will received.
+ * calling this method -- or no callbacks will received.</p>
*
- * The buffer queue will be cleared if this method is called with a null
+ * <p>The buffer queue will be cleared if this method is called with a null
* callback, {@link #setPreviewCallback(Camera.PreviewCallback)} is called,
- * or {@link #setOneShotPreviewCallback(Camera.PreviewCallback)} is called.
+ * or {@link #setOneShotPreviewCallback(Camera.PreviewCallback)} is
+ * called.</p>
+ *
+ * <p>If you are using the preview data to create video or still images,
+ * strongly consider using {@link android.media.MediaActionSound} to
+ * properly indicate image capture or recording start/stop to the user.</p>
*
* @param cb a callback object that receives a copy of the preview frame,
* or null to stop receiving callbacks and clear the buffer queue.
* @see #addCallbackBuffer(byte[])
+ * @see android.media.MediaActionSound
*/
public final void setPreviewCallbackWithBuffer(PreviewCallback cb) {
mPreviewCallback = cb;
@@ -834,10 +915,15 @@ public class Camera {
* the focus position. Applications must call cancelAutoFocus to reset the
* focus.</p>
*
+ * <p>If autofocus is successful, consider using
+ * {@link android.media.MediaActionSound} to properly play back an autofocus
+ * success sound to the user.</p>
+ *
* @param cb the callback to run
* @see #cancelAutoFocus()
* @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean)
* @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean)
+ * @see android.media.MediaActionSound
*/
public final void autoFocus(AutoFocusCallback cb)
{
@@ -1293,6 +1379,17 @@ public class Camera {
public static final int CAMERA_ERROR_UNKNOWN = 1;
/**
+ * Camera was released because another client has opened the camera. The
+ * application should call {@link #release()} after getting this. The apps
+ * should not call any method except <code>release</code> and {@link #isReleased()}
+ * after this.
+ *
+ * @see Camera.ErrorCallback
+ * @hide
+ */
+ public static final int CAMERA_ERROR_RELEASED = 2;
+
+ /**
* Media server died. In this case, the application must release the
* Camera object and instantiate a new one.
* @see Camera.ErrorCallback
@@ -1907,12 +2004,12 @@ public class Camera {
* @param value the String value of the parameter
*/
public void set(String key, String value) {
- if (key.indexOf('=') != -1 || key.indexOf(';') != -1) {
- Log.e(TAG, "Key \"" + key + "\" contains invalid character (= or ;)");
+ if (key.indexOf('=') != -1 || key.indexOf(';') != -1 || key.indexOf(0) != -1) {
+ Log.e(TAG, "Key \"" + key + "\" contains invalid character (= or ; or \\0)");
return;
}
- if (value.indexOf('=') != -1 || value.indexOf(';') != -1) {
- Log.e(TAG, "Value \"" + value + "\" contains invalid character (= or ;)");
+ if (value.indexOf('=') != -1 || value.indexOf(';') != -1 || value.indexOf(0) != -1) {
+ Log.e(TAG, "Value \"" + value + "\" contains invalid character (= or ; or \\0)");
return;
}
diff --git a/core/java/android/hardware/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java
index 96fbe77..0369825 100644
--- a/core/java/android/hardware/GeomagneticField.java
+++ b/core/java/android/hardware/GeomagneticField.java
@@ -19,7 +19,7 @@ package android.hardware;
import java.util.GregorianCalendar;
/**
- * This class is used to estimated estimate magnetic field at a given point on
+ * Estimates magnetic field at a given point on
* Earth, and in particular, to compute the magnetic declination from true
* north.
*
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index 1a74abf..ee12989 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -31,6 +31,14 @@ import com.android.internal.util.Objects;
* @hide
*/
public class NetworkIdentity {
+ /**
+ * When enabled, combine all {@link #mSubType} together under
+ * {@link #SUBTYPE_COMBINED}.
+ */
+ public static final boolean COMBINE_SUBTYPE_ENABLED = true;
+
+ public static final int SUBTYPE_COMBINED = -1;
+
final int mType;
final int mSubType;
final String mSubscriberId;
@@ -38,7 +46,7 @@ public class NetworkIdentity {
public NetworkIdentity(int type, int subType, String subscriberId, boolean roaming) {
this.mType = type;
- this.mSubType = subType;
+ this.mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
this.mSubscriberId = subscriberId;
this.mRoaming = roaming;
}
@@ -52,9 +60,8 @@ public class NetworkIdentity {
public boolean equals(Object obj) {
if (obj instanceof NetworkIdentity) {
final NetworkIdentity ident = (NetworkIdentity) obj;
- return mType == ident.mType && mSubType == ident.mSubType
- && Objects.equal(mSubscriberId, ident.mSubscriberId)
- && mRoaming == ident.mRoaming;
+ return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
+ && Objects.equal(mSubscriberId, ident.mSubscriberId);
}
return false;
}
@@ -63,7 +70,9 @@ public class NetworkIdentity {
public String toString() {
final String typeName = ConnectivityManager.getNetworkTypeName(mType);
final String subTypeName;
- if (ConnectivityManager.isNetworkTypeMobile(mType)) {
+ if (COMBINE_SUBTYPE_ENABLED) {
+ subTypeName = "COMBINED";
+ } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
subTypeName = TelephonyManager.getNetworkTypeName(mSubType);
} else {
subTypeName = Integer.toString(mSubType);
@@ -130,5 +139,4 @@ public class NetworkIdentity {
}
return new NetworkIdentity(type, subType, subscriberId, roaming);
}
-
}
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 8ebfd8d..e1fbdcc 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
import static android.net.NetworkIdentity.scrubSubscriberId;
import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
@@ -199,6 +200,7 @@ public class NetworkTemplate implements Parcelable {
* Check if mobile network classified 3G or lower with matching IMSI.
*/
private boolean matchesMobile3gLower(NetworkIdentity ident) {
+ ensureSubtypeAvailable();
if (ident.mType == TYPE_WIMAX) {
return false;
} else if (matchesMobile(ident)) {
@@ -216,6 +218,7 @@ public class NetworkTemplate implements Parcelable {
* Check if mobile network classified 4G with matching IMSI.
*/
private boolean matchesMobile4g(NetworkIdentity ident) {
+ ensureSubtypeAvailable();
if (ident.mType == TYPE_WIMAX) {
// TODO: consider matching against WiMAX subscriber identity
return true;
@@ -268,6 +271,13 @@ public class NetworkTemplate implements Parcelable {
}
}
+ private static void ensureSubtypeAvailable() {
+ if (COMBINE_SUBTYPE_ENABLED) {
+ throw new IllegalArgumentException(
+ "Unable to enforce 3G_LOWER template on combined data.");
+ }
+ }
+
public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
public NetworkTemplate createFromParcel(Parcel in) {
return new NetworkTemplate(in);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 6139296..770bf1c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -98,6 +98,12 @@ public class Process {
public static final int SDCARD_RW_GID = 1015;
/**
+ * Defines the UID/GID for the group that controls VPN services.
+ * @hide
+ */
+ public static final int VPN_UID = 1016;
+
+ /**
* Defines the UID/GID for the NFC service process.
* @hide
*/
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 759be91..ce213fb 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -1201,7 +1201,7 @@ public final class StrictMode {
// throttled back to 60fps via SurfaceFlinger/View
// invalidates, _not_ by posting frame updates every 16
// milliseconds.
- threadHandler.get().post(new Runnable() {
+ threadHandler.get().postAtFrontOfQueue(new Runnable() {
public void run() {
long loopFinishTime = SystemClock.uptimeMillis();
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
new file mode 100644
index 0000000..af94a37
--- /dev/null
+++ b/core/java/android/os/Trace.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Writes trace events to the kernel trace buffer. These trace events can be
+ * collected using the "atrace" program for offline analysis.
+ *
+ * This tracing mechanism is independent of the method tracing mechanism
+ * offered by {@link Debug#startMethodTracing}. In particular, it enables
+ * tracing of events that occur across processes.
+ *
+ * @hide
+ */
+public final class Trace {
+ // These tags must be kept in sync with frameworks/native/include/utils/Trace.h.
+ public static final long TRACE_TAG_NEVER = 0;
+ public static final long TRACE_TAG_ALWAYS = 1L << 0;
+ public static final long TRACE_TAG_GRAPHICS = 1L << 1;
+ public static final long TRACE_TAG_INPUT = 1L << 2;
+ public static final long TRACE_TAG_VIEW = 1L << 3;
+
+ private static final long sEnabledTags = nativeGetEnabledTags();
+
+ private static native long nativeGetEnabledTags();
+ private static native void nativeTraceCounter(long tag, String name, int value);
+ private static native void nativeTraceBegin(long tag, String name);
+ private static native void nativeTraceEnd(long tag);
+
+ private Trace() {
+ }
+
+ /**
+ * Returns true if a trace tag is enabled.
+ *
+ * @param traceTag The trace tag to check.
+ * @return True if the trace tag is valid.
+ */
+ public static boolean isTagEnabled(long traceTag) {
+ return (sEnabledTags & traceTag) != 0;
+ }
+
+ /**
+ * Writes trace message to indicate the value of a given counter.
+ *
+ * @param traceTag The trace tag.
+ * @param counterName The counter name to appear in the trace.
+ * @param counterValue The counter value.
+ */
+ public static void traceCounter(long traceTag, String counterName, int counterValue) {
+ if ((sEnabledTags & traceTag) != 0) {
+ nativeTraceCounter(traceTag, counterName, counterValue);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given method has begun.
+ * Must be followed by a call to {@link #traceEnd} using the same tag.
+ *
+ * @param traceTag The trace tag.
+ * @param methodName The method name to appear in the trace.
+ */
+ public static void traceBegin(long traceTag, String methodName) {
+ if ((sEnabledTags & traceTag) != 0) {
+ nativeTraceBegin(traceTag, methodName);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that the current method has ended.
+ * Must be called exactly once for each call to {@link #traceBegin} using the same tag.
+ *
+ * @param traceTag The trace tag.
+ */
+ public static void traceEnd(long traceTag) {
+ if ((sEnabledTags & traceTag) != 0) {
+ nativeTraceEnd(traceTag);
+ }
+ }
+}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 0e6d07d..9612151 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -596,8 +596,8 @@ public final class Telephony {
* values:</p>
*
* <ul>
- * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
- * that make up the message.</li>
+ * <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+ * data. This is not an emergency alert, so ETWS and CMAS data will be null.</li>
* </ul>
*
* <p>The extra values can be extracted using
@@ -616,8 +616,8 @@ public final class Telephony {
* values:</p>
*
* <ul>
- * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
- * that make up the message.</li>
+ * <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+ * data, including ETWS or CMAS warning notification info if present.</li>
* </ul>
*
* <p>The extra values can be extracted using
@@ -631,6 +631,26 @@ public final class Telephony {
"android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
/**
+ * Broadcast Action: A new CDMA SMS has been received containing Service Category
+ * Program Data (updates the list of enabled broadcast channels). The intent will
+ * have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>operations</em> - An array of CdmaSmsCbProgramData objects containing
+ * the service category operations (add/delete/clear) to perform.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
+
+ /**
* Broadcast Action: The SIM storage for SMS messages is full. If
* space is not freed, messages targeted for the SIM (class 2) may
* not be saved.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 026af34..5f2d642 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -20,6 +20,8 @@ import android.graphics.Paint;
import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
+import com.android.internal.util.ArrayUtils;
+
import java.lang.ref.WeakReference;
/**
@@ -30,8 +32,7 @@ import java.lang.ref.WeakReference;
* {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
* Canvas.drawText()} directly.</p>
*/
-public class DynamicLayout
-extends Layout
+public class DynamicLayout extends Layout
{
private static final int PRIORITY = 128;
@@ -116,6 +117,10 @@ extends Layout
mObjects = new PackedObjectVector<Directions>(1);
+ mBlockEnds = new int[] { 0 };
+ mBlockIndices = new int[] { INVALID_BLOCK_INDEX };
+ mNumberOfBlocks = 1;
+
mIncludePad = includepad;
/*
@@ -295,9 +300,9 @@ extends Layout
n--;
// remove affected lines from old layout
-
mInts.deleteAt(startline, endline - startline);
mObjects.deleteAt(startline, endline - startline);
+ updateBlocks(startline, endline - 1, n);
// adjust offsets in layout for new height and offsets
@@ -363,6 +368,124 @@ extends Layout
}
}
+ /**
+ * This method is called every time the layout is reflowed after an edition.
+ * It updates the internal block data structure. The text is split in blocks
+ * of contiguous lines, with at least one block for the entire text.
+ * When a range of lines is edited, new blocks (from 0 to 3 depending on the
+ * overlap structure) will replace the set of overlapping blocks.
+ * Blocks are listed in order and are represented by their ending line number.
+ * An index is associated to each block (which will be used by display lists),
+ * this class simply invalidates the index of blocks overlapping a modification.
+ *
+ * @param startLine the first line of the range of modified lines
+ * @param endLine the last line of the range, possibly equal to startLine, lower
+ * than getLineCount()
+ * @param newLineCount the number of lines that will replace the range, possibly 0
+ */
+ private void updateBlocks(int startLine, int endLine, int newLineCount) {
+ int firstBlock = -1;
+ int lastBlock = -1;
+ for (int i = 0; i < mNumberOfBlocks; i++) {
+ if (mBlockEnds[i] >= startLine) {
+ firstBlock = i;
+ break;
+ }
+ }
+ for (int i = firstBlock; i < mNumberOfBlocks; i++) {
+ if (mBlockEnds[i] >= endLine) {
+ lastBlock = i;
+ break;
+ }
+ }
+ final int lastBlockEndLine = mBlockEnds[lastBlock];
+
+ boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 :
+ mBlockEnds[firstBlock - 1] + 1);
+ boolean createBlock = newLineCount > 0;
+ boolean createBlockAfter = endLine < mBlockEnds[lastBlock];
+
+ int numAddedBlocks = 0;
+ if (createBlockBefore) numAddedBlocks++;
+ if (createBlock) numAddedBlocks++;
+ if (createBlockAfter) numAddedBlocks++;
+
+ final int numRemovedBlocks = lastBlock - firstBlock + 1;
+ final int newNumberOfBlocks = mNumberOfBlocks + numAddedBlocks - numRemovedBlocks;
+
+ if (newNumberOfBlocks == 0) {
+ // Even when text is empty, there is actually one line and hence one block
+ mBlockEnds[0] = 0;
+ mBlockIndices[0] = INVALID_BLOCK_INDEX;
+ mNumberOfBlocks = 1;
+ return;
+ }
+
+ if (newNumberOfBlocks > mBlockEnds.length) {
+ final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
+ int[] blockEnds = new int[newSize];
+ int[] blockIndices = new int[newSize];
+ System.arraycopy(mBlockEnds, 0, blockEnds, 0, firstBlock);
+ System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
+ System.arraycopy(mBlockEnds, lastBlock + 1,
+ blockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ System.arraycopy(mBlockIndices, lastBlock + 1,
+ blockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ mBlockEnds = blockEnds;
+ mBlockIndices = blockIndices;
+ } else {
+ System.arraycopy(mBlockEnds, lastBlock + 1,
+ mBlockEnds, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ System.arraycopy(mBlockIndices, lastBlock + 1,
+ mBlockIndices, firstBlock + numAddedBlocks, mNumberOfBlocks - lastBlock - 1);
+ }
+
+ mNumberOfBlocks = newNumberOfBlocks;
+ final int deltaLines = newLineCount - (endLine - startLine + 1);
+ for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
+ mBlockEnds[i] += deltaLines;
+ }
+
+ int blockIndex = firstBlock;
+ if (createBlockBefore) {
+ mBlockEnds[blockIndex] = startLine - 1;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ blockIndex++;
+ }
+
+ if (createBlock) {
+ mBlockEnds[blockIndex] = startLine + newLineCount - 1;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ blockIndex++;
+ }
+
+ if (createBlockAfter) {
+ mBlockEnds[blockIndex] = lastBlockEndLine + deltaLines;
+ mBlockIndices[blockIndex] = INVALID_BLOCK_INDEX;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getBlockEnds() {
+ return mBlockEnds;
+ }
+
+ /**
+ * @hide
+ */
+ public int[] getBlockIndices() {
+ return mBlockIndices;
+ }
+
+ /**
+ * @hide
+ */
+ public int getNumberOfBlocks() {
+ return mNumberOfBlocks;
+ }
+
@Override
public int getLineCount() {
return mInts.size() - 1;
@@ -428,6 +551,7 @@ extends Layout
}
public void beforeTextChanged(CharSequence s, int where, int before, int after) {
+ // Intentionally empty
}
public void onTextChanged(CharSequence s, int where, int before, int after) {
@@ -435,6 +559,7 @@ extends Layout
}
public void afterTextChanged(Editable s) {
+ // Intentionally empty
}
public void onSpanAdded(Spannable s, Object o, int start, int end) {
@@ -486,6 +611,20 @@ extends Layout
private PackedIntVector mInts;
private PackedObjectVector<Directions> mObjects;
+ /**
+ * Value used in mBlockIndices when a block has been created or recycled and indicating that its
+ * display list needs to be re-created.
+ * @hide
+ */
+ public static final int INVALID_BLOCK_INDEX = -1;
+ // Stores the line numbers of the last line of each block
+ private int[] mBlockEnds;
+ // The indices of this block's display list in TextView's internal display list array or
+ // INVALID_BLOCK_INDEX if this block has been invalidated during an edition
+ private int[] mBlockIndices;
+ // Number of items actually currently being used in the above 2 arrays
+ private int mNumberOfBlocks;
+
private int mTopPadding, mBottomPadding;
private static StaticLayout sStaticLayout = new StaticLayout(null);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 516ce2a..2dcea80 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1,4 +1,4 @@
- /*
+/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -363,40 +363,67 @@ public abstract class Layout {
// direction of the layout or line. XXX: Should they?
// They are evaluated at each line.
if (mSpannedText) {
- int previousLineBottom = getLineTop(firstLine);
- int previousLineEnd = getLineStart(firstLine);
- ParagraphStyle[] spans = NO_PARA_SPANS;
- TextPaint paint = mPaint;
- CharSequence buf = mText;
- int spanEnd = 0;
- final int width = mWidth;
- Spanned sp = (Spanned) buf;
- int textLength = buf.length();
- for (int i = firstLine; i <= lastLine; i++) {
- int start = previousLineEnd;
- int end = getLineStart(i + 1);
- previousLineEnd = end;
-
- int ltop = previousLineBottom;
- int lbottom = getLineTop(i + 1);
- previousLineBottom = lbottom;
- int lbaseline = lbottom - getLineDescent(i);
-
- if (start >= spanEnd) {
- // These should be infrequent, so we'll use this so that
- // we don't have to check as often.
- spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class);
- // All LineBackgroundSpans on a line contribute to its background.
- spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
- }
+ if (lineBackgroundSpans == null) {
+ lineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
+ }
- for (int n = 0; n < spans.length; n++) {
- LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
- back.drawBackground(canvas, paint, 0, width,
- ltop, lbaseline, lbottom,
- buf, start, end, i);
+ Spanned buffer = (Spanned) mText;
+ int textLength = buffer.length();
+ lineBackgroundSpans.init(buffer, 0, textLength);
+
+ if (lineBackgroundSpans.numberOfSpans > 0) {
+ int previousLineBottom = getLineTop(firstLine);
+ int previousLineEnd = getLineStart(firstLine);
+ ParagraphStyle[] spans = NO_PARA_SPANS;
+ int spansLength = 0;
+ TextPaint paint = mPaint;
+ int spanEnd = 0;
+ final int width = mWidth;
+ for (int i = firstLine; i <= lastLine; i++) {
+ int start = previousLineEnd;
+ int end = getLineStart(i + 1);
+ previousLineEnd = end;
+
+ int ltop = previousLineBottom;
+ int lbottom = getLineTop(i + 1);
+ previousLineBottom = lbottom;
+ int lbaseline = lbottom - getLineDescent(i);
+
+ if (start >= spanEnd) {
+ // These should be infrequent, so we'll use this so that
+ // we don't have to check as often.
+ spanEnd = lineBackgroundSpans.getNextTransition(start, textLength);
+ // All LineBackgroundSpans on a line contribute to its background.
+ spansLength = 0;
+ // Duplication of the logic of getParagraphSpans
+ if (start != end || start == 0) {
+ // Equivalent to a getSpans(start, end), but filling the 'spans' local
+ // array instead to reduce memory allocation
+ for (int j = 0; j < lineBackgroundSpans.numberOfSpans; j++) {
+ // equal test is valid since both intervals are not empty by construction
+ if (lineBackgroundSpans.spanStarts[j] >= end ||
+ lineBackgroundSpans.spanEnds[j] <= start) continue;
+ if (spansLength == spans.length) {
+ // The spans array needs to be expanded
+ int newSize = ArrayUtils.idealObjectArraySize(2 * spansLength);
+ ParagraphStyle[] newSpans = new ParagraphStyle[newSize];
+ System.arraycopy(spans, 0, newSpans, 0, spansLength);
+ spans = newSpans;
+ }
+ spans[spansLength++] = lineBackgroundSpans.spans[j];
+ }
+ }
+ }
+
+ for (int n = 0; n < spansLength; n++) {
+ LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
+ lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
+ ltop, lbaseline, lbottom,
+ buffer, start, end, i);
+ }
}
}
+ lineBackgroundSpans.recycle();
}
// There can be a highlight even without spans if we are drawing
@@ -1830,6 +1857,7 @@ public abstract class Layout {
private static final Rect sTempRect = new Rect();
private boolean mSpannedText;
private TextDirectionHeuristic mTextDir;
+ private SpanSet<LineBackgroundSpan> lineBackgroundSpans;
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java
new file mode 100644
index 0000000..3ca6033
--- /dev/null
+++ b/core/java/android/text/SpanSet.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import java.lang.reflect.Array;
+
+/**
+ * A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then
+ * provides faster access to {@link Spanned#nextSpanTransition(int, int, Class)}.
+ *
+ * Fields are left public for a convenient direct access.
+ *
+ * Note that empty spans are ignored by this class.
+ * @hide
+ */
+public class SpanSet<E> {
+ private final Class<? extends E> classType;
+
+ int numberOfSpans;
+ E[] spans;
+ int[] spanStarts;
+ int[] spanEnds;
+ int[] spanFlags;
+
+ SpanSet(Class<? extends E> type) {
+ classType = type;
+ numberOfSpans = 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void init(Spanned spanned, int start, int limit) {
+ final E[] allSpans = spanned.getSpans(start, limit, classType);
+ final int length = allSpans.length;
+
+ if (length > 0 && (spans == null || spans.length < length)) {
+ // These arrays may end up being too large because of the discarded empty spans
+ spans = (E[]) Array.newInstance(classType, length);
+ spanStarts = new int[length];
+ spanEnds = new int[length];
+ spanFlags = new int[length];
+ }
+
+ numberOfSpans = 0;
+ for (int i = 0; i < length; i++) {
+ final E span = allSpans[i];
+
+ final int spanStart = spanned.getSpanStart(span);
+ final int spanEnd = spanned.getSpanEnd(span);
+ if (spanStart == spanEnd) continue;
+
+ final int spanFlag = spanned.getSpanFlags(span);
+
+ spans[numberOfSpans] = span;
+ spanStarts[numberOfSpans] = spanStart;
+ spanEnds[numberOfSpans] = spanEnd;
+ spanFlags[numberOfSpans] = spanFlag;
+
+ numberOfSpans++;
+ }
+ }
+
+ /**
+ * Returns true if there are spans intersecting the given interval.
+ * @param end must be strictly greater than start
+ */
+ public boolean hasSpansIntersecting(int start, int end) {
+ for (int i = 0; i < numberOfSpans; i++) {
+ // equal test is valid since both intervals are not empty by construction
+ if (spanStarts[i] >= end || spanEnds[i] <= start) continue;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Similar to {@link Spanned#nextSpanTransition(int, int, Class)}
+ */
+ int getNextTransition(int start, int limit) {
+ for (int i = 0; i < numberOfSpans; i++) {
+ final int spanStart = spanStarts[i];
+ final int spanEnd = spanEnds[i];
+ if (spanStart > start && spanStart < limit) limit = spanStart;
+ if (spanEnd > start && spanEnd < limit) limit = spanEnd;
+ }
+ return limit;
+ }
+
+ /**
+ * Removes all internal references to the spans to avoid memory leaks.
+ */
+ public void recycle() {
+ // The spans array is guaranteed to be not null when numberOfSpans is > 0
+ for (int i = 0; i < numberOfSpans; i++) {
+ spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
+ }
+ }
+}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1e8a2f7..0d2835a 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -30,8 +30,6 @@ import android.util.Log;
import com.android.internal.util.ArrayUtils;
-import java.lang.reflect.Array;
-
/**
* Represents a line of styled text, for measuring in visual order and
* for rendering.
@@ -860,78 +858,6 @@ class TextLine {
return runIsRtl ? -ret : ret;
}
- private static class SpanSet<E> {
- int numberOfSpans;
- E[] spans;
- int[] spanStarts;
- int[] spanEnds;
- int[] spanFlags;
- final Class<? extends E> classType;
-
- SpanSet(Class<? extends E> type) {
- classType = type;
- numberOfSpans = 0;
- }
-
- @SuppressWarnings("unchecked")
- public void init(Spanned spanned, int start, int limit) {
- final E[] allSpans = spanned.getSpans(start, limit, classType);
- final int length = allSpans.length;
-
- if (length > 0 && (spans == null || spans.length < length)) {
- // These arrays may end up being too large because of empty spans
- spans = (E[]) Array.newInstance(classType, length);
- spanStarts = new int[length];
- spanEnds = new int[length];
- spanFlags = new int[length];
- }
-
- numberOfSpans = 0;
- for (int i = 0; i < length; i++) {
- final E span = allSpans[i];
-
- final int spanStart = spanned.getSpanStart(span);
- final int spanEnd = spanned.getSpanEnd(span);
- if (spanStart == spanEnd) continue;
-
- final int spanFlag = spanned.getSpanFlags(span);
-
- spans[numberOfSpans] = span;
- spanStarts[numberOfSpans] = spanStart;
- spanEnds[numberOfSpans] = spanEnd;
- spanFlags[numberOfSpans] = spanFlag;
-
- numberOfSpans++;
- }
- }
-
- public boolean hasSpansIntersecting(int start, int end) {
- for (int i = 0; i < numberOfSpans; i++) {
- // equal test is valid since both intervals are not empty by construction
- if (spanStarts[i] >= end || spanEnds[i] <= start) continue;
- return true;
- }
- return false;
- }
-
- int getNextTransition(int start, int limit) {
- for (int i = 0; i < numberOfSpans; i++) {
- final int spanStart = spanStarts[i];
- final int spanEnd = spanEnds[i];
- if (spanStart > start && spanStart < limit) limit = spanStart;
- if (spanEnd > start && spanEnd < limit) limit = spanEnd;
- }
- return limit;
- }
-
- public void recycle() {
- // The spans array is guaranteed to be not null when numberOfSpans is > 0
- for (int i = 0; i < numberOfSpans; i++) {
- spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
- }
- }
- }
-
/**
* Utility function for handling a unidirectional run. The run must not
* contain tabs or emoji but can contain styles.
diff --git a/core/java/android/util/LocaleUtil.java b/core/java/android/util/LocaleUtil.java
index 9953252..60526e1 100644
--- a/core/java/android/util/LocaleUtil.java
+++ b/core/java/android/util/LocaleUtil.java
@@ -24,7 +24,6 @@ import libcore.icu.ICU;
/**
* Various utilities for Locales
*
- * @hide
*/
public class LocaleUtil {
@@ -41,9 +40,7 @@ public class LocaleUtil {
* {@link View#LAYOUT_DIRECTION_LTR} or
* {@link View#LAYOUT_DIRECTION_RTL}.
*
- * Be careful: this code will need to be changed when vertical scripts will be supported
- *
- * @hide
+ * Be careful: this code will need to be updated when vertical scripts will be supported
*/
public static int getLayoutDirectionFromLocale(Locale locale) {
if (locale != null && !locale.equals(Locale.ROOT)) {
@@ -69,7 +66,7 @@ public class LocaleUtil {
* {@link View#LAYOUT_DIRECTION_LTR} or
* {@link View#LAYOUT_DIRECTION_RTL}.
*
- * Be careful: this code will need to be changed when vertical scripts will be supported
+ * Be careful: this code will need to be updated when vertical scripts will be supported
*
* @hide
*/
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index f60c8f0..1dabad2 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -16,6 +16,8 @@
package android.view;
+import android.os.Handler;
+
/**
* A display lists records a series of graphics related operation and can replay
* them later. Display lists are usually built by recording operations on a
@@ -70,4 +72,204 @@ public abstract class DisplayList {
* @return The size of this display list in bytes
*/
public abstract int getSize();
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DisplayList Property Setters
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Set the caching property on the DisplayList, which indicates whether the DisplayList
+ * holds a layer. Layer DisplayLists should avoid creating an alpha layer, since alpha is
+ * handled in the drawLayer operation directly (and more efficiently).
+ *
+ * @param caching true if the DisplayList represents a hardware layer, false otherwise.
+ */
+ public abstract void setCaching(boolean caching);
+
+ /**
+ * Set whether the DisplayList should clip itself to its bounds. This property is controlled by
+ * the view's parent.
+ *
+ * @param clipChildren true if the DisplayList should clip to its bounds
+ */
+ public abstract void setClipChildren(boolean clipChildren);
+
+ /**
+ * Set the application scale on the DisplayList. This scale is incurred by applications that
+ * are auto-scaled for compatibility reasons. By default, the value is 1 (unscaled).
+ *
+ * @param scale The scaling factor
+ */
+ public abstract void setApplicationScale(float scale);
+
+ /**
+ * Sets the alpha value for the DisplayList
+ *
+ * @param alpha The translucency of the DisplayList
+ * @see View#setAlpha(float)
+ */
+ public abstract void setAlpha(float alpha);
+
+ /**
+ * Sets the translationX value for the DisplayList
+ *
+ * @param translationX The translationX value of the DisplayList
+ * @see View#setTranslationX(float)
+ */
+ public abstract void setTranslationX(float translationX);
+
+ /**
+ * Sets the translationY value for the DisplayList
+ *
+ * @param translationY The translationY value of the DisplayList
+ * @see View#setTranslationY(float)
+ */
+ public abstract void setTranslationY(float translationY);
+
+ /**
+ * Sets the rotation value for the DisplayList
+ *
+ * @param rotation The rotation value of the DisplayList
+ * @see View#setRotation(float)
+ */
+ public abstract void setRotation(float rotation);
+
+ /**
+ * Sets the rotationX value for the DisplayList
+ *
+ * @param rotationX The rotationX value of the DisplayList
+ * @see View#setRotationX(float)
+ */
+ public abstract void setRotationX(float rotationX);
+
+ /**
+ * Sets the rotationY value for the DisplayList
+ *
+ * @param rotationY The rotationY value of the DisplayList
+ * @see View#setRotationY(float)
+ */
+ public abstract void setRotationY(float rotationY);
+
+ /**
+ * Sets the scaleX value for the DisplayList
+ *
+ * @param scaleX The scaleX value of the DisplayList
+ * @see View#setScaleX(float)
+ */
+ public abstract void setScaleX(float scaleX);
+
+ /**
+ * Sets the scaleY value for the DisplayList
+ *
+ * @param scaleY The scaleY value of the DisplayList
+ * @see View#setScaleY(float)
+ */
+ public abstract void setScaleY(float scaleY);
+
+ /**
+ * Sets all of the transform-related values of the View onto the DisplayList
+ *
+ * @param alpha The alpha value of the DisplayList
+ * @param translationX The translationX value of the DisplayList
+ * @param translationY The translationY value of the DisplayList
+ * @param rotation The rotation value of the DisplayList
+ * @param rotationX The rotationX value of the DisplayList
+ * @param rotationY The rotationY value of the DisplayList
+ * @param scaleX The scaleX value of the DisplayList
+ * @param scaleY The scaleY value of the DisplayList
+ */
+ public abstract void setTransformationInfo(float alpha, float translationX, float translationY,
+ float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
+
+ /**
+ * Sets the pivotX value for the DisplayList
+ *
+ * @param pivotX The pivotX value of the DisplayList
+ * @see View#setPivotX(float)
+ */
+ public abstract void setPivotX(float pivotX);
+
+ /**
+ * Sets the pivotY value for the DisplayList
+ *
+ * @param pivotY The pivotY value of the DisplayList
+ * @see View#setPivotY(float)
+ */
+ public abstract void setPivotY(float pivotY);
+
+ /**
+ * Sets the camera distance for the DisplayList
+ *
+ * @param distance The distance in z of the camera of the DisplayList
+ * @see View#setCameraDistance(float)
+ */
+ public abstract void setCameraDistance(float distance);
+
+ /**
+ * Sets the left value for the DisplayList
+ *
+ * @param left The left value of the DisplayList
+ * @see View#setLeft(int)
+ */
+ public abstract void setLeft(int left);
+
+ /**
+ * Sets the top value for the DisplayList
+ *
+ * @param top The top value of the DisplayList
+ * @see View#setTop(int)
+ */
+ public abstract void setTop(int top);
+
+ /**
+ * Sets the right value for the DisplayList
+ *
+ * @param right The right value of the DisplayList
+ * @see View#setRight(int)
+ */
+ public abstract void setRight(int right);
+
+ /**
+ * Sets the bottom value for the DisplayList
+ *
+ * @param bottom The bottom value of the DisplayList
+ * @see View#setBottom(int)
+ */
+ public abstract void setBottom(int bottom);
+
+ /**
+ * Sets the left and top values for the DisplayList
+ *
+ * @param left The left value of the DisplayList
+ * @param top The top value of the DisplayList
+ * @see View#setLeft(int)
+ * @see View#setTop(int)
+ */
+ public abstract void setLeftTop(int left, int top);
+
+ /**
+ * Sets the left and top values for the DisplayList
+ *
+ * @param left The left value of the DisplayList
+ * @param top The top value of the DisplayList
+ * @see View#setLeft(int)
+ * @see View#setTop(int)
+ */
+ public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom);
+
+ /**
+ * Offsets the left and right values for the DisplayList
+ *
+ * @param offset The amount that the left and right values of the DisplayList are offset
+ * @see View#offsetLeftAndRight(int)
+ */
+ public abstract void offsetLeftRight(int offset);
+
+ /**
+ * Offsets the top and bottom values for the DisplayList
+ *
+ * @param offset The amount that the top and bottom values of the DisplayList are offset
+ * @see View#offsetTopAndBottom(int)
+ */
+ public abstract void offsetTopBottom(int offset);
}
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 969c9ab..9b4cf21 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -96,6 +96,251 @@ class GLES20DisplayList extends DisplayList {
return GLES20Canvas.getDisplayListSize(mFinalizer.mNativeDisplayList);
}
+ ///////////////////////////////////////////////////////////////////////////
+ // Native View Properties
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void setCaching(boolean caching) {
+ try {
+ nSetCaching(getNativeDisplayList(), caching);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setClipChildren(boolean clipChildren) {
+ try {
+ nSetClipChildren(getNativeDisplayList(), clipChildren);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setApplicationScale(float scale) {
+ try {
+ nSetApplicationScale(getNativeDisplayList(), scale);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ try {
+ nSetAlpha(getNativeDisplayList(), alpha);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setTranslationX(float translationX) {
+ try {
+ nSetTranslationX(getNativeDisplayList(), translationX);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setTranslationY(float translationY) {
+ try {
+ nSetTranslationY(getNativeDisplayList(), translationY);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setRotation(float rotation) {
+ try {
+ nSetRotation(getNativeDisplayList(), rotation);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setRotationX(float rotationX) {
+ try {
+ nSetRotationX(getNativeDisplayList(), rotationX);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setRotationY(float rotationY) {
+ try {
+ nSetRotationY(getNativeDisplayList(), rotationY);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setScaleX(float scaleX) {
+ try {
+ nSetScaleX(getNativeDisplayList(), scaleX);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setScaleY(float scaleY) {
+ try {
+ nSetScaleY(getNativeDisplayList(), scaleY);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setTransformationInfo(float alpha, float translationX, float translationY,
+ float rotation, float rotationX, float rotationY, float scaleX, float scaleY) {
+ try {
+ nSetTransformationInfo(getNativeDisplayList(), alpha, translationX, translationY,
+ rotation, rotationX, rotationY, scaleX, scaleY);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setPivotX(float pivotX) {
+ try {
+ nSetPivotX(getNativeDisplayList(), pivotX);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setPivotY(float pivotY) {
+ try {
+ nSetPivotY(getNativeDisplayList(), pivotY);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setCameraDistance(float distance) {
+ try {
+ nSetCameraDistance(getNativeDisplayList(), distance);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setLeft(int left) {
+ try {
+ nSetLeft(getNativeDisplayList(), left);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setTop(int top) {
+ try {
+ nSetTop(getNativeDisplayList(), top);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setRight(int right) {
+ try {
+ nSetRight(getNativeDisplayList(), right);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setBottom(int bottom) {
+ try {
+ nSetBottom(getNativeDisplayList(), bottom);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setLeftTop(int left, int top) {
+ try {
+ nSetLeftTop(getNativeDisplayList(), left, top);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ try {
+ nSetLeftTopRightBottom(getNativeDisplayList(), left, top, right, bottom);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void offsetLeftRight(int offset) {
+ try {
+ nOffsetLeftRight(getNativeDisplayList(), offset);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ @Override
+ public void offsetTopBottom(int offset) {
+ try {
+ nOffsetTopBottom(getNativeDisplayList(), offset);
+ } catch (IllegalStateException e) {
+ // invalid DisplayList okay: we'll set current values the next time we render to it
+ }
+ }
+
+ private static native void nOffsetTopBottom(int displayList, int offset);
+ private static native void nOffsetLeftRight(int displayList, int offset);
+ private static native void nSetLeftTopRightBottom(int displayList, int left, int top,
+ int right, int bottom);
+ private static native void nSetLeftTop(int displayList, int left, int top);
+ private static native void nSetBottom(int displayList, int bottom);
+ private static native void nSetRight(int displayList, int right);
+ private static native void nSetTop(int displayList, int top);
+ private static native void nSetLeft(int displayList, int left);
+ private static native void nSetCameraDistance(int displayList, float distance);
+ private static native void nSetPivotY(int displayList, float pivotY);
+ private static native void nSetPivotX(int displayList, float pivotX);
+ private static native void nSetCaching(int displayList, boolean caching);
+ private static native void nSetClipChildren(int displayList, boolean clipChildren);
+ private static native void nSetApplicationScale(int displayList, float scale);
+ private static native void nSetAlpha(int displayList, float alpha);
+ private static native void nSetTranslationX(int displayList, float translationX);
+ private static native void nSetTranslationY(int displayList, float translationY);
+ private static native void nSetRotation(int displayList, float rotation);
+ private static native void nSetRotationX(int displayList, float rotationX);
+ private static native void nSetRotationY(int displayList, float rotationY);
+ private static native void nSetScaleX(int displayList, float scaleX);
+ private static native void nSetScaleY(int displayList, float scaleY);
+ private static native void nSetTransformationInfo(int displayList, float alpha,
+ float translationX, float translationY, float rotation, float rotationX,
+ float rotationY, float scaleX, float scaleY);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Finalization
+ ///////////////////////////////////////////////////////////////////////////
+
private static class DisplayListFinalizer {
final int mNativeDisplayList;
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index 63f5ec1..f031fe7 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -153,7 +153,8 @@ public class Gravity
* container.
* @param layoutDirection The layout direction.
*
- * @hide
+ * @see {@link View#LAYOUT_DIRECTION_LTR}
+ * @see {@link View#LAYOUT_DIRECTION_RTL}
*/
public static void apply(int gravity, int w, int h, Rect container,
Rect outRect, int layoutDirection) {
@@ -268,6 +269,37 @@ public class Gravity
}
/**
+ * Apply a gravity constant to an object.
+ *
+ * @param gravity The desired placement of the object, as defined by the
+ * constants in this class.
+ * @param w The horizontal size of the object.
+ * @param h The vertical size of the object.
+ * @param container The frame of the containing space, in which the object
+ * will be placed. Should be large enough to contain the
+ * width and height of the object.
+ * @param xAdj Offset to apply to the X axis. If gravity is LEFT this
+ * pushes it to the right; if gravity is RIGHT it pushes it to
+ * the left; if gravity is CENTER_HORIZONTAL it pushes it to the
+ * right or left; otherwise it is ignored.
+ * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes
+ * it down; if gravity is BOTTOM it pushes it up; if gravity is
+ * CENTER_VERTICAL it pushes it down or up; otherwise it is
+ * ignored.
+ * @param outRect Receives the computed frame of the object in its
+ * container.
+ * @param layoutDirection The layout direction.
+ *
+ * @see {@link View#LAYOUT_DIRECTION_LTR}
+ * @see {@link View#LAYOUT_DIRECTION_RTL}
+ */
+ public static void apply(int gravity, int w, int h, Rect container,
+ int xAdj, int yAdj, Rect outRect, int layoutDirection) {
+ int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+ apply(absGravity, w, h, container, xAdj, yAdj, outRect);
+ }
+
+ /**
* Apply additional gravity behavior based on the overall "display" that an
* object exists in. This can be used after
* {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
@@ -320,7 +352,32 @@ public class Gravity
}
}
}
-
+
+ /**
+ * Apply additional gravity behavior based on the overall "display" that an
+ * object exists in. This can be used after
+ * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+ * within a visible display. By default this moves or clips the object
+ * to be visible in the display; the gravity flags
+ * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+ * can be used to change this behavior.
+ *
+ * @param gravity Gravity constants to modify the placement within the
+ * display.
+ * @param display The rectangle of the display in which the object is
+ * being placed.
+ * @param inoutObj Supplies the current object position; returns with it
+ * modified if needed to fit in the display.
+ * @param layoutDirection The layout direction.
+ *
+ * @see {@link View#LAYOUT_DIRECTION_LTR}
+ * @see {@link View#LAYOUT_DIRECTION_RTL}
+ */
+ public static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) {
+ int absGravity = getAbsoluteGravity(gravity, layoutDirection);
+ applyDisplay(absGravity, display, inoutObj);
+ }
+
/**
* <p>Indicate whether the supplied gravity has a vertical pull.</p>
*
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index a97167b..e73f7bf 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -36,6 +36,7 @@ abstract class HardwareLayer {
int mWidth;
int mHeight;
+ DisplayList mDisplayList;
boolean mOpaque;
@@ -79,6 +80,24 @@ abstract class HardwareLayer {
}
/**
+ * Returns the DisplayList for the layer.
+ *
+ * @return The DisplayList of the hardware layer
+ */
+ DisplayList getDisplayList() {
+ return mDisplayList;
+ }
+
+ /**
+ * Sets the DisplayList for the layer.
+ *
+ * @param displayList The new DisplayList for this layer
+ */
+ void setDisplayList(DisplayList displayList) {
+ mDisplayList = displayList;
+ }
+
+ /**
* Returns whether or not this layer is opaque.
*
* @return True if the layer is opaque, false otherwise
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index bf91700..d08a61f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -960,6 +960,11 @@ public abstract class HardwareRenderer {
Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took " +
total + "ms");
}
+ if (View.USE_DISPLAY_LIST_PROPERTIES) {
+ Log.d("DLProperties", "getDisplayList():\t" +
+ mProfileData[mProfileCurrentFrame]);
+ }
+
}
if (displayList != null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7a1923b..fdf3a814 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -336,9 +336,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Padding can be used to offset the content of the view by a specific amount of
* pixels. For instance, a left padding of 2 will push the view's content by
* 2 pixels to the right of the left edge. Padding can be set using the
- * {@link #setPadding(int, int, int, int)} method and queried by calling
- * {@link #getPaddingLeft()}, {@link #getPaddingTop()},
- * {@link #getPaddingRight()}, {@link #getPaddingBottom()}.
+ * {@link #setPadding(int, int, int, int)} or {@link #setPaddingRelative(int, int, int, int)}
+ * method and queried by calling {@link #getPaddingLeft()}, {@link #getPaddingTop()},
+ * {@link #getPaddingRight()}, {@link #getPaddingBottom()}, {@link #getPaddingStart()},
+ * {@link #getPaddingEnd()}.
* </p>
*
* <p>
@@ -1497,6 +1498,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>();
/**
+ * Temporary flag, used to enable processing of View properties in the native DisplayList
+ * object instead of during draw(). Soon to be enabled by default for hardware-accelerated
+ * apps.
+ * @hide
+ */
+ public static final boolean USE_DISPLAY_LIST_PROPERTIES = true;
+
+ /**
* Map used to store views' tags.
*/
private SparseArray<Object> mKeyedTags;
@@ -7283,6 +7292,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Gets the distance along the Z axis from the camera to this view.
+ *
+ * @see #setCameraDistance(float)
+ *
+ * @return The distance along the Z axis.
+ */
+ public float getCameraDistance() {
+ ensureTransformationInfo();
+ final float dpi = mResources.getDisplayMetrics().densityDpi;
+ final TransformationInfo info = mTransformationInfo;
+ if (info.mCamera == null) {
+ info.mCamera = new Camera();
+ info.matrix3D = new Matrix();
+ }
+ return -(info.mCamera.getLocationZ() * dpi);
+ }
+
+ /**
* <p>Sets the distance along the Z axis (orthogonal to the X/Y plane on which
* views are drawn) from the camera to this view. The camera's distance
* affects 3D transformations, for instance rotations around the X and Y
@@ -7322,8 +7349,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #setRotationY(float)
*/
public void setCameraDistance(float distance) {
- invalidateParentCaches();
- invalidate(false);
+ invalidateViewProperty(true, false);
ensureTransformationInfo();
final float dpi = mResources.getDisplayMetrics().densityDpi;
@@ -7336,7 +7362,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
info.mCamera.setLocation(0.0f, 0.0f, -Math.abs(distance) / dpi);
info.mMatrixDirty = true;
- invalidate(false);
+ invalidateViewProperty(false, false);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setCameraDistance(distance);
+ }
}
/**
@@ -7371,13 +7400,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mRotation != rotation) {
- invalidateParentCaches();
// Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mRotation = rotation;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setRotation(rotation);
+ }
}
}
@@ -7418,13 +7448,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mRotationY != rotationY) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mRotationY = rotationY;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setRotationY(rotationY);
+ }
}
}
@@ -7465,13 +7495,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mRotationX != rotationX) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mRotationX = rotationX;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setRotationX(rotationX);
+ }
}
}
@@ -7504,13 +7534,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mScaleX != scaleX) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mScaleX = scaleX;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setScaleX(scaleX);
+ }
}
}
@@ -7543,13 +7573,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mScaleY != scaleY) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mScaleY = scaleY;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setScaleY(scaleY);
+ }
}
}
@@ -7588,13 +7618,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mPrivateFlags |= PIVOT_EXPLICITLY_SET;
final TransformationInfo info = mTransformationInfo;
if (info.mPivotX != pivotX) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mPivotX = pivotX;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setPivotX(pivotX);
+ }
}
}
@@ -7632,13 +7662,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mPrivateFlags |= PIVOT_EXPLICITLY_SET;
final TransformationInfo info = mTransformationInfo;
if (info.mPivotY != pivotY) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mPivotY = pivotY;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setPivotY(pivotY);
+ }
}
}
@@ -7677,14 +7707,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
if (mTransformationInfo.mAlpha != alpha) {
mTransformationInfo.mAlpha = alpha;
- invalidateParentCaches();
if (onSetAlpha((int) (alpha * 255))) {
mPrivateFlags |= ALPHA_SET;
// subclass is handling alpha - don't optimize rendering cache invalidation
+ invalidateParentCaches();
invalidate(true);
} else {
mPrivateFlags &= ~ALPHA_SET;
- invalidate(false);
+ invalidateViewProperty(true, false);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setAlpha(alpha);
+ }
}
}
}
@@ -7709,6 +7742,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return true;
} else {
mPrivateFlags &= ~ALPHA_SET;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setAlpha(alpha);
+ }
}
}
return false;
@@ -7758,6 +7794,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int oldHeight = mBottom - mTop;
mTop = top;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setTop(mTop);
+ }
onSizeChanged(width, mBottom - mTop, width, oldHeight);
@@ -7824,6 +7863,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int oldHeight = mBottom - mTop;
mBottom = bottom;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setBottom(mBottom);
+ }
onSizeChanged(width, mBottom - mTop, width, oldHeight);
@@ -7884,6 +7926,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int height = mBottom - mTop;
mLeft = left;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setLeft(left);
+ }
onSizeChanged(mRight - mLeft, height, oldWidth, height);
@@ -7897,6 +7942,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
mBackgroundSizeChanged = true;
invalidateParentIfNeeded();
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+
+ }
}
}
@@ -7941,6 +7989,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int height = mBottom - mTop;
mRight = right;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setRight(mRight);
+ }
onSizeChanged(mRight - mLeft, height, oldWidth, height);
@@ -8030,13 +8081,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mTranslationX != translationX) {
- invalidateParentCaches();
// Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mTranslationX = translationX;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setTranslationX(translationX);
+ }
}
}
@@ -8067,13 +8119,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mTranslationY != translationY) {
- invalidateParentCaches();
- // Double-invalidation is necessary to capture view's old and new areas
- invalidate(false);
+ invalidateViewProperty(true, false);
info.mTranslationY = translationY;
info.mMatrixDirty = true;
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ invalidateViewProperty(false, true);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setTranslationY(translationY);
+ }
}
}
@@ -8182,36 +8234,43 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final boolean matrixIsIdentity = mTransformationInfo == null
|| mTransformationInfo.mMatrixIsIdentity;
if (matrixIsIdentity) {
- final ViewParent p = mParent;
- if (p != null && mAttachInfo != null) {
- final Rect r = mAttachInfo.mTmpInvalRect;
- int minTop;
- int maxBottom;
- int yLoc;
- if (offset < 0) {
- minTop = mTop + offset;
- maxBottom = mBottom;
- yLoc = offset;
- } else {
- minTop = mTop;
- maxBottom = mBottom + offset;
- yLoc = 0;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ invalidateViewProperty(false, false);
+ } else {
+ final ViewParent p = mParent;
+ if (p != null && mAttachInfo != null) {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ int minTop;
+ int maxBottom;
+ int yLoc;
+ if (offset < 0) {
+ minTop = mTop + offset;
+ maxBottom = mBottom;
+ yLoc = offset;
+ } else {
+ minTop = mTop;
+ maxBottom = mBottom + offset;
+ yLoc = 0;
+ }
+ r.set(0, yLoc, mRight - mLeft, maxBottom - minTop);
+ p.invalidateChild(this, r);
}
- r.set(0, yLoc, mRight - mLeft, maxBottom - minTop);
- p.invalidateChild(this, r);
}
} else {
- invalidate(false);
+ invalidateViewProperty(false, false);
}
mTop += offset;
mBottom += offset;
-
- if (!matrixIsIdentity) {
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.offsetTopBottom(offset);
+ invalidateViewProperty(false, false);
+ } else {
+ if (!matrixIsIdentity) {
+ invalidateViewProperty(false, true);
+ }
+ invalidateParentIfNeeded();
}
- invalidateParentIfNeeded();
}
}
@@ -8226,33 +8285,40 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final boolean matrixIsIdentity = mTransformationInfo == null
|| mTransformationInfo.mMatrixIsIdentity;
if (matrixIsIdentity) {
- final ViewParent p = mParent;
- if (p != null && mAttachInfo != null) {
- final Rect r = mAttachInfo.mTmpInvalRect;
- int minLeft;
- int maxRight;
- if (offset < 0) {
- minLeft = mLeft + offset;
- maxRight = mRight;
- } else {
- minLeft = mLeft;
- maxRight = mRight + offset;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ invalidateViewProperty(false, false);
+ } else {
+ final ViewParent p = mParent;
+ if (p != null && mAttachInfo != null) {
+ final Rect r = mAttachInfo.mTmpInvalRect;
+ int minLeft;
+ int maxRight;
+ if (offset < 0) {
+ minLeft = mLeft + offset;
+ maxRight = mRight;
+ } else {
+ minLeft = mLeft;
+ maxRight = mRight + offset;
+ }
+ r.set(0, 0, maxRight - minLeft, mBottom - mTop);
+ p.invalidateChild(this, r);
}
- r.set(0, 0, maxRight - minLeft, mBottom - mTop);
- p.invalidateChild(this, r);
}
} else {
- invalidate(false);
+ invalidateViewProperty(false, false);
}
mLeft += offset;
mRight += offset;
-
- if (!matrixIsIdentity) {
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
- invalidate(false);
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.offsetLeftRight(offset);
+ invalidateViewProperty(false, false);
+ } else {
+ if (!matrixIsIdentity) {
+ invalidateViewProperty(false, true);
+ }
+ invalidateParentIfNeeded();
}
- invalidateParentIfNeeded();
}
}
@@ -8657,6 +8723,62 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Quick invalidation for View property changes (alpha, translationXY, etc.). We don't want to
+ * set any flags or handle all of the cases handled by the default invalidation methods.
+ * Instead, we just want to schedule a traversal in ViewRootImpl with the appropriate
+ * dirty rect. This method calls into fast invalidation methods in ViewGroup that
+ * walk up the hierarchy, transforming the dirty rect as necessary.
+ *
+ * The method also handles normal invalidation logic if display list properties are not
+ * being used in this view. The invalidateParent and forceRedraw flags are used by that
+ * backup approach, to handle these cases used in the various property-setting methods.
+ *
+ * @param invalidateParent Force a call to invalidateParentCaches() if display list properties
+ * are not being used in this view
+ * @param forceRedraw Mark the view as DRAWN to force the invalidation to propagate, if display
+ * list properties are not being used in this view
+ */
+ void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+ if (!USE_DISPLAY_LIST_PROPERTIES || mDisplayList == null ||
+ (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
+ if (invalidateParent) {
+ invalidateParentCaches();
+ }
+ if (forceRedraw) {
+ mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ }
+ invalidate(false);
+ } else {
+ final AttachInfo ai = mAttachInfo;
+ final ViewParent p = mParent;
+ if (p != null && ai != null) {
+ final Rect r = ai.mTmpInvalRect;
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).invalidateChildFast(this, r);
+ } else {
+ mParent.invalidateChild(this, r);
+ }
+ }
+ }
+ }
+
+ /**
+ * Utility method to transform a given Rect by the current matrix of this view.
+ */
+ void transformRect(final Rect rect) {
+ if (!getMatrix().isIdentity()) {
+ RectF boundingRect = mAttachInfo.mTmpTransformRect;
+ boundingRect.set(rect);
+ getMatrix().mapRect(boundingRect);
+ rect.set((int) (boundingRect.left - 0.5f),
+ (int) (boundingRect.top - 0.5f),
+ (int) (boundingRect.right + 0.5f),
+ (int) (boundingRect.bottom + 0.5f));
+ }
+ }
+
+ /**
* Used to indicate that the parent of this view should clear its caches. This functionality
* is used to force the parent to rebuild its display list (when hardware-accelerated),
* which is necessary when various parent-managed properties of the view change, such as
@@ -9819,8 +9941,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @param layoutDirection the direction of the layout
*
- * {@link #LAYOUT_DIRECTION_LTR}
- * {@link #LAYOUT_DIRECTION_RTL}
+ * @see {@link #LAYOUT_DIRECTION_LTR}
+ * @see {@link #LAYOUT_DIRECTION_RTL}
*/
public void onPaddingChanged(int layoutDirection) {
}
@@ -9890,12 +10012,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
destroyLayer();
- if (mDisplayList != null) {
- mDisplayList.invalidate();
- }
-
if (mAttachInfo != null) {
+ if (mDisplayList != null) {
+ mAttachInfo.mViewRootImpl.invalidateDisplayList(mDisplayList);
+ }
mAttachInfo.mViewRootImpl.cancelInvalidate(this);
+ } else {
+ if (mDisplayList != null) {
+ // Should never happen
+ mDisplayList.invalidate();
+ }
}
mCurrentAnimation = null;
@@ -10372,10 +10498,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mHardwareLayer == null) {
mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
width, height, isOpaque());
- mLocalDirtyRect.setEmpty();
+ mLocalDirtyRect.set(0, 0, width, height);
} else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
mHardwareLayer.resize(width, height);
- mLocalDirtyRect.setEmpty();
+ mLocalDirtyRect.set(0, 0, width, height);
}
// The layer is not valid if the underlying GPU resources cannot be allocated
@@ -10383,7 +10509,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
- mHardwareLayer.redraw(getDisplayList(), mLocalDirtyRect);
+ mHardwareLayer.redraw(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect);
mLocalDirtyRect.setEmpty();
}
@@ -10535,78 +10661,138 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * <p>Returns a display list that can be used to draw this view again
- * without executing its draw method.</p>
+ * Returns a DisplayList. If the incoming displayList is null, one will be created.
+ * Otherwise, the same display list will be returned (after having been rendered into
+ * along the way, depending on the invalidation state of the view).
*
- * @return A DisplayList ready to replay, or null if caching is not enabled.
- *
- * @hide
+ * @param displayList The previous version of this displayList, could be null.
+ * @param isLayer Whether the requester of the display list is a layer. If so,
+ * the view will avoid creating a layer inside the resulting display list.
+ * @return A new or reused DisplayList object.
*/
- public DisplayList getDisplayList() {
+ private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
if (!canHaveDisplayList()) {
return null;
}
if (((mPrivateFlags & DRAWING_CACHE_VALID) == 0 ||
- mDisplayList == null || !mDisplayList.isValid() ||
- mRecreateDisplayList)) {
+ displayList == null || !displayList.isValid() ||
+ (!isLayer && mRecreateDisplayList))) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
- if (mDisplayList != null && mDisplayList.isValid() &&
- !mRecreateDisplayList) {
+ if (displayList != null && displayList.isValid() &&
+ !isLayer && !mRecreateDisplayList) {
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
mPrivateFlags &= ~DIRTY_MASK;
dispatchGetDisplayList();
- return mDisplayList;
+ return displayList;
}
- // If we got here, we're recreating it. Mark it as such to ensure that
- // we copy in child display lists into ours in drawChild()
- mRecreateDisplayList = true;
- if (mDisplayList == null) {
+ if (!isLayer) {
+ // If we got here, we're recreating it. Mark it as such to ensure that
+ // we copy in child display lists into ours in drawChild()
+ mRecreateDisplayList = true;
+ }
+ if (displayList == null) {
final String name = getClass().getSimpleName();
- mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(name);
+ displayList = mAttachInfo.mHardwareRenderer.createDisplayList(name);
// If we're creating a new display list, make sure our parent gets invalidated
// since they will need to recreate their display list to account for this
// new child display list.
invalidateParentCaches();
}
- final HardwareCanvas canvas = mDisplayList.start();
+ boolean caching = false;
+ final HardwareCanvas canvas = displayList.start();
int restoreCount = 0;
- try {
- int width = mRight - mLeft;
- int height = mBottom - mTop;
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ try {
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
+ int layerType = (
+ !(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ?
+ getLayerType() : LAYER_TYPE_NONE;
+ if (!isLayer && layerType == LAYER_TYPE_HARDWARE && USE_DISPLAY_LIST_PROPERTIES) {
+ final HardwareLayer layer = getHardwareLayer();
+ if (layer != null && layer.isValid()) {
+ canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+ } else {
+ canvas.saveLayer(0, 0,
+ mRight - mLeft, mBottom - mTop, mLayerPaint,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ }
+ caching = true;
+ } else {
- computeScroll();
+ computeScroll();
- restoreCount = canvas.save();
- canvas.translate(-mScrollX, -mScrollY);
- mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
- mPrivateFlags &= ~DIRTY_MASK;
+ if (!USE_DISPLAY_LIST_PROPERTIES) {
+ restoreCount = canvas.save();
+ }
+ canvas.translate(-mScrollX, -mScrollY);
+ if (!isLayer) {
+ mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~DIRTY_MASK;
+ }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- dispatchDraw(canvas);
- } else {
- draw(canvas);
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ dispatchDraw(canvas);
+ } else {
+ draw(canvas);
+ }
}
} finally {
- canvas.restoreToCount(restoreCount);
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+ canvas.restoreToCount(restoreCount);
+ }
canvas.onPostDraw();
- mDisplayList.end();
+ displayList.end();
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+ displayList.setCaching(caching);
+ }
+ if (isLayer && USE_DISPLAY_LIST_PROPERTIES) {
+ displayList.setLeftTopRightBottom(0, 0, width, height);
+ } else {
+ setDisplayListProperties(displayList);
+ }
}
- } else {
+ } else if (!isLayer) {
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
mPrivateFlags &= ~DIRTY_MASK;
}
+ return displayList;
+ }
+
+ /**
+ * Get the DisplayList for the HardwareLayer
+ *
+ * @param layer The HardwareLayer whose DisplayList we want
+ * @return A DisplayList fopr the specified HardwareLayer
+ */
+ private DisplayList getHardwareLayerDisplayList(HardwareLayer layer) {
+ DisplayList displayList = getDisplayList(layer.getDisplayList(), true);
+ layer.setDisplayList(displayList);
+ return displayList;
+ }
+
+
+ /**
+ * <p>Returns a display list that can be used to draw this view again
+ * without executing its draw method.</p>
+ *
+ * @return A DisplayList ready to replay, or null if caching is not enabled.
+ *
+ * @hide
+ */
+ public DisplayList getDisplayList() {
+ mDisplayList = getDisplayList(mDisplayList, false);
return mDisplayList;
}
@@ -11151,19 +11337,57 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return more;
}
+ void setDisplayListProperties() {
+ setDisplayListProperties(mDisplayList);
+ }
+
+ /**
+ * This method is called by getDisplayList() when a display list is created or re-rendered.
+ * It sets or resets the current value of all properties on that display list (resetting is
+ * necessary when a display list is being re-created, because we need to make sure that
+ * previously-set transform values
+ */
+ void setDisplayListProperties(DisplayList displayList) {
+ if (USE_DISPLAY_LIST_PROPERTIES && displayList != null) {
+ displayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ if (mParent instanceof ViewGroup) {
+ displayList.setClipChildren(
+ (((ViewGroup)mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
+ }
+ if (mAttachInfo != null && mAttachInfo.mScalingRequired &&
+ mAttachInfo.mApplicationScale != 1.0f) {
+ displayList.setApplicationScale(1f / mAttachInfo.mApplicationScale);
+ }
+ if (mTransformationInfo != null) {
+ displayList.setTransformationInfo(mTransformationInfo.mAlpha,
+ mTransformationInfo.mTranslationX, mTransformationInfo.mTranslationY,
+ mTransformationInfo.mRotation, mTransformationInfo.mRotationX,
+ mTransformationInfo.mRotationY, mTransformationInfo.mScaleX,
+ mTransformationInfo.mScaleY);
+ displayList.setCameraDistance(getCameraDistance());
+ if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) {
+ displayList.setPivotX(getPivotX());
+ displayList.setPivotY(getPivotY());
+ }
+ }
+ }
+ }
+
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
* This draw() method is an implementation detail and is not intended to be overridden or
* to be called from anywhere else other than ViewGroup.drawChild().
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+ boolean useDisplayListProperties = USE_DISPLAY_LIST_PROPERTIES && mAttachInfo != null &&
+ mAttachInfo.mHardwareAccelerated;
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int flags = parent.mGroupFlags;
- if ((flags & parent.FLAG_CLEAR_TRANSFORMATION) == parent.FLAG_CLEAR_TRANSFORMATION) {
+ if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) {
parent.mChildTransformation.clear();
- parent.mGroupFlags &= ~parent.FLAG_CLEAR_TRANSFORMATION;
+ parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply = null;
@@ -11174,8 +11398,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int layerType = parent.mDrawLayers ? getLayerType() : LAYER_TYPE_NONE;
final boolean hardwareAccelerated = canvas.isHardwareAccelerated();
- if ((flags & parent.FLAG_CHILDREN_DRAWN_WITH_CACHE) == parent.FLAG_CHILDREN_DRAWN_WITH_CACHE ||
- (flags & parent.FLAG_ALWAYS_DRAWN_WITH_CACHE) == parent.FLAG_ALWAYS_DRAWN_WITH_CACHE) {
+ if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 ||
+ (flags & ViewGroup.FLAG_ALWAYS_DRAWN_WITH_CACHE) != 0) {
caching = true;
if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
} else {
@@ -11187,8 +11411,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
transformToApply = parent.mChildTransformation;
- } else if ((flags & parent.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
- parent.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
+ } else if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
final boolean hasTransform =
parent.getChildStaticTransformation(this, parent.mChildTransformation);
if (hasTransform) {
@@ -11238,6 +11461,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
buildDrawingCache(true);
cache = getDrawingCache(true);
break;
+ case LAYER_TYPE_HARDWARE:
+ if (useDisplayListProperties) {
+ hasDisplayList = canHaveDisplayList();
+ }
+ break;
case LAYER_TYPE_NONE:
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
@@ -11246,24 +11474,33 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
}
+ useDisplayListProperties &= hasDisplayList;
final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null && !hasDisplayList &&
layerType != LAYER_TYPE_HARDWARE;
- final int restoreTo = canvas.save();
+ int restoreTo = -1;
+ if (!useDisplayListProperties) {
+ restoreTo = canvas.save();
+ }
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
} else {
- canvas.translate(mLeft, mTop);
+ if (!useDisplayListProperties) {
+ canvas.translate(mLeft, mTop);
+ }
if (scalingRequired) {
+ if (useDisplayListProperties) {
+ restoreTo = canvas.save();
+ }
// mAttachInfo cannot be null, otherwise scalingRequired == false
final float scale = 1.0f / mAttachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
}
- float alpha = getAlpha();
+ float alpha = useDisplayListProperties ? 1 : getAlpha();
if (transformToApply != null || alpha < 1.0f || !hasIdentityMatrix()) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
@@ -11278,20 +11515,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (concatMatrix) {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
- canvas.translate(-transX, -transY);
- canvas.concat(transformToApply.getMatrix());
- canvas.translate(transX, transY);
- parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
+ if (!useDisplayListProperties) {
+ canvas.translate(-transX, -transY);
+ canvas.concat(transformToApply.getMatrix());
+ canvas.translate(transX, transY);
+ }
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1.0f) {
alpha *= transformToApply.getAlpha();
- parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
- if (!childHasIdentityMatrix) {
+ if (!childHasIdentityMatrix && !useDisplayListProperties) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
@@ -11299,20 +11538,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
if (alpha < 1.0f) {
- parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
+ parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
if (hasNoCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
- if ((flags & parent.FLAG_CLIP_CHILDREN) == parent.FLAG_CLIP_CHILDREN ||
+ if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 ||
layerType != LAYER_TYPE_NONE) {
layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG;
}
if (layerType == LAYER_TYPE_NONE) {
- final int scrollX = hasDisplayList ? 0 : sx;
- final int scrollY = hasDisplayList ? 0 : sy;
- canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft,
- scrollY + mBottom - mTop, multipliedAlpha, layerFlags);
+ if (!useDisplayListProperties) {
+ final int scrollX = hasDisplayList ? 0 : sx;
+ final int scrollY = hasDisplayList ? 0 : sy;
+ canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft,
+ scrollY + mBottom - mTop, multipliedAlpha, layerFlags);
+ }
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
@@ -11325,7 +11566,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mPrivateFlags &= ~ALPHA_SET;
}
- if ((flags & parent.FLAG_CLIP_CHILDREN) == parent.FLAG_CLIP_CHILDREN) {
+ if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN &&
+ !useDisplayListProperties) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
} else {
@@ -11350,7 +11592,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (hasNoCache) {
boolean layerRendered = false;
- if (layerType == LAYER_TYPE_HARDWARE) {
+ if (layerType == LAYER_TYPE_HARDWARE && !useDisplayListProperties) {
final HardwareLayer layer = getHardwareLayer();
if (layer != null && layer.isValid()) {
mLayerPaint.setAlpha((int) (alpha * 255));
@@ -11396,11 +11638,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
if (alpha < 1.0f) {
cachePaint.setAlpha((int) (alpha * 255));
- parent.mGroupFlags |= parent.FLAG_ALPHA_LOWER_THAN_ONE;
- } else if ((flags & parent.FLAG_ALPHA_LOWER_THAN_ONE) ==
- parent.FLAG_ALPHA_LOWER_THAN_ONE) {
+ parent.mGroupFlags |= ViewGroup.FLAG_ALPHA_LOWER_THAN_ONE;
+ } else if ((flags & ViewGroup.FLAG_ALPHA_LOWER_THAN_ONE) != 0) {
cachePaint.setAlpha(255);
- parent.mGroupFlags &= ~parent.FLAG_ALPHA_LOWER_THAN_ONE;
+ parent.mGroupFlags &= ~ViewGroup.FLAG_ALPHA_LOWER_THAN_ONE;
}
} else {
cachePaint = mLayerPaint;
@@ -11409,7 +11650,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
}
- canvas.restoreToCount(restoreTo);
+ if (restoreTo >= 0) {
+ canvas.restoreToCount(restoreTo);
+ }
if (a != null && !more) {
if (!hardwareAccelerated && !a.getFillAfter()) {
@@ -11867,6 +12110,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mTop = top;
mRight = right;
mBottom = bottom;
+ if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
+ mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ }
mPrivateFlags |= HAS_BOUNDS;
@@ -12473,9 +12719,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Returns the start padding of this view. If there are inset and enabled
- * scrollbars, this value may include the space required to display the
- * scrollbars as well.
+ * Returns the start padding of this view depending on its resolved layout direction.
+ * If there are inset and enabled scrollbars, this value may include the space
+ * required to display the scrollbars as well.
*
* @return the start padding in pixels
*/
@@ -12496,9 +12742,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Returns the end padding of this view. If there are inset and enabled
- * scrollbars, this value may include the space required to display the
- * scrollbars as well.
+ * Returns the end padding of this view depending on its resolved layout direction.
+ * If there are inset and enabled scrollbars, this value may include the space
+ * required to display the scrollbars as well.
*
* @return the end padding in pixels
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c9e0242..b8fbf17 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2679,15 +2679,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return child.draw(canvas, this, drawingTime);
}
- @Override
- public void requestLayout() {
- if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) {
- throw new IllegalStateException("Views with AccessibilityNodeProvider"
- + " can't have children.");
- }
- super.requestLayout();
- }
-
/**
*
* @param enabled True if children should be drawn with layers, false otherwise.
@@ -2736,7 +2727,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_clipChildren
*/
public void setClipChildren(boolean clipChildren) {
- setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
+ boolean previousValue = (mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN;
+ if (clipChildren != previousValue) {
+ setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+ for (int i = 0; i < mChildrenCount; ++i) {
+ View child = getChildAt(i);
+ if (child.mDisplayList != null) {
+ child.mDisplayList.setClipChildren(clipChildren);
+ }
+ }
+ }
+ }
}
/**
@@ -3098,11 +3100,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
- if (getAccessibilityNodeProvider() != null) {
- throw new IllegalStateException("Views with AccessibilityNodeProvider"
- + " can't have children.");
- }
-
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
@@ -3885,6 +3882,72 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Quick invalidation method called by View.invalidateViewProperty. This doesn't set the
+ * DRAWN flags and doesn't handle the Animation logic that the default invalidation methods
+ * do; all we want to do here is schedule a traversal with the appropriate dirty rect.
+ *
+ * @hide
+ */
+ public void invalidateChildFast(View child, final Rect dirty) {
+ ViewParent parent = this;
+
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ if (child.mLayerType != LAYER_TYPE_NONE) {
+ child.mLocalDirtyRect.union(dirty);
+ }
+
+ int left = child.mLeft;
+ int top = child.mTop;
+ if (!child.getMatrix().isIdentity()) {
+ child.transformRect(dirty);
+ }
+
+ do {
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentVG = (ViewGroup) parent;
+ parent = parentVG.invalidateChildInParentFast(left, top, dirty);
+ left = parentVG.mLeft;
+ top = parentVG.mTop;
+ } else {
+ // Reached the top; this calls into the usual invalidate method in
+ // ViewRootImpl, which schedules a traversal
+ final int[] location = attachInfo.mInvalidateChildLocation;
+ location[0] = left;
+ location[1] = top;
+ parent = parent.invalidateChildInParent(location, dirty);
+ }
+ } while (parent != null);
+ }
+ }
+
+ /**
+ * Quick invalidation method that simply transforms the dirty rect into the parent's
+ * coordinate system, pruning the invalidation if the parent has already been invalidated.
+ */
+ private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
+ if ((mPrivateFlags & DRAWN) == DRAWN ||
+ (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
+ dirty.offset(left - mScrollX, top - mScrollY);
+
+ if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 ||
+ dirty.intersect(0, 0, mRight - mLeft, mBottom - mTop)) {
+
+ if (mLayerType != LAYER_TYPE_NONE) {
+ mLocalDirtyRect.union(dirty);
+ }
+ if (!getMatrix().isIdentity()) {
+ transformRect(dirty);
+ }
+
+ return mParent;
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Offset a rectangle that is in a descendant's coordinate
* space into our coordinate space.
* @param descendant A descendant of this view
@@ -3973,6 +4036,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
+ if (USE_DISPLAY_LIST_PROPERTIES && v.mDisplayList != null) {
+ v.mDisplayList.offsetTopBottom(offset);
+ invalidateViewProperty(false, false);
+ }
}
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 0fdcd0f..623b567 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -866,6 +866,8 @@ public class ViewPropertyAnimator {
info.mAlpha = value;
break;
}
+ // TODO: optimize to set only the properties that have changed
+ mView.setDisplayListProperties();
}
/**
@@ -984,17 +986,22 @@ public class ViewPropertyAnimator {
// Shouldn't happen, but just to play it safe
return;
}
+ boolean useDisplayListProperties = View.USE_DISPLAY_LIST_PROPERTIES &&
+ mView.mDisplayList != null;
+
// alpha requires slightly different treatment than the other (transform) properties.
// The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
// logic is dependent on how the view handles an internal call to onSetAlpha().
// We track what kinds of properties are set, and how alpha is handled when it is
// set, and perform the invalidation steps appropriately.
boolean alphaHandled = false;
- mView.invalidateParentCaches();
+ if (!useDisplayListProperties) {
+ mView.invalidateParentCaches();
+ }
float fraction = animation.getAnimatedFraction();
int propertyMask = propertyBundle.mPropertyMask;
if ((propertyMask & TRANSFORM_MASK) != 0) {
- mView.invalidate(false);
+ mView.invalidateViewProperty(false, false);
}
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
@@ -1011,11 +1018,17 @@ public class ViewPropertyAnimator {
}
if ((propertyMask & TRANSFORM_MASK) != 0) {
mView.mTransformationInfo.mMatrixDirty = true;
- mView.mPrivateFlags |= View.DRAWN; // force another invalidation
+ if (!useDisplayListProperties) {
+ mView.mPrivateFlags |= View.DRAWN; // force another invalidation
+ }
}
// invalidate(false) in all cases except if alphaHandled gets set to true
// via the call to setAlphaNoInvalidation(), above
- mView.invalidate(alphaHandled);
+ if (alphaHandled) {
+ mView.invalidate(true);
+ } else {
+ mView.invalidateViewProperty(false, false);
+ }
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 72365c7..4eb70ab 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -51,6 +51,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -116,6 +117,8 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean DEBUG_FPS = false;
+ private static final boolean USE_RENDER_THREAD = false;
+
/**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
@@ -299,6 +302,8 @@ public final class ViewRootImpl implements ViewParent,
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
+ private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(24);
+
/**
* see {@link #playSoundEffect(int)}
*/
@@ -401,23 +406,27 @@ public final class ViewRootImpl implements ViewParent,
* false otherwise
*/
private static boolean isRenderThreadRequested(Context context) {
- synchronized (sRenderThreadQueryLock) {
- if (!sRenderThreadQueried) {
- final PackageManager packageManager = context.getPackageManager();
- final String packageName = context.getApplicationInfo().packageName;
- try {
- ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName,
- PackageManager.GET_META_DATA);
- if (applicationInfo.metaData != null) {
- sUseRenderThread = applicationInfo.metaData.getBoolean(
- "android.graphics.renderThread", false);
+ if (USE_RENDER_THREAD) {
+ synchronized (sRenderThreadQueryLock) {
+ if (!sRenderThreadQueried) {
+ final PackageManager packageManager = context.getPackageManager();
+ final String packageName = context.getApplicationInfo().packageName;
+ try {
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ if (applicationInfo.metaData != null) {
+ sUseRenderThread = applicationInfo.metaData.getBoolean(
+ "android.graphics.renderThread", false);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ } finally {
+ sRenderThreadQueried = true;
}
- } catch (PackageManager.NameNotFoundException e) {
- } finally {
- sRenderThreadQueried = true;
}
+ return sUseRenderThread;
}
- return sUseRenderThread;
+ } else {
+ return false;
}
}
@@ -689,7 +698,7 @@ public final class ViewRootImpl implements ViewParent,
return;
}
- boolean renderThread = isRenderThreadRequested(context);
+ final boolean renderThread = isRenderThreadRequested(context);
if (renderThread) {
Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated");
}
@@ -832,7 +841,9 @@ public final class ViewRootImpl implements ViewParent,
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
- localDirty.intersect(0, 0, mWidth, mHeight);
+ final float appScale = mAttachInfo.mApplicationScale;
+ localDirty.intersect(0, 0,
+ (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!mWillDrawSoon) {
scheduleTraversals();
@@ -960,7 +971,12 @@ public final class ViewRootImpl implements ViewParent,
}
}
- performTraversals();
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
+ try {
+ performTraversals();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
if (ViewDebug.DEBUG_LATENCY) {
long now = System.nanoTime();
@@ -1919,7 +1935,13 @@ public final class ViewRootImpl implements ViewParent,
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
- draw(fullRedrawNeeded);
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+ try {
+ draw(fullRedrawNeeded);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
if (ViewDebug.DEBUG_LATENCY) {
long now = System.nanoTime();
@@ -2198,6 +2220,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ void invalidateDisplayLists() {
+ final ArrayList<DisplayList> displayLists = mDisplayLists;
+ final int count = displayLists.size();
+
+ for (int i = 0; i < count; i++) {
+ displayLists.get(i).invalidate();
+ }
+
+ displayLists.clear();
+ }
+
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
final View.AttachInfo attachInfo = mAttachInfo;
final Rect ci = attachInfo.mContentInsets;
@@ -2504,6 +2537,7 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22;
private final static int MSG_PROCESS_INPUT_EVENTS = 23;
private final static int MSG_DISPATCH_SCREEN_STATE = 24;
+ private final static int MSG_INVALIDATE_DISPLAY_LIST = 25;
final class ViewRootHandler extends Handler {
@Override
@@ -2555,6 +2589,10 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_PROCESS_INPUT_EVENTS:
return "MSG_PROCESS_INPUT_EVENTS";
+ case MSG_DISPATCH_SCREEN_STATE:
+ return "MSG_DISPATCH_SCREEN_STATE";
+ case MSG_INVALIDATE_DISPLAY_LIST:
+ return "MSG_INVALIDATE_DISPLAY_LIST";
}
return super.getMessageName(message);
}
@@ -2765,9 +2803,13 @@ public final class ViewRootImpl implements ViewParent,
handleScreenStateChange(msg.arg1 == 1);
}
} break;
+ case MSG_INVALIDATE_DISPLAY_LIST: {
+ invalidateDisplayLists();
+ } break;
}
}
}
+
final ViewRootHandler mHandler = new ViewRootHandler();
/**
@@ -2897,17 +2939,22 @@ public final class ViewRootImpl implements ViewParent,
q.mDeliverTimeNanos = System.nanoTime();
}
- if (q.mEvent instanceof KeyEvent) {
- deliverKeyEvent(q);
- } else {
- final int source = q.mEvent.getSource();
- if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- deliverPointerEvent(q);
- } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- deliverTrackballEvent(q);
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
+ try {
+ if (q.mEvent instanceof KeyEvent) {
+ deliverKeyEvent(q);
} else {
- deliverGenericMotionEvent(q);
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ deliverPointerEvent(q);
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ deliverTrackballEvent(q);
+ } else {
+ deliverGenericMotionEvent(q);
+ }
}
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@@ -4119,6 +4166,14 @@ public final class ViewRootImpl implements ViewParent,
mInvalidateOnAnimationRunnable.addViewRect(info);
}
+ public void invalidateDisplayList(DisplayList displayList) {
+ mDisplayLists.add(displayList);
+
+ mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST);
+ Message msg = mHandler.obtainMessage(MSG_INVALIDATE_DISPLAY_LIST);
+ mHandler.sendMessage(msg);
+ }
+
public void cancelInvalidate(View view) {
mHandler.removeMessages(MSG_INVALIDATE, view);
// fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning
diff --git a/core/java/android/webkit/AutoCompletePopup.java b/core/java/android/webkit/AutoCompletePopup.java
new file mode 100644
index 0000000..b26156c
--- /dev/null
+++ b/core/java/android/webkit/AutoCompletePopup.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.AbsoluteLayout;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListAdapter;
+import android.widget.ListPopupWindow;
+
+class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener {
+ private static class AnchorView extends View {
+ AnchorView(Context context) {
+ super(context);
+ setFocusable(false);
+ }
+ }
+ private static final int AUTOFILL_FORM = 100;
+ private boolean mIsAutoFillProfileSet;
+ private Handler mHandler;
+ private int mQueryId;
+ private ListPopupWindow mPopup;
+ private Filter mFilter;
+ private CharSequence mText;
+ private ListAdapter mAdapter;
+ private View mAnchor;
+ private WebViewClassic.WebViewInputConnection mInputConnection;
+ private WebViewClassic mWebView;
+
+ public AutoCompletePopup(Context context,
+ WebViewClassic webView,
+ WebViewClassic.WebViewInputConnection inputConnection) {
+ mInputConnection = inputConnection;
+ mWebView = webView;
+ mPopup = new ListPopupWindow(context);
+ mAnchor = new AnchorView(context);
+ mWebView.getWebView().addView(mAnchor);
+ mPopup.setOnItemClickListener(this);
+ mPopup.setAnchorView(mAnchor);
+ mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AUTOFILL_FORM:
+ mWebView.autoFillForm(mQueryId);
+ break;
+ }
+ }
+ };
+ }
+
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) {
+ // special case for the back key, we do not even try to send it
+ // to the drop down list but instead, consume it immediately
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
+ if (state != null) {
+ state.startTracking(event, this);
+ }
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
+ if (state != null) {
+ state.handleUpEvent(event);
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ mPopup.dismiss();
+ return true;
+ }
+ }
+ }
+ if (mPopup.isShowing()) {
+ return mPopup.onKeyPreIme(keyCode, event);
+ }
+ return false;
+ }
+
+ public void setText(CharSequence text) {
+ mText = text;
+ if (mFilter != null) {
+ mFilter.filter(text, this);
+ }
+ }
+
+ public void setAutoFillQueryId(int queryId) {
+ mQueryId = queryId;
+ }
+
+ public void clearAdapter() {
+ mAdapter = null;
+ mFilter = null;
+ mPopup.dismiss();
+ mPopup.setAdapter(null);
+ }
+
+ public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+ mPopup.setAdapter(adapter);
+ mAdapter = adapter;
+ if (adapter != null) {
+ mFilter = adapter.getFilter();
+ mFilter.filter(mText, this);
+ } else {
+ mFilter = null;
+ }
+ resetRect();
+ }
+
+ public void resetRect() {
+ int left = mWebView.contentToViewX(mWebView.mEditTextBounds.left);
+ int right = mWebView.contentToViewX(mWebView.mEditTextBounds.right);
+ int width = right - left;
+ mPopup.setWidth(width);
+
+ int bottom = mWebView.contentToViewY(mWebView.mEditTextBounds.bottom);
+ int top = mWebView.contentToViewY(mWebView.mEditTextBounds.top);
+ int height = bottom - top;
+
+ AbsoluteLayout.LayoutParams lp =
+ (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams();
+ boolean needsUpdate = false;
+ if (null == lp) {
+ lp = new AbsoluteLayout.LayoutParams(width, height, left, top);
+ } else {
+ if ((lp.x != left) || (lp.y != top) || (lp.width != width)
+ || (lp.height != height)) {
+ needsUpdate = true;
+ lp.x = left;
+ lp.y = top;
+ lp.width = width;
+ lp.height = height;
+ }
+ }
+ if (needsUpdate) {
+ mAnchor.setLayoutParams(lp);
+ }
+ if (mPopup.isShowing()) {
+ mPopup.show(); // update its position
+ }
+ }
+
+ // AdapterView.OnItemClickListener implementation
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) {
+ mText = "";
+ pushTextToInputConnection();
+ // Blank out the text box while we wait for WebCore to fill the form.
+ if (mIsAutoFillProfileSet) {
+ // Call a webview method to tell WebCore to autofill the form.
+ mWebView.autoFillForm(mQueryId);
+ } else {
+ // There is no autofill profile setup yet and the user has
+ // elected to try and set one up. Call through to the
+ // embedder to action that.
+ mWebView.getWebChromeClient().setupAutoFill(
+ mHandler.obtainMessage(AUTOFILL_FORM));
+ }
+ } else {
+ Object selectedItem;
+ if (position < 0) {
+ selectedItem = mPopup.getSelectedItem();
+ } else {
+ selectedItem = mAdapter.getItem(position);
+ }
+ if (selectedItem != null) {
+ setText(mFilter.convertResultToString(selectedItem));
+ pushTextToInputConnection();
+ }
+ }
+ mPopup.dismiss();
+ }
+
+ public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) {
+ mIsAutoFillProfileSet = isAutoFillProfileSet;
+ }
+
+ private void pushTextToInputConnection() {
+ Editable oldText = mInputConnection.getEditable();
+ mInputConnection.setSelection(0, oldText.length());
+ mInputConnection.replaceSelection(mText);
+ mInputConnection.setSelection(mText.length(), mText.length());
+ }
+
+ @Override
+ public void onFilterComplete(int count) {
+ boolean showDropDown = (count > 0) &&
+ (mInputConnection.getIsAutoFillable() || mText.length() > 0);
+ if (showDropDown) {
+ if (!mPopup.isShowing()) {
+ // Make sure the list does not obscure the IME when shown for the first time.
+ mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
+ }
+ mPopup.show();
+ mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
+ } else {
+ mPopup.dismiss();
+ }
+ }
+}
+
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index f09e29d..dbcea71 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -742,7 +742,8 @@ class BrowserFrame extends Handler {
url = url.replaceFirst(ANDROID_ASSET, "");
try {
AssetManager assets = mContext.getAssets();
- return assets.open(url, AssetManager.ACCESS_STREAMING);
+ Uri uri = Uri.parse(url);
+ return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
} catch (IOException e) {
return null;
}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 964cf3e..6c331ac 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -45,7 +45,6 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
private int mNumberOfMatches;
private int mActiveMatchIndex;
private ActionMode mActionMode;
- private String mLastFind;
FindActionModeCallback(Context context) {
mCustomView = LayoutInflater.from(context).inflate(
@@ -134,13 +133,12 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mWebView.clearMatches();
mMatches.setVisibility(View.GONE);
mMatchesFound = false;
- mLastFind = null;
+ mWebView.findAll(null);
} else {
mMatchesFound = true;
mMatches.setVisibility(View.INVISIBLE);
mNumberOfMatches = 0;
- mLastFind = find.toString();
- mWebView.findAllAsync(mLastFind);
+ mWebView.findAllAsync(find.toString());
}
}
@@ -150,9 +148,8 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mInput.showSoftInput(mEditText, 0);
}
- public void updateMatchCount(int matchIndex, int matchCount,
- String findText) {
- if (mLastFind != null && mLastFind.equals(findText)) {
+ public void updateMatchCount(int matchIndex, int matchCount, boolean isNewFind) {
+ if (!isNewFind) {
mNumberOfMatches = matchCount;
mActiveMatchIndex = matchIndex;
updateMatchesString();
diff --git a/core/java/android/webkit/FindListener.java b/core/java/android/webkit/FindListener.java
new file mode 100644
index 0000000..124f737
--- /dev/null
+++ b/core/java/android/webkit/FindListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+/**
+ * @hide
+ */
+public interface FindListener {
+ /**
+ * Notify the host application that a find result is available.
+ *
+ * @param numberOfMatches How many matches have been found
+ * @param activeMatchOrdinal The ordinal of the currently selected match
+ * @param isDoneCounting Whether we have finished counting matches
+ */
+ public void onFindResultReceived(int numberOfMatches,
+ int activeMatchOrdinal, boolean isDoneCounting);
+}
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index fac549d..730ad08 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -112,6 +112,18 @@ public class HTML5VideoFullScreen extends HTML5VideoView
}
};
+ MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+ new MediaPlayer.OnVideoSizeChangedListener() {
+ @Override
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ }
+ }
+ };
+
private SurfaceView getSurfaceView() {
return mVideoSurfaceView;
}
@@ -150,6 +162,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
setMediaController(mc);
mPlayer.setScreenOnWhilePlaying(true);
+ mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
prepareDataAndDisplayMode(mProxy);
}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 1644b06..40c3778 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -146,6 +146,12 @@ class HTML5VideoViewProxy extends Handler
// Save the inline video info and inherit it in the full screen
int savePosition = 0;
if (mHTML5VideoView != null) {
+ // We don't allow enter full screen mode while the previous
+ // full screen video hasn't finished yet.
+ if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
+ Log.w(LOGTAG, "Try to reenter the full screen mode");
+ return;
+ }
// If we are playing the same video, then it is better to
// save the current position.
if (layerId == mHTML5VideoView.getVideoLayerId()) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a561577..5e09416 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1226,7 +1226,19 @@ public class WebView extends AbsoluteLayout
}
- /*
+ /**
+ * Register the interface to be used when a find-on-page result has become
+ * available. This will replace the current handler.
+ *
+ * @param listener An implementation of FindListener
+ * @hide
+ */
+ public void setFindListener(FindListener listener) {
+ checkThread();
+ mProvider.setFindListener(listener);
+ }
+
+ /**
* Highlight and scroll to the next occurance of String in findAll.
* Wraps the page infinitely, and scrolls. Must be called after
* calling findAll.
@@ -1238,8 +1250,9 @@ public class WebView extends AbsoluteLayout
mProvider.findNext(forward);
}
- /*
+ /**
* Find all instances of find on the page and highlight them.
+ *
* @param find String to find.
* @return int The number of occurances of the String "find"
* that were found.
@@ -1250,6 +1263,18 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Find all instances of find on the page and highlight them,
+ * asynchronously.
+ *
+ * @param find String to find.
+ * @hide
+ */
+ public void findAllAsync(String find) {
+ checkThread();
+ mProvider.findAllAsync(find);
+ }
+
+ /**
* Start an ActionMode for finding text in this WebView. Only works if this
* WebView is attached to the view system.
* @param text If non-null, will be the initial text to search for.
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 856f787..e5434ce 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -71,6 +71,8 @@ import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.view.Display;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.HardwareCanvas;
@@ -114,6 +116,7 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.OverScroller;
import android.widget.PopupWindow;
+import android.widget.Scroller;
import android.widget.TextView;
import android.widget.Toast;
@@ -375,7 +378,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* InputConnection used for ContentEditable. This captures changes
* to the text and sends them either as key strokes or text changes.
*/
- private class WebViewInputConnection extends BaseInputConnection {
+ class WebViewInputConnection extends BaseInputConnection {
// Used for mapping characters to keys typed.
private KeyCharacterMap mKeyCharacterMap;
private boolean mIsKeySentByMe;
@@ -383,11 +386,31 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private int mImeOptions;
private String mHint;
private int mMaxLength;
+ private boolean mIsAutoFillable;
+ private boolean mIsAutoCompleteEnabled;
+ private String mName;
public WebViewInputConnection() {
super(mWebView, true);
}
+ public void setAutoFillable(int queryId) {
+ mIsAutoFillable = getSettings().getAutoFillEnabled()
+ && (queryId != WebTextView.FORM_NOT_AUTOFILLABLE);
+ int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+ if (variation != EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD
+ && (mIsAutoFillable || mIsAutoCompleteEnabled)) {
+ if (mName != null && mName.length() > 0) {
+ requestFormData(mName, mFieldPointer, mIsAutoFillable,
+ mIsAutoCompleteEnabled);
+ }
+ }
+ }
+
+ public boolean getIsAutoFillable() {
+ return mIsAutoFillable;
+ }
+
@Override
public boolean sendKeyEvent(KeyEvent event) {
// Some IMEs send key events directly using sendKeyEvents.
@@ -528,9 +551,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (!initData.mIsSpellCheckEnabled) {
inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
}
- if (WebTextView.TEXT_AREA != type
- && initData.mIsTextFieldNext) {
- imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
+ if (WebTextView.TEXT_AREA != type) {
+ if (initData.mIsTextFieldNext) {
+ imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
+ }
+ if (initData.mIsTextFieldPrev) {
+ imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
+ }
}
switch (type) {
case WebTextView.NORMAL_TEXT_FIELD:
@@ -582,6 +609,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mInputType = inputType;
mImeOptions = imeOptions;
mMaxLength = initData.mMaxLength;
+ mIsAutoCompleteEnabled = initData.mIsAutoCompleteEnabled;
+ mName = initData.mName;
+ mAutoCompletePopup.clearAdapter();
}
public void setupEditorInfo(EditorInfo outAttrs) {
@@ -629,6 +659,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
REPLACE_TEXT, start, end, text.toString());
mPrivateHandler.sendMessage(replaceMessage);
}
+ if (mAutoCompletePopup != null) {
+ StringBuilder newText = new StringBuilder();
+ newText.append(editable.subSequence(0, start));
+ newText.append(text);
+ newText.append(editable.subSequence(end, editable.length()));
+ mAutoCompletePopup.setText(newText.toString());
+ }
mIsKeySentByMe = false;
}
@@ -768,6 +805,51 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
+ private class TextScrollListener extends SimpleOnGestureListener {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2,
+ float velocityX, float velocityY) {
+ int maxScrollX = mEditTextContent.width() -
+ mEditTextBounds.width();
+ int maxScrollY = mEditTextContent.height() -
+ mEditTextBounds.height();
+
+ int contentVelocityX = viewToContentDimension((int)-velocityX);
+ int contentVelocityY = viewToContentDimension((int)-velocityY);
+ mEditTextScroller.fling(-mEditTextContent.left,
+ -mEditTextContent.top,
+ contentVelocityX, contentVelocityY,
+ 0, maxScrollX, 0, maxScrollY);
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ // Scrollable edit text. Scroll it.
+ int newScrollX = deltaToTextScroll(
+ -mEditTextContent.left, mEditTextContent.width(),
+ mEditTextBounds.width(),
+ (int) distanceX);
+ int newScrollY = deltaToTextScroll(
+ -mEditTextContent.top, mEditTextContent.height(),
+ mEditTextBounds.height(),
+ (int) distanceY);
+ scrollEditText(newScrollX, newScrollY);
+ return true;
+ }
+
+ private int deltaToTextScroll(int oldScroll, int contentSize,
+ int boundsSize, int delta) {
+ int newScroll = oldScroll +
+ viewToContentDimension(delta);
+ int maxScroll = contentSize - boundsSize;
+ newScroll = Math.min(maxScroll, newScroll);
+ newScroll = Math.max(0, newScroll);
+ return newScroll;
+ }
+ }
+
// The listener to capture global layout change event.
private InnerGlobalLayoutListener mGlobalLayoutListener = null;
@@ -795,6 +877,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
WebViewInputConnection mInputConnection = null;
private int mFieldPointer;
private PastePopupWindow mPasteWindow;
+ private AutoCompletePopup mAutoCompletePopup;
+ private GestureDetector mGestureDetector;
+ Rect mEditTextBounds = new Rect();
+ Rect mEditTextContent = new Rect();
+ int mEditTextLayerId;
+ boolean mIsEditingText = false;
private static class OnTrimMemoryListener implements ComponentCallbacks2 {
private static OnTrimMemoryListener sInstance = null;
@@ -950,6 +1038,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// true when the touch movement exceeds the slop
private boolean mConfirmMove;
+ private boolean mTouchInEditText;
// if true, touch events will be first processed by WebCore, if prevent
// default is not set, the UI will continue handle them.
@@ -998,6 +1087,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// pages with the space bar, in pixels.
private static final int PAGE_SCROLL_OVERLAP = 24;
+ // Time between successive calls to text scroll fling animation
+ private static final int TEXT_SCROLL_ANIMATION_DELAY_MS = 16;
+
/**
* These prevent calling requestLayout if either dimension is fixed. This
* depends on the layout parameters and the measure specs.
@@ -1031,6 +1123,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// Used by OverScrollGlow
OverScroller mScroller;
+ Scroller mEditTextScroller;
private boolean mInOverScrollMode = false;
private static Paint mOverScrollBackground;
@@ -1104,6 +1197,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static final int SWITCH_TO_SHORTPRESS = 3;
private static final int SWITCH_TO_LONGPRESS = 4;
private static final int RELEASE_SINGLE_TAP = 5;
+ private static final int REQUEST_FORM_DATA = 6;
private static final int DRAG_HELD_MOTIONLESS = 8;
private static final int AWAKEN_SCROLL_BARS = 9;
private static final int PREVENT_DEFAULT_TIMEOUT = 10;
@@ -1156,6 +1250,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int REPLACE_TEXT = 143;
static final int CLEAR_CARET_HANDLE = 144;
static final int KEY_PRESS = 145;
+ static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
+ static final int FOCUS_NODE_CHANGED = 147;
+ static final int AUTOFILL_FORM = 148;
+ static final int ANIMATE_TEXT_SCROLL = 149;
+ static final int EDIT_TEXT_SIZE_CHANGED = 150;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1341,6 +1440,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// Used to notify listeners of a new picture.
private PictureListener mPictureListener;
+ // Used to notify listeners about find-on-page results.
+ private FindListener mFindListener;
+
/**
* Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
*/
@@ -1390,6 +1492,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
mAutoFillData = new WebViewCore.AutoFillData();
+ mGestureDetector = new GestureDetector(mContext, new TextScrollListener());
+ mEditTextScroller = new Scroller(context);
}
// === START: WebView Proxy binding ===
@@ -3560,7 +3664,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public void clearFormData() {
checkThread();
- // TODO: Implement b/6083041
+ if (mAutoCompletePopup != null) {
+ mAutoCompletePopup.clearAdapter();
+ }
}
/**
@@ -3592,6 +3698,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
+ * Register the interface to be used when a find-on-page result has become
+ * available. This will replace the current handler.
+ *
+ * @param listener An implementation of FindListener
+ */
+ public void setFindListener(FindListener listener) {
+ checkThread();
+ mFindListener = listener;
+ }
+
+ /**
* See {@link WebView#findNext(boolean)}
*/
@Override
@@ -3620,6 +3737,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
checkThread();
if (0 == mNativeClass) return 0; // client isn't initialized
mLastFind = find;
+ if (find == null) return 0;
mWebViewCore.removeMessages(EventHub.FIND_ALL);
WebViewCore.FindAllRequest request = new
WebViewCore.FindAllRequest(find);
@@ -3861,12 +3979,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private void scrollLayerTo(int x, int y) {
- if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) {
+ int dx = mScrollingLayerRect.left - x;
+ int dy = mScrollingLayerRect.top - y;
+ if (dx == 0 && y == 0) {
return;
}
if (mSelectingText) {
- int dx = mScrollingLayerRect.left - x;
- int dy = mScrollingLayerRect.top - y;
if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
mSelectCursorBase.offset(dx, dy);
}
@@ -3874,6 +3992,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mSelectCursorExtent.offset(dx, dy);
}
}
+ if (mAutoCompletePopup != null &&
+ mCurrentScrollingLayerId == mEditTextLayerId) {
+ mEditTextBounds.offset(dx, dy);
+ mAutoCompletePopup.resetRect();
+ }
nativeScrollLayer(mCurrentScrollingLayerId, x, y);
mScrollingLayerRect.left = x;
mScrollingLayerRect.top = y;
@@ -4726,6 +4849,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private void onZoomAnimationEnd() {
+ mPrivateHandler.sendEmptyMessage(RELOCATE_AUTO_COMPLETE_POPUP);
}
void onFixedLengthZoomAnimationStart() {
@@ -4800,11 +4924,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
private void getSelectionHandles(int[] handles) {
handles[0] = mSelectCursorBase.right;
- handles[1] = mSelectCursorBase.bottom -
- (mSelectCursorBase.height() / 4);
+ handles[1] = mSelectCursorBase.bottom;
handles[2] = mSelectCursorExtent.left;
- handles[3] = mSelectCursorExtent.bottom
- - (mSelectCursorExtent.height() / 4);
+ handles[3] = mSelectCursorExtent.bottom;
if (!nativeIsBaseFirst(mNativeClass)) {
int swap = handles[0];
handles[0] = handles[2];
@@ -4880,11 +5002,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (mInputConnection == null) {
mInputConnection = new WebViewInputConnection();
+ mAutoCompletePopup = new AutoCompletePopup(mContext, this,
+ mInputConnection);
}
mInputConnection.setupEditorInfo(outAttrs);
return mInputConnection;
}
+ private void relocateAutoCompletePopup() {
+ if (mAutoCompletePopup != null) {
+ mAutoCompletePopup.resetRect();
+ mAutoCompletePopup.setText(mInputConnection.getEditable());
+ }
+ }
+
/**
* Called in response to a message from webkit telling us that the soft
* keyboard should be launched.
@@ -4916,6 +5047,91 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
+ * Called by AutoCompletePopup to find saved form data associated with the
+ * textfield
+ * @param name Name of the textfield.
+ * @param nodePointer Pointer to the node of the textfield, so it can be
+ * compared to the currently focused textfield when the data is
+ * retrieved.
+ * @param autoFillable true if WebKit has determined this field is part of
+ * a form that can be auto filled.
+ * @param autoComplete true if the attribute "autocomplete" is set to true
+ * on the textfield.
+ */
+ /* package */ void requestFormData(String name, int nodePointer,
+ boolean autoFillable, boolean autoComplete) {
+ if (mWebViewCore.getSettings().getSaveFormData()) {
+ Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
+ update.arg1 = nodePointer;
+ RequestFormData updater = new RequestFormData(name, getUrl(),
+ update, autoFillable, autoComplete);
+ Thread t = new Thread(updater);
+ t.start();
+ }
+ }
+
+ /*
+ * This class requests an Adapter for the AutoCompletePopup which shows past
+ * entries stored in the database. It is a Runnable so that it can be done
+ * in its own thread, without slowing down the UI.
+ */
+ private class RequestFormData implements Runnable {
+ private String mName;
+ private String mUrl;
+ private Message mUpdateMessage;
+ private boolean mAutoFillable;
+ private boolean mAutoComplete;
+ private WebSettingsClassic mWebSettings;
+
+ public RequestFormData(String name, String url, Message msg,
+ boolean autoFillable, boolean autoComplete) {
+ mName = name;
+ mUrl = WebTextView.urlForAutoCompleteData(url);
+ mUpdateMessage = msg;
+ mAutoFillable = autoFillable;
+ mAutoComplete = autoComplete;
+ mWebSettings = getSettings();
+ }
+
+ @Override
+ public void run() {
+ ArrayList<String> pastEntries = new ArrayList<String>();
+
+ if (mAutoFillable) {
+ // Note that code inside the adapter click handler in AutoCompletePopup depends
+ // on the AutoFill item being at the top of the drop down list. If you change
+ // the order, make sure to do it there too!
+ if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) {
+ pastEntries.add(mWebView.getResources().getText(
+ com.android.internal.R.string.autofill_this_form).toString() +
+ " " +
+ mAutoFillData.getPreviewString());
+ mAutoCompletePopup.setIsAutoFillProfileSet(true);
+ } else {
+ // There is no autofill profile set up yet, so add an option that
+ // will invite the user to set their profile up.
+ pastEntries.add(mWebView.getResources().getText(
+ com.android.internal.R.string.setup_autofill).toString());
+ mAutoCompletePopup.setIsAutoFillProfileSet(false);
+ }
+ }
+
+ if (mAutoComplete) {
+ pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
+ }
+
+ if (pastEntries.size() > 0) {
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+ mContext,
+ com.android.internal.R.layout.web_text_view_dropdown,
+ pastEntries);
+ mUpdateMessage.obj = adapter;
+ mUpdateMessage.sendToTarget();
+ }
+ }
+ }
+
+ /**
* Dump the display tree to "/sdcard/displayTree.txt"
*
* @hide debug only
@@ -4990,6 +5206,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|| keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
}
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (mAutoCompletePopup != null) {
+ return mAutoCompletePopup.onKeyPreIme(keyCode, event);
+ }
+ return false;
+ }
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DebugFlags.WEB_VIEW) {
@@ -5593,6 +5816,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// However, do not update the base layer as that hasn't changed
setNewPicture(mLoadedPicture, false);
}
+ relocateAutoCompletePopup();
}
@Override
@@ -5796,6 +6020,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mPreventDefault = PREVENT_DEFAULT_NO;
mConfirmMove = false;
mInitialHitTestResult = null;
+ if (!mEditTextScroller.isFinished()) {
+ mEditTextScroller.abortAnimation();
+ }
if (!mScroller.isFinished()) {
// stop the current scroll animation, but if this is
// the start of a fling, allow it to add to the current
@@ -5933,6 +6160,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
startTouch(x, y, eventTime);
+ if (mIsEditingText) {
+ mTouchInEditText = mEditTextBounds.contains(contentX, contentY);
+ mGestureDetector.onTouchEvent(ev);
+ }
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -5966,6 +6197,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
break;
+ } else if (mConfirmMove && mTouchInEditText) {
+ ViewParent parent = mWebView.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ mGestureDetector.onTouchEvent(ev);
+ break;
}
// pass the touch events from UI thread to WebCore thread
@@ -6135,6 +6373,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
case MotionEvent.ACTION_UP: {
+ mGestureDetector.onTouchEvent(ev);
+ if (mTouchInEditText && mConfirmMove) {
+ break; // We've been scrolling the edit text.
+ }
// pass the touch events from UI thread to WebCore thread
if (shouldForwardTouchEvent()) {
TouchEventData ted = new TouchEventData();
@@ -8020,6 +8262,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
break;
}
+ case REQUEST_FORM_DATA:
+ if (mFieldPointer == msg.arg1) {
+ ArrayAdapter<String> adapter = (ArrayAdapter<String>)msg.obj;
+ mAutoCompletePopup.setAdapter(adapter);
+ }
+ break;
case LONG_PRESS_CENTER:
// as this is shared by keydown and trackballdown, reset all
@@ -8174,6 +8422,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
break;
+ case FOCUS_NODE_CHANGED:
+ mIsEditingText = (msg.arg1 == mFieldPointer);
+ if (mAutoCompletePopup != null && !mIsEditingText) {
+ mAutoCompletePopup.clearAdapter();
+ }
+ // fall through to HIT_TEST_RESULT
case HIT_TEST_RESULT:
WebKitHitTest hit = (WebKitHitTest) msg.obj;
mFocusedNode = hit;
@@ -8190,11 +8444,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
case SET_AUTOFILLABLE:
mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
- // TODO: Support (b/6083041)
+ if (mInputConnection != null) {
+ mInputConnection.setAutoFillable(mAutoFillData.getQueryId());
+ mAutoCompletePopup.setAutoFillQueryId(mAutoFillData.getQueryId());
+ }
break;
case AUTOFILL_COMPLETE:
- // TODO: Support (b/6083041)
+ if (mAutoCompletePopup != null) {
+ ArrayList<String> pastEntries = new ArrayList<String>();
+ mAutoCompletePopup.setAdapter(new ArrayAdapter<String>(
+ mContext,
+ com.android.internal.R.layout.web_text_view_dropdown,
+ pastEntries));
+ }
break;
case COPY_TO_CLIPBOARD:
@@ -8208,6 +8471,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mFieldPointer = initData.mFieldPointer;
mInputConnection.initEditorInfo(initData);
mInputConnection.setTextAndKeepSelection(initData.mText);
+ mEditTextBounds.set(initData.mNodeBounds);
+ mEditTextLayerId = initData.mNodeLayerId;
+ nativeMapLayerRect(mNativeClass, mEditTextLayerId,
+ mEditTextBounds);
+ mEditTextContent.set(initData.mContentRect);
+ relocateAutoCompletePopup();
}
break;
@@ -8222,10 +8491,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
case UPDATE_MATCH_COUNT: {
- if (mFindCallback != null) {
- mFindCallback.updateMatchCount(msg.arg1, msg.arg2,
- (String) msg.obj);
- }
+ boolean isNewFind = mLastFind == null || !mLastFind.equals(msg.obj);
+ if (mFindCallback != null)
+ mFindCallback.updateMatchCount(msg.arg1, msg.arg2, isNewFind);
+ if (mFindListener != null)
+ mFindListener.onFindResultReceived(msg.arg1, msg.arg2, true);
break;
}
case CLEAR_CARET_HANDLE:
@@ -8236,6 +8506,25 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebViewCore.sendMessage(EventHub.KEY_PRESS, msg.arg1);
break;
+ case RELOCATE_AUTO_COMPLETE_POPUP:
+ relocateAutoCompletePopup();
+ break;
+
+ case AUTOFILL_FORM:
+ mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM,
+ msg.arg1, /* unused */0);
+ break;
+
+ case ANIMATE_TEXT_SCROLL:
+ computeEditTextScroll();
+ break;
+
+ case EDIT_TEXT_SIZE_CHANGED:
+ if (msg.arg1 == mFieldPointer) {
+ mEditTextContent.set((Rect)msg.obj);
+ }
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -8549,6 +8838,25 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
+ private void computeEditTextScroll() {
+ if (mEditTextScroller.computeScrollOffset()) {
+ scrollEditText(mEditTextScroller.getCurrX(),
+ mEditTextScroller.getCurrY());
+ }
+ }
+
+ private void scrollEditText(int scrollX, int scrollY) {
+ // Scrollable edit text. Scroll it.
+ float maxScrollX = (float)(mEditTextContent.width() -
+ mEditTextBounds.width());
+ float scrollPercentX = ((float)scrollX)/maxScrollX;
+ mEditTextContent.offsetTo(-scrollX, -scrollY);
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
+ scrollY, (Float)scrollPercentX);
+ mPrivateHandler.sendEmptyMessageDelayed(ANIMATE_TEXT_SCROLL,
+ TEXT_SCROLL_ANIMATION_DELAY_MS);
+ }
+
// Class used to use a dropdown for a <select> element
private class InvokeListBox implements Runnable {
// Whether the listbox allows multiple selection.
@@ -9019,7 +9327,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/*package*/ void autoFillForm(int autoFillQueryId) {
- mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM, autoFillQueryId, /* unused */0);
+ mPrivateHandler.obtainMessage(AUTOFILL_FORM, autoFillQueryId, 0)
+ .sendToTarget();
}
/* package */ ViewManager getViewManager() {
@@ -9169,4 +9478,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static native int nativeGetHandleLayerId(int instance, int handle,
Rect cursorLocation);
private static native boolean nativeIsBaseFirst(int instance);
+ private static native void nativeMapLayerRect(int instance, int layerId,
+ Rect rect);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index de30755..d784b08 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -339,10 +339,10 @@ public final class WebViewCore {
/**
* Called by JNI when the focus node changed.
*/
- private void focusNodeChanged(WebKitHitTest hitTest) {
+ private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) {
if (mWebView == null) return;
- mWebView.mPrivateHandler.obtainMessage(WebViewClassic.HIT_TEST_RESULT, hitTest)
- .sendToTarget();
+ mWebView.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED,
+ nodePointer, 0, hitTest).sendToTarget();
}
/**
@@ -951,24 +951,19 @@ public final class WebViewCore {
}
static class TextFieldInitData {
- public TextFieldInitData(int fieldPointer,
- String text, int type, boolean isSpellCheckEnabled,
- boolean isTextFieldNext, String label, int maxLength) {
- mFieldPointer = fieldPointer;
- mText = text;
- mType = type;
- mIsSpellCheckEnabled = isSpellCheckEnabled;
- mIsTextFieldNext = isTextFieldNext;
- mLabel = label;
- mMaxLength = maxLength;
- }
- int mFieldPointer;
- String mText;
- int mType;
- boolean mIsSpellCheckEnabled;
- boolean mIsTextFieldNext;
- String mLabel;
- int mMaxLength;
+ public int mFieldPointer;
+ public String mText;
+ public int mType;
+ public boolean mIsSpellCheckEnabled;
+ public boolean mIsTextFieldNext;
+ public boolean mIsTextFieldPrev;
+ public boolean mIsAutoCompleteEnabled;
+ public String mName;
+ public String mLabel;
+ public int mMaxLength;
+ public Rect mNodeBounds;
+ public int mNodeLayerId;
+ public Rect mContentRect;
}
// mAction of TouchEventData can be MotionEvent.getAction() which uses the
@@ -1919,6 +1914,11 @@ public final class WebViewCore {
mEventHub.sendMessage(Message.obtain(null, what));
}
+ void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
+ mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
+ null, what, arg1, arg2, obj));
+ }
+
void sendMessage(int what, Object obj) {
mEventHub.sendMessage(Message.obtain(null, what, obj));
}
@@ -2779,6 +2779,18 @@ public final class WebViewCore {
}
// called by JNI
+ private void updateTextSizeAndScroll(int pointer, int width, int height,
+ int scrollX, int scrollY) {
+ if (mWebView != null) {
+ Rect rect = new Rect(-scrollX, -scrollY, width - scrollX,
+ height - scrollY);
+ Message.obtain(mWebView.mPrivateHandler,
+ WebViewClassic.EDIT_TEXT_SIZE_CHANGED, pointer, 0, rect)
+ .sendToTarget();
+ }
+ }
+
+ // called by JNI
private void clearTextEntry() {
if (mWebView == null) return;
Message.obtain(mWebView.mPrivateHandler,
@@ -2786,20 +2798,17 @@ public final class WebViewCore {
}
// called by JNI
- private void initEditField(int pointer, String text, int inputType,
- boolean isSpellCheckEnabled, boolean nextFieldIsText,
- String label, int start, int end, int selectionPtr, int maxLength) {
+ private void initEditField(int start, int end, int selectionPtr,
+ TextFieldInitData initData) {
if (mWebView == null) {
return;
}
- TextFieldInitData initData = new TextFieldInitData(pointer,
- text, inputType, isSpellCheckEnabled, nextFieldIsText, label,
- maxLength);
Message.obtain(mWebView.mPrivateHandler,
WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget();
Message.obtain(mWebView.mPrivateHandler,
- WebViewClassic.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer,
- 0, new TextSelectionData(start, end, selectionPtr))
+ WebViewClassic.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID,
+ initData.mFieldPointer, 0,
+ new TextSelectionData(start, end, selectionPtr))
.sendToTarget();
}
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 2e8ad6d..9016fbc 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -191,10 +191,14 @@ public interface WebViewProvider {
public WebBackForwardList copyBackForwardList();
+ public void setFindListener(FindListener listener);
+
public void findNext(boolean forward);
public int findAll(String find);
+ public void findAllAsync(String find);
+
public boolean showFindDialog(String text, boolean showIme);
public void clearMatches();
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 5774440..057aabe 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -88,6 +88,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
+ @SuppressWarnings("UnusedDeclaration")
private static final String TAG = "AbsListView";
/**
@@ -2429,7 +2430,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final ViewTreeObserver treeObserver = getViewTreeObserver();
treeObserver.removeOnTouchModeChangeListener(this);
if (mTextFilterEnabled && mPopup != null) {
- treeObserver.removeGlobalOnLayoutListener(this);
+ treeObserver.removeOnGlobalLayoutListener(this);
mGlobalLayoutListenerAddedFilter = false;
}
@@ -2947,16 +2948,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
+ invalidate(mEdgeGlowTop.getBounds(false));
} else if (rawDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overscroll / getHeight());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
+ invalidate(mEdgeGlowBottom.getBounds(true));
}
}
}
mMotionY = y;
- invalidate();
}
mLastY = y;
}
@@ -2990,26 +2992,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
+ invalidate(mEdgeGlowTop.getBounds(false));
} else if (rawDeltaY < 0) {
mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
+ invalidate(mEdgeGlowBottom.getBounds(true));
}
- invalidate();
}
}
if (incrementalDeltaY != 0) {
// Coming back to 'real' list scrolling
- mScrollY = 0;
- invalidateParentIfNeeded();
-
- // No need to do all this work if we're not going to move anyway
- if (incrementalDeltaY != 0) {
- trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ invalidateParentIfNeeded();
}
+ trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+
mTouchMode = TOUCH_MODE_SCROLL;
// We did not scroll the full amount. Treat this essentially like the
@@ -3468,11 +3470,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int rightPadding = mListPadding.right + mGlowPaddingRight;
final int width = getWidth() - leftPadding - rightPadding;
- canvas.translate(leftPadding,
- Math.min(0, scrollY + mFirstPositionDistanceGuess));
+ int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess);
+ canvas.translate(leftPadding, edgeY);
mEdgeGlowTop.setSize(width, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
- invalidate();
+ mEdgeGlowTop.setPosition(leftPadding, edgeY);
+ invalidate(mEdgeGlowTop.getBounds(false));
}
canvas.restoreToCount(restoreCount);
}
@@ -3483,12 +3486,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int width = getWidth() - leftPadding - rightPadding;
final int height = getHeight();
- canvas.translate(-width + leftPadding,
- Math.max(height, scrollY + mLastPositionDistanceGuess));
+ int edgeX = -width + leftPadding;
+ int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess);
+ canvas.translate(edgeX, edgeY);
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width, height);
if (mEdgeGlowBottom.draw(canvas)) {
- invalidate();
+ // Account for the rotation
+ mEdgeGlowBottom.setPosition(edgeX + width, edgeY);
+ invalidate(mEdgeGlowBottom.getBounds(true));
}
canvas.restoreToCount(restoreCount);
}
@@ -3874,7 +3880,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
// Don't stop just because delta is zero (it could have been rounded)
- final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+ final boolean atEdge = trackMotionScroll(delta, delta);
+ final boolean atEnd = atEdge && (delta != 0);
if (atEnd) {
if (motionView != null) {
// Tweak the scroll for how far we overshot
@@ -3889,7 +3896,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (more && !atEnd) {
- invalidate();
+ if (atEdge) invalidate();
mLastFlingY = y;
post(this);
} else {
@@ -4431,7 +4438,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void createScrollingCache() {
- if (mScrollingCacheEnabled && !mCachingStarted) {
+ if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) {
setChildrenDrawnWithCacheEnabled(true);
setChildrenDrawingCacheEnabled(true);
mCachingStarted = mCachingActive = true;
@@ -4439,23 +4446,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void clearScrollingCache() {
- if (mClearScrollingCache == null) {
- mClearScrollingCache = new Runnable() {
- public void run() {
- if (mCachingStarted) {
- mCachingStarted = mCachingActive = false;
- setChildrenDrawnWithCacheEnabled(false);
- if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
- setChildrenDrawingCacheEnabled(false);
- }
- if (!isAlwaysDrawnWithCacheEnabled()) {
- invalidate();
+ if (!isHardwareAccelerated()) {
+ if (mClearScrollingCache == null) {
+ mClearScrollingCache = new Runnable() {
+ public void run() {
+ if (mCachingStarted) {
+ mCachingStarted = mCachingActive = false;
+ setChildrenDrawnWithCacheEnabled(false);
+ if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) {
+ setChildrenDrawingCacheEnabled(false);
+ }
+ if (!isAlwaysDrawnWithCacheEnabled()) {
+ invalidate();
+ }
}
}
- }
- };
+ };
+ }
+ post(mClearScrollingCache);
}
- post(mClearScrollingCache);
}
/**
@@ -4599,14 +4608,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mRecycler.removeSkippedScrap();
}
+ // invalidate before moving the children to avoid unnecessary invalidate
+ // calls to bubble up from the children all the way to the top
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
- invalidate();
-
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
@@ -4629,7 +4642,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
- awakenScrollBars();
return false;
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index fd93980..c5066b6 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -29,8 +29,8 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -280,9 +280,7 @@ public class DatePicker extends FrameLayout {
reorderSpinners();
// set content descriptions
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- setContentDescriptions();
- }
+ setContentDescriptions();
}
/**
@@ -717,20 +715,27 @@ public class DatePicker extends FrameLayout {
private void setContentDescriptions() {
// Day
- String text = mContext.getString(R.string.date_picker_increment_day_button);
- mDaySpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.date_picker_decrement_day_button);
- mDaySpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mDaySpinner, R.id.increment,
+ R.string.date_picker_increment_day_button);
+ trySetContentDescription(mDaySpinner, R.id.decrement,
+ R.string.date_picker_decrement_day_button);
// Month
- text = mContext.getString(R.string.date_picker_increment_month_button);
- mMonthSpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.date_picker_decrement_month_button);
- mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mMonthSpinner, R.id.increment,
+ R.string.date_picker_increment_month_button);
+ trySetContentDescription(mMonthSpinner, R.id.decrement,
+ R.string.date_picker_decrement_month_button);
// Year
- text = mContext.getString(R.string.date_picker_increment_year_button);
- mYearSpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.date_picker_decrement_year_button);
- mYearSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mYearSpinner, R.id.increment,
+ R.string.date_picker_increment_year_button);
+ trySetContentDescription(mYearSpinner, R.id.decrement,
+ R.string.date_picker_decrement_year_button);
+ }
+
+ private void trySetContentDescription(View root, int viewId, int contDescResId) {
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ target.setContentDescription(mContext.getString(contDescResId));
+ }
}
private void updateInputState() {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 83aa8ba..bb4a4cf 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.graphics.Rect;
import com.android.internal.R;
import android.content.Context;
@@ -45,6 +46,7 @@ import android.view.animation.Interpolator;
* {@link #draw(Canvas)} method.</p>
*/
public class EdgeEffect {
+ @SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
// Time it will take the effect to fully recede in ms
@@ -57,10 +59,7 @@ public class EdgeEffect {
private static final int PULL_DECAY_TIME = 1000;
private static final float MAX_ALPHA = 1.f;
- private static final float HELD_EDGE_ALPHA = 0.7f;
private static final float HELD_EDGE_SCALE_Y = 0.5f;
- private static final float HELD_GLOW_ALPHA = 0.5f;
- private static final float HELD_GLOW_SCALE_Y = 0.5f;
private static final float MAX_GLOW_HEIGHT = 4.f;
@@ -76,7 +75,9 @@ public class EdgeEffect {
private final Drawable mGlow;
private int mWidth;
private int mHeight;
- private final int MIN_WIDTH = 300;
+ private int mX;
+ private int mY;
+ private static final int MIN_WIDTH = 300;
private final int mMinWidth;
private float mEdgeAlpha;
@@ -119,6 +120,13 @@ public class EdgeEffect {
private int mState = STATE_IDLE;
private float mPullDistance;
+
+ private final Rect mBounds = new Rect();
+
+ private final int mEdgeHeight;
+ private final int mGlowHeight;
+ private final int mGlowWidth;
+ private final int mMaxEffectHeight;
/**
* Construct a new EdgeEffect with a theme appropriate for the provided context.
@@ -129,6 +137,14 @@ public class EdgeEffect {
mEdge = res.getDrawable(R.drawable.overscroll_edge);
mGlow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdgeHeight = mEdge.getIntrinsicHeight();
+ mGlowHeight = mGlow.getIntrinsicHeight();
+ mGlowWidth = mGlow.getIntrinsicWidth();
+
+ mMaxEffectHeight = (int) (Math.min(
+ mGlowHeight * MAX_GLOW_HEIGHT * mGlowHeight / mGlowWidth * 0.6f,
+ mGlowHeight * MAX_GLOW_HEIGHT) + 0.5f);
+
mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
mInterpolator = new DecelerateInterpolator();
}
@@ -145,6 +161,18 @@ public class EdgeEffect {
}
/**
+ * Set the position of this edge effect in pixels. This position is
+ * only used by {@link #getBounds(boolean)}.
+ *
+ * @param x The position of the edge effect on the X axis
+ * @param y The position of the edge effect on the Y axis
+ */
+ void setPosition(int x, int y) {
+ mX = x;
+ mY = y;
+ }
+
+ /**
* Reports if this EdgeEffect's animation is finished. If this method returns false
* after a call to {@link #draw(Canvas)} the host widget should schedule another
* drawing pass to continue the animation.
@@ -300,16 +328,11 @@ public class EdgeEffect {
public boolean draw(Canvas canvas) {
update();
- final int edgeHeight = mEdge.getIntrinsicHeight();
- final int edgeWidth = mEdge.getIntrinsicWidth();
- final int glowHeight = mGlow.getIntrinsicHeight();
- final int glowWidth = mGlow.getIntrinsicWidth();
-
mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
int glowBottom = (int) Math.min(
- glowHeight * mGlowScaleY * glowHeight/ glowWidth * 0.6f,
- glowHeight * MAX_GLOW_HEIGHT);
+ mGlowHeight * mGlowScaleY * mGlowHeight / mGlowWidth * 0.6f,
+ mGlowHeight * MAX_GLOW_HEIGHT);
if (mWidth < mMinWidth) {
// Center the glow and clip it.
int glowLeft = (mWidth - mMinWidth)/2;
@@ -323,7 +346,7 @@ public class EdgeEffect {
mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
- int edgeBottom = (int) (edgeHeight * mEdgeScaleY);
+ int edgeBottom = (int) (mEdgeHeight * mEdgeScaleY);
if (mWidth < mMinWidth) {
// Center the edge and clip it.
int edgeLeft = (mWidth - mMinWidth)/2;
@@ -334,9 +357,25 @@ public class EdgeEffect {
}
mEdge.draw(canvas);
+ if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
+ mState = STATE_IDLE;
+ }
+
return mState != STATE_IDLE;
}
+ /**
+ * Returns the bounds of the edge effect.
+ *
+ * @hide
+ */
+ public Rect getBounds(boolean reverse) {
+ mBounds.set(0, 0, mWidth, mMaxEffectHeight);
+ mBounds.offset(mX, mY - (reverse ? mMaxEffectHeight : 0));
+
+ return mBounds;
+ }
+
private void update() {
final long time = AnimationUtils.currentAnimationTimeMillis();
final float t = Math.min((time - mStartTime) / mDuration, 1.f);
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index da98884..d019d8c 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -122,10 +122,25 @@ public class FrameLayout extends ViewGroup {
}
/**
+ * Describes how the foreground is positioned.
+ *
+ * @return foreground gravity.
+ *
+ * @see #setForegroundGravity(int)
+ *
+ * @attr ref android.R.styleable#FrameLayout_foregroundGravity
+ */
+ public int getForegroundGravity() {
+ return mForegroundGravity;
+ }
+
+ /**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
*
+ * @see #getForegroundGravity()
+ *
* @attr ref android.R.styleable#FrameLayout_foregroundGravity
*/
@android.view.RemotableViewMethod
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index fc08cc5..60dd55c 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -29,8 +29,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-
import com.android.internal.R;
+import android.widget.RemoteViews.RemoteView;
import java.lang.reflect.Array;
import java.util.ArrayList;
@@ -146,6 +146,7 @@ import static java.lang.Math.min;
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
+@RemoteView
public class GridLayout extends ViewGroup {
// Public constants
@@ -234,7 +235,6 @@ public class GridLayout extends ViewGroup {
final Axis horizontalAxis = new Axis(true);
final Axis verticalAxis = new Axis(false);
- boolean layoutParamsValid = false;
int orientation = DEFAULT_ORIENTATION;
boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
int alignmentMode = DEFAULT_ALIGNMENT_MODE;
@@ -713,12 +713,10 @@ public class GridLayout extends ViewGroup {
minor = minor + minorSpan;
}
- lastLayoutParamsHashCode = computeLayoutParamsHashCode();
- invalidateStructure();
}
private void invalidateStructure() {
- layoutParamsValid = false;
+ lastLayoutParamsHashCode = UNINITIALIZED_HASH;
horizontalAxis.invalidateStructure();
verticalAxis.invalidateStructure();
// This can end up being done twice. Better twice than not at all.
@@ -742,10 +740,6 @@ public class GridLayout extends ViewGroup {
}
final LayoutParams getLayoutParams(View c) {
- if (!layoutParamsValid) {
- validateLayoutParams();
- layoutParamsValid = true;
- }
return (LayoutParams) c.getLayoutParams();
}
@@ -874,20 +868,22 @@ public class GridLayout extends ViewGroup {
return result;
}
- private void checkForLayoutParamsModification() {
- int layoutParamsHashCode = computeLayoutParamsHashCode();
- if (lastLayoutParamsHashCode != UNINITIALIZED_HASH &&
- lastLayoutParamsHashCode != layoutParamsHashCode) {
- invalidateStructure();
+ private void consistencyCheck() {
+ if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) {
+ validateLayoutParams();
+ lastLayoutParamsHashCode = computeLayoutParamsHashCode();
+ } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
Log.w(TAG, "The fields of some layout parameters were modified in between layout " +
"operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
+ invalidateStructure();
+ consistencyCheck();
}
}
// Measurement
private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
- int childWidth, int childHeight) {
+ int childWidth, int childHeight) {
int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
mPaddingLeft + mPaddingRight + getTotalMargin(child, true), childWidth);
int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
@@ -923,7 +919,7 @@ public class GridLayout extends ViewGroup {
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
- checkForLayoutParamsModification();
+ consistencyCheck();
/** If we have been called by {@link View#measure(int, int)}, one of width or height
* is likely to have changed. We must invalidate if so. */
@@ -993,7 +989,7 @@ public class GridLayout extends ViewGroup {
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- checkForLayoutParamsModification();
+ consistencyCheck();
int targetWidth = right - left;
int targetHeight = bottom - top;
@@ -1250,7 +1246,7 @@ public class GridLayout extends ViewGroup {
}
private void include(List<Arc> arcs, Interval key, MutableInt size,
- boolean ignoreIfAlreadyPresent) {
+ boolean ignoreIfAlreadyPresent) {
/*
Remove self referential links.
These appear:
@@ -1429,8 +1425,8 @@ public class GridLayout extends ViewGroup {
int dst = arc.span.max;
int value = arc.value.value;
result.append((src < dst) ?
- var + dst + " - " + var + src + " > " + value :
- var + src + " - " + var + dst + " < " + -value);
+ var + dst + "-" + var + src + ">=" + value :
+ var + src + "-" + var + dst + "<=" + -value);
}
return result.toString();
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 0dedf8b..739bcce 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -99,7 +99,7 @@ public class GridView extends AbsListView {
private final Rect mTempRect = new Rect();
public GridView(Context context) {
- super(context);
+ this(context, null);
}
public GridView(Context context, AttributeSet attrs) {
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index a1bea43..5ed005f 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -235,9 +235,24 @@ public class LinearLayout extends ViewGroup {
}
/**
+ * @return the divider Drawable that will divide each item.
+ *
+ * @see #setDividerDrawable(Drawable)
+ *
+ * @attr ref android.R.styleable#LinearLayout_divider
+ */
+ public Drawable getDividerDrawable() {
+ return mDivider;
+ }
+
+ /**
* Set a drawable to be used as a divider between items.
+ *
* @param divider Drawable that will divide each item.
+ *
* @see #setShowDividers(int)
+ *
+ * @attr ref android.R.styleable#LinearLayout_divider
*/
public void setDividerDrawable(Drawable divider) {
if (divider == mDivider) {
@@ -398,6 +413,8 @@ public class LinearLayout extends ViewGroup {
*
* @return True to measure children with a weight using the minimum
* size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
*/
public boolean isMeasureWithLargestChildEnabled() {
return mUseLargestChild;
@@ -412,6 +429,8 @@ public class LinearLayout extends ViewGroup {
*
* @param enabled True to measure children with a weight using the
* minimum size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
*/
@android.view.RemotableViewMethod
public void setMeasureWithLargestChildEnabled(boolean enabled) {
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 3335da0..506b0c0 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,10 +16,6 @@
package android.widget;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -48,22 +44,41 @@ import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* A widget that enables the user to select a number form a predefined range.
- * The widget presents an input field and up and down buttons for selecting the
- * current value. Pressing/long-pressing the up and down buttons increments and
- * decrements the current value respectively. Touching the input field shows a
- * scroll wheel, which when touched allows direct edit
- * of the current value. Sliding gestures up or down hide the buttons and the
- * input filed, show and rotate the scroll wheel. Flinging is
- * also supported. The widget enables mapping from positions to strings such
- * that, instead of the position index, the corresponding string is displayed.
+ * There are two flavors of this widget and which one is presented to the user
+ * depends on the current theme.
+ * <ul>
+ * <li>
+ * If the current theme is derived from {@link android.R.style#Theme} the widget
+ * presents the current value as an editable input field with an increment button
+ * above and a decrement button below. Long pressing the buttons allows for a quick
+ * change of the current value. Tapping on the input field allows to type in
+ * a desired value.
+ * </li>
+ * <li>
+ * If the current theme is derived from {@link android.R.style#Theme_Holo} or
+ * {@link android.R.style#Theme_Holo_Light} the widget presents the current
+ * value as an editable input field with a lesser value above and a greater
+ * value below. Tapping on the lesser or greater value selects it by animating
+ * the number axis up or down to make the chosen value current. Flinging up
+ * or down allows for multiple increments or decrements of the current value.
+ * Long pressing on the lesser and greater values also allows for a quick change
+ * of the current value. Tapping on the current value allows to type in a
+ * desired value.
+ * </li>
+ * </ul>
* <p>
* For an example of using this widget, see {@link android.widget.TimePicker}.
* </p>
@@ -74,7 +89,7 @@ public class NumberPicker extends LinearLayout {
/**
* The number of items show in the selector wheel.
*/
- public static final int SELECTOR_WHEEL_ITEM_COUNT = 5;
+ private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;
/**
* The default update interval during long press.
@@ -84,7 +99,7 @@ public class NumberPicker extends LinearLayout {
/**
* The index of the middle selector item.
*/
- private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2;
+ private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;
/**
* The coefficient by which to adjust (divide) the max fling velocity.
@@ -97,19 +112,12 @@ public class NumberPicker extends LinearLayout {
private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
/**
- * The duration of scrolling to the next/previous value while changing
- * the current value by one, i.e. increment or decrement.
+ * The duration of scrolling to the next/previous value while changing the
+ * current value by one, i.e. increment or decrement.
*/
private static final int CHANGE_CURRENT_BY_ONE_SCROLL_DURATION = 300;
/**
- * The the delay for showing the input controls after a single tap on the
- * input text.
- */
- private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration
- .getDoubleTapTimeout();
-
- /**
* The strength of fading in the top and bottom while drawing the selector.
*/
private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
@@ -120,56 +128,31 @@ public class NumberPicker extends LinearLayout {
private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
/**
- * In this state the selector wheel is not shown.
- */
- private static final int SELECTOR_WHEEL_STATE_NONE = 0;
-
- /**
- * In this state the selector wheel is small.
- */
- private static final int SELECTOR_WHEEL_STATE_SMALL = 1;
-
- /**
- * In this state the selector wheel is large.
- */
- private static final int SELECTOR_WHEEL_STATE_LARGE = 2;
-
- /**
- * The alpha of the selector wheel when it is bright.
- */
- private static final int SELECTOR_WHEEL_BRIGHT_ALPHA = 255;
-
- /**
- * The alpha of the selector wheel when it is dimmed.
+ * The default unscaled distance between the selection dividers.
*/
- private static final int SELECTOR_WHEEL_DIM_ALPHA = 60;
+ private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
/**
- * The alpha for the increment/decrement button when it is transparent.
+ * The default unscaled minimal distance for a swipe to be considered a fling.
*/
- private static final int BUTTON_ALPHA_TRANSPARENT = 0;
+ private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150;
/**
- * The alpha for the increment/decrement button when it is opaque.
+ * Coefficient for adjusting touch scroll distance.
*/
- private static final int BUTTON_ALPHA_OPAQUE = 1;
+ private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.5f;
/**
- * The property for setting the selector paint.
+ * The resource id for the default layout.
*/
- private static final String PROPERTY_SELECTOR_PAINT_ALPHA = "selectorPaintAlpha";
-
- /**
- * The property for setting the increment/decrement button alpha.
- */
- private static final String PROPERTY_BUTTON_ALPHA = "alpha";
+ private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
/**
* The numbers accepted by the input text's {@link Filter}
*/
private static final char[] DIGIT_CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
- };
+ };
/**
* Constant for unspecified size.
@@ -215,6 +198,11 @@ public class NumberPicker extends LinearLayout {
private final EditText mInputText;
/**
+ * The distance between the two selection dividers.
+ */
+ private final int mSelectionDividersDistance;
+
+ /**
* The min height of this widget.
*/
private final int mMinHeight;
@@ -245,6 +233,11 @@ public class NumberPicker extends LinearLayout {
private final int mTextSize;
/**
+ * The minimal distance for a swipe to be considered a fling.
+ */
+ private final int mMinFlingDistance;
+
+ /**
* The height of the gap between text elements if the selector wheel.
*/
private int mSelectorTextGapHeight;
@@ -297,10 +290,7 @@ public class NumberPicker extends LinearLayout {
/**
* The selector indices whose value are show by the selector.
*/
- private final int[] mSelectorIndices = new int[] {
- Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
- Integer.MIN_VALUE
- };
+ private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];
/**
* The {@link Paint} for drawing the selector.
@@ -343,25 +333,15 @@ public class NumberPicker extends LinearLayout {
private SetSelectionCommand mSetSelectionCommand;
/**
- * Handle to the reusable command for adjusting the scroller.
- */
- private AdjustScrollerCommand mAdjustScrollerCommand;
-
- /**
* Handle to the reusable command for changing the current value from long
* press by one.
*/
private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
/**
- * {@link Animator} for showing the up/down arrows.
- */
- private final AnimatorSet mShowInputControlsAnimator;
-
- /**
- * {@link Animator} for dimming the selector wheel.
+ * Command for beginning an edit of the current value via IME on long press.
*/
- private final Animator mDimSelectorWheelAnimator;
+ private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;
/**
* The Y position of the last down event.
@@ -369,24 +349,14 @@ public class NumberPicker extends LinearLayout {
private float mLastDownEventY;
/**
- * The Y position of the last motion event.
+ * The time of the last down event.
*/
- private float mLastMotionEventY;
+ private long mLastDownEventTime;
/**
- * Flag if to check for double tap and potentially start edit.
+ * The Y position of the last down or move event.
*/
- private boolean mCheckBeginEditOnUpEvent;
-
- /**
- * Flag if to adjust the selector wheel on next up event.
- */
- private boolean mAdjustScrollerOnUpEvent;
-
- /**
- * The state of the selector wheel.
- */
- private int mSelectorWheelState;
+ private float mLastDownOrMoveEventY;
/**
* Determines speed during touch scrolling.
@@ -419,9 +389,9 @@ public class NumberPicker extends LinearLayout {
private final int mSolidColor;
/**
- * Flag indicating if this widget supports flinging.
+ * Flag whether this widget has a selector wheel.
*/
- private final boolean mFlingable;
+ private final boolean mHasSelectorWheel;
/**
* Divider for showing item to be selected while scrolling
@@ -434,29 +404,40 @@ public class NumberPicker extends LinearLayout {
private final int mSelectionDividerHeight;
/**
- * Reusable {@link Rect} instance.
+ * The current scroll state of the number picker.
*/
- private final Rect mTempRect = new Rect();
+ private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
- * The current scroll state of the number picker.
+ * Flag whether to ignore move events - we ignore such when we show in IME
+ * to prevent the content from scrolling.
*/
- private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ private boolean mIngonreMoveEvents;
/**
- * The duration of the animation for showing the input controls.
+ * Flag whether to show soft input on tap.
*/
- private final long mShowInputControlsAnimimationDuration;
+ private boolean mShowSoftInputOnTap;
/**
- * Flag whether the scoll wheel and the fading edges have been initialized.
+ * The top of the top selection divider.
*/
- private boolean mScrollWheelAndFadingEdgesInitialized;
+ private int mTopSelectionDividerTop;
/**
- * The time of the last up event.
+ * The bottom of the bottom selection divider.
*/
- private long mLastUpEventTimeMillis;
+ private int mBottomSelectionDividerBottom;
+
+ /**
+ * The virtual id of the last hovered child.
+ */
+ private int mLastHoveredChildVirtualViewId;
+
+ /**
+ * Provider to report to clients the semantic structure of this widget.
+ */
+ private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
/**
* Interface to listen for changes of the current value.
@@ -484,7 +465,7 @@ public class NumberPicker extends LinearLayout {
public static int SCROLL_STATE_IDLE = 0;
/**
- * The user is scrolling using touch, and their finger is still on the screen.
+ * The user is scrolling using touch, and his finger is still on the screen.
*/
public static int SCROLL_STATE_TOUCH_SCROLL = 1;
@@ -549,58 +530,78 @@ public class NumberPicker extends LinearLayout {
super(context, attrs, defStyle);
// process style attributes
- TypedArray attributesArray = context.obtainStyledAttributes(attrs,
- R.styleable.NumberPicker, defStyle, 0);
+ TypedArray attributesArray = context.obtainStyledAttributes(
+ attrs, R.styleable.NumberPicker, defStyle, 0);
+ final int layoutResId = attributesArray.getResourceId(
+ R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
+
+ mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
+
mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
- mFlingable = attributesArray.getBoolean(R.styleable.NumberPicker_flingable, true);
+
mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
- int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
+
+ final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
getResources().getDisplayMetrics());
mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
+
+ final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
+ getResources().getDisplayMetrics());
+ mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
+ R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
+
+ final int defMinFlingDistance = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE,
+ getResources().getDisplayMetrics());
+ mMinFlingDistance = attributesArray.getDimensionPixelSize(
+ R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance);
+
mMinHeight = attributesArray.getDimensionPixelSize(
R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
+
mMaxHeight = attributesArray.getDimensionPixelSize(
R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
&& mMinHeight > mMaxHeight) {
throw new IllegalArgumentException("minHeight > maxHeight");
}
- mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMinWidth,
- SIZE_UNSPECIFIED);
- mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMaxWidth,
- SIZE_UNSPECIFIED);
+
+ mMinWidth = attributesArray.getDimensionPixelSize(
+ R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
+
+ mMaxWidth = attributesArray.getDimensionPixelSize(
+ R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
&& mMinWidth > mMaxWidth) {
throw new IllegalArgumentException("minWidth > maxWidth");
}
+
mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
- attributesArray.recycle();
- mShowInputControlsAnimimationDuration = getResources().getInteger(
- R.integer.config_longAnimTime);
+ attributesArray.recycle();
// By default Linearlayout that we extend is not drawn. This is
// its draw() method is not called but dispatchDraw() is called
// directly (see ViewGroup.drawChild()). However, this class uses
// the fading edge effect implemented by View and we need our
// draw() method to be called. Therefore, we declare we will draw.
- setWillNotDraw(false);
- setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE);
+ setWillNotDraw(!mHasSelectorWheel);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.number_picker, this, true);
+ inflater.inflate(layoutResId, this, true);
OnClickListener onClickListener = new OnClickListener() {
public void onClick(View v) {
hideSoftInput();
mInputText.clearFocus();
if (v.getId() == R.id.increment) {
- changeCurrentByOne(true);
+ changeValueByOne(true);
} else {
- changeCurrentByOne(false);
+ changeValueByOne(false);
}
}
};
@@ -610,23 +611,31 @@ public class NumberPicker extends LinearLayout {
hideSoftInput();
mInputText.clearFocus();
if (v.getId() == R.id.increment) {
- postChangeCurrentByOneFromLongPress(true);
+ postChangeCurrentByOneFromLongPress(true, 0);
} else {
- postChangeCurrentByOneFromLongPress(false);
+ postChangeCurrentByOneFromLongPress(false, 0);
}
return true;
}
};
// increment button
- mIncrementButton = (ImageButton) findViewById(R.id.increment);
- mIncrementButton.setOnClickListener(onClickListener);
- mIncrementButton.setOnLongClickListener(onLongClickListener);
+ if (!mHasSelectorWheel) {
+ mIncrementButton = (ImageButton) findViewById(R.id.increment);
+ mIncrementButton.setOnClickListener(onClickListener);
+ mIncrementButton.setOnLongClickListener(onLongClickListener);
+ } else {
+ mIncrementButton = null;
+ }
// decrement button
- mDecrementButton = (ImageButton) findViewById(R.id.decrement);
- mDecrementButton.setOnClickListener(onClickListener);
- mDecrementButton.setOnLongClickListener(onLongClickListener);
+ if (!mHasSelectorWheel) {
+ mDecrementButton = (ImageButton) findViewById(R.id.decrement);
+ mDecrementButton.setOnClickListener(onClickListener);
+ mDecrementButton.setOnLongClickListener(onLongClickListener);
+ } else {
+ mDecrementButton = null;
+ }
// input text
mInputText = (EditText) findViewById(R.id.numberpicker_input);
@@ -648,7 +657,6 @@ public class NumberPicker extends LinearLayout {
mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
// initialize constants
- mTouchSlop = ViewConfiguration.getTapTimeout();
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
@@ -667,69 +675,22 @@ public class NumberPicker extends LinearLayout {
paint.setColor(color);
mSelectorWheelPaint = paint;
- // create the animator for showing the input controls
- mDimSelectorWheelAnimator = ObjectAnimator.ofInt(this, PROPERTY_SELECTOR_PAINT_ALPHA,
- SELECTOR_WHEEL_BRIGHT_ALPHA, SELECTOR_WHEEL_DIM_ALPHA);
- final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
- PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
- final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
- PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
- mShowInputControlsAnimator = new AnimatorSet();
- mShowInputControlsAnimator.playTogether(mDimSelectorWheelAnimator, showIncrementButton,
- showDecrementButton);
- mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCanceled = false;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCanceled) {
- // if canceled => we still want the wheel drawn
- setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
- }
- mCanceled = false;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- if (mShowInputControlsAnimator.isRunning()) {
- mCanceled = true;
- }
- }
- });
-
// create the fling and adjust scrollers
mFlingScroller = new Scroller(getContext(), null, true);
mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
updateInputTextView();
- updateIncrementAndDecrementButtonsVisibilityState();
-
- if (mFlingable) {
- if (isInEditMode()) {
- setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
- } else {
- // Start with shown selector wheel and hidden controls. When made
- // visible hide the selector and fade-in the controls to suggest
- // fling interaction.
- setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
- hideInputControls();
- }
- }
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mHasSelectorWheel) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
final int msrdWdth = getMeasuredWidth();
final int msrdHght = getMeasuredHeight();
- // Increment button at the top.
- final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
- final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
- final int incrBtnTop = 0;
- final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
- final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
- mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
-
// Input text centered horizontally.
final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
@@ -739,24 +700,23 @@ public class NumberPicker extends LinearLayout {
final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
- // Decrement button at the top.
- final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
- final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
- final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
- final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
- final int decrBtnBottom = msrdHght;
- mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
-
- if (!mScrollWheelAndFadingEdgesInitialized) {
- mScrollWheelAndFadingEdgesInitialized = true;
+ if (changed) {
// need to do all this when we know our size
initializeSelectorWheel();
initializeFadingEdges();
+ mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
+ - mSelectionDividerHeight;
+ mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
+ + mSelectionDividersDistance;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!mHasSelectorWheel) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
// Try greedily to fit the max width and height.
final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
@@ -769,120 +729,143 @@ public class NumberPicker extends LinearLayout {
setMeasuredDimension(widthSize, heightSize);
}
+ /**
+ * Move to the final position of a scroller. Ensures to force finish the scroller
+ * and if it is not at its final position a scroll of the selector wheel is
+ * performed to fast forward to the final position.
+ *
+ * @param scroller The scroller to whose final position to get.
+ * @return True of the a move was performed, i.e. the scroller was not in final position.
+ */
+ private boolean moveToFinalScrollerPosition(Scroller scroller) {
+ scroller.forceFinished(true);
+ int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
+ int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
+ int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
+ if (overshootAdjustment != 0) {
+ if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
+ if (overshootAdjustment > 0) {
+ overshootAdjustment -= mSelectorElementHeight;
+ } else {
+ overshootAdjustment += mSelectorElementHeight;
+ }
+ }
+ amountToScroll += overshootAdjustment;
+ scrollBy(0, amountToScroll);
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (!isEnabled() || !mFlingable) {
+ if (!mHasSelectorWheel || !isEnabled()) {
return false;
}
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mLastMotionEventY = mLastDownEventY = event.getY();
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
removeAllCallbacks();
- mShowInputControlsAnimator.cancel();
- mDimSelectorWheelAnimator.cancel();
- mCheckBeginEditOnUpEvent = false;
- mAdjustScrollerOnUpEvent = true;
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
- mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
- boolean scrollersFinished = mFlingScroller.isFinished()
- && mAdjustScroller.isFinished();
- if (!scrollersFinished) {
- mFlingScroller.forceFinished(true);
- mAdjustScroller.forceFinished(true);
- onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- }
- mCheckBeginEditOnUpEvent = scrollersFinished;
- mAdjustScrollerOnUpEvent = true;
+ mInputText.setVisibility(View.INVISIBLE);
+ mLastDownOrMoveEventY = mLastDownEventY = event.getY();
+ mLastDownEventTime = event.getEventTime();
+ mIngonreMoveEvents = false;
+ mShowSoftInputOnTap = false;
+ if (!mFlingScroller.isFinished()) {
+ mFlingScroller.forceFinished(true);
+ mAdjustScroller.forceFinished(true);
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ } else if (!mAdjustScroller.isFinished()) {
+ mFlingScroller.forceFinished(true);
+ mAdjustScroller.forceFinished(true);
+ } else if (mLastDownEventY < mTopSelectionDividerTop) {
hideSoftInput();
- hideInputControls();
- return true;
- }
- if (isEventInVisibleViewHitRect(event, mIncrementButton)
- || isEventInVisibleViewHitRect(event, mDecrementButton)) {
- return false;
- }
- mAdjustScrollerOnUpEvent = false;
- setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
- hideSoftInput();
- hideInputControls();
- return true;
- case MotionEvent.ACTION_MOVE:
- float currentMoveY = event.getY();
- int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
- if (deltaDownY > mTouchSlop) {
- mCheckBeginEditOnUpEvent = false;
- onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
+ postChangeCurrentByOneFromLongPress(
+ false, ViewConfiguration.getLongPressTimeout());
+ } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
hideSoftInput();
- hideInputControls();
- return true;
+ postChangeCurrentByOneFromLongPress(
+ true, ViewConfiguration.getLongPressTimeout());
+ } else {
+ mShowSoftInputOnTap = true;
+ postBeginSoftInputOnLongPressCommand();
}
- break;
+ return true;
+ }
}
return false;
}
@Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (!isEnabled()) {
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!isEnabled() || !mHasSelectorWheel) {
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
- mVelocityTracker.addMovement(ev);
- int action = ev.getActionMasked();
+ mVelocityTracker.addMovement(event);
+ int action = event.getActionMasked();
switch (action) {
- case MotionEvent.ACTION_MOVE:
- float currentMoveY = ev.getY();
- if (mCheckBeginEditOnUpEvent
- || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ case MotionEvent.ACTION_MOVE: {
+ if (mIngonreMoveEvents) {
+ break;
+ }
+ float currentMoveY = event.getY();
+ if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
if (deltaDownY > mTouchSlop) {
- mCheckBeginEditOnUpEvent = false;
+ removeAllCallbacks();
onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
+ } else {
+ int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY)
+ / TOUCH_SCROLL_DECELERATION_COEFFICIENT);
+ scrollBy(0, deltaMoveY);
+ invalidate();
}
- int deltaMoveY = (int) (currentMoveY - mLastMotionEventY);
- scrollBy(0, deltaMoveY);
- invalidate();
- mLastMotionEventY = currentMoveY;
- break;
- case MotionEvent.ACTION_UP:
- if (mCheckBeginEditOnUpEvent) {
- mCheckBeginEditOnUpEvent = false;
- final long deltaTapTimeMillis = ev.getEventTime() - mLastUpEventTimeMillis;
- if (deltaTapTimeMillis < ViewConfiguration.getDoubleTapTimeout()) {
- setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
- showInputControls(mShowInputControlsAnimimationDuration);
- mInputText.requestFocus();
- InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
- if (inputMethodManager != null) {
- inputMethodManager.showSoftInput(mInputText, 0);
- }
- mLastUpEventTimeMillis = ev.getEventTime();
- return true;
- }
- }
+ mLastDownOrMoveEventY = currentMoveY;
+ } break;
+ case MotionEvent.ACTION_UP: {
+ removeBeginSoftInputCommand();
+ removeChangeCurrentByOneFromLongPress();
VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity();
if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
- fling(initialVelocity);
+ int deltaMove = (int) (event.getY() - mLastDownEventY);
+ int absDeltaMoveY = Math.abs(deltaMove);
+ if (absDeltaMoveY > mMinFlingDistance) {
+ fling(initialVelocity);
+ } else {
+ changeValueByOne(deltaMove < 0);
+ }
onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
} else {
- if (mAdjustScrollerOnUpEvent) {
- if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) {
- postAdjustScrollerCommand(0);
+ int eventY = (int) event.getY();
+ int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
+ long deltaTime = event.getEventTime() - mLastDownEventTime;
+ if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
+ if (mShowSoftInputOnTap) {
+ mShowSoftInputOnTap = false;
+ showSoftInput();
+ } else {
+ int selectorIndexOffset = (eventY / mSelectorElementHeight)
+ - SELECTOR_MIDDLE_ITEM_INDEX;
+ if (selectorIndexOffset > 0) {
+ changeValueByOne(true);
+ } else if (selectorIndexOffset < 0) {
+ changeValueByOne(false);
+ }
}
} else {
- postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS);
+ ensureScrollWheelAdjusted();
}
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
- mLastUpEventTimeMillis = ev.getEventTime();
- break;
+ } break;
}
return true;
}
@@ -891,12 +874,6 @@ public class NumberPicker extends LinearLayout {
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
switch (action) {
- case MotionEvent.ACTION_MOVE:
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
- removeAllCallbacks();
- forceCompleteChangeCurrentByOneViaScroll();
- }
- break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
removeAllCallbacks();
@@ -907,27 +884,75 @@ public class NumberPicker extends LinearLayout {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- int keyCode = event.getKeyCode();
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
- removeAllCallbacks();
+ final int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ removeAllCallbacks();
+ break;
}
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- removeAllCallbacks();
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ removeAllCallbacks();
+ break;
}
return super.dispatchTrackballEvent(event);
}
@Override
- public void computeScroll() {
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
- return;
+ protected boolean dispatchHoverEvent(MotionEvent event) {
+ if (!mHasSelectorWheel) {
+ return super.dispatchHoverEvent(event);
+ }
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ final int eventY = (int) event.getY();
+ final int hoveredVirtualViewId;
+ if (eventY < mTopSelectionDividerTop) {
+ hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
+ } else if (eventY > mBottomSelectionDividerBottom) {
+ hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
+ } else {
+ hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
+ }
+ final int action = event.getActionMasked();
+ AccessibilityNodeProviderImpl provider =
+ (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER: {
+ provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+ } break;
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
+ && mLastHoveredChildVirtualViewId != View.NO_ID) {
+ provider.sendAccessibilityEventForVirtualView(
+ mLastHoveredChildVirtualViewId,
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+ }
+ } break;
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mLastHoveredChildVirtualViewId = View.NO_ID;
+ } break;
+ }
}
+ return false;
+ }
+
+ @Override
+ public void computeScroll() {
Scroller scroller = mFlingScroller;
if (scroller.isFinished()) {
scroller = mAdjustScroller;
@@ -952,16 +977,17 @@ public class NumberPicker extends LinearLayout {
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
- mIncrementButton.setEnabled(enabled);
- mDecrementButton.setEnabled(enabled);
+ if (!mHasSelectorWheel) {
+ mIncrementButton.setEnabled(enabled);
+ }
+ if (!mHasSelectorWheel) {
+ mDecrementButton.setEnabled(enabled);
+ }
mInputText.setEnabled(enabled);
}
@Override
public void scrollBy(int x, int y) {
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
- return;
- }
int[] selectorIndices = mSelectorIndices;
if (!mWrapSelectorWheel && y > 0
&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
@@ -977,7 +1003,7 @@ public class NumberPicker extends LinearLayout {
while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
mCurrentScrollOffset -= mSelectorElementHeight;
decrementSelectorIndices(selectorIndices);
- changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+ setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
mCurrentScrollOffset = mInitialScrollOffset;
}
@@ -985,7 +1011,7 @@ public class NumberPicker extends LinearLayout {
while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
mCurrentScrollOffset += mSelectorElementHeight;
incrementSelectorIndices(selectorIndices);
- changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+ setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
mCurrentScrollOffset = mInitialScrollOffset;
}
@@ -1024,8 +1050,7 @@ public class NumberPicker extends LinearLayout {
*
* @param formatter The formatter object. If formatter is <code>null</code>,
* {@link String#valueOf(int)} will be used.
- *
- * @see #setDisplayedValues(String[])
+ *@see #setDisplayedValues(String[])
*/
public void setFormatter(Formatter formatter) {
if (formatter == mFormatter) {
@@ -1068,26 +1093,35 @@ public class NumberPicker extends LinearLayout {
if (mValue == value) {
return;
}
- if (value < mMinValue) {
- value = mWrapSelectorWheel ? mMaxValue : mMinValue;
- }
- if (value > mMaxValue) {
- value = mWrapSelectorWheel ? mMinValue : mMaxValue;
- }
- mValue = value;
+ setValueInternal(value, false);
initializeSelectorWheelIndices();
- updateInputTextView();
- updateIncrementAndDecrementButtonsVisibilityState();
invalidate();
}
/**
- * Hides the soft input of it is active for the input text.
+ * Shows the soft input for its input text.
+ */
+ private void showSoftInput() {
+ InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
+ if (inputMethodManager != null) {
+ if (mHasSelectorWheel) {
+ mInputText.setVisibility(View.VISIBLE);
+ }
+ mInputText.requestFocus();
+ inputMethodManager.showSoftInput(mInputText, 0);
+ }
+ }
+
+ /**
+ * Hides the soft input if it is active for the input text.
*/
private void hideSoftInput() {
InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ if (mHasSelectorWheel) {
+ mInputText.setVisibility(View.INVISIBLE);
+ }
}
}
@@ -1151,23 +1185,23 @@ public class NumberPicker extends LinearLayout {
* wrap around the {@link NumberPicker#getMinValue()} and
* {@link NumberPicker#getMaxValue()} values.
* <p>
- * By default if the range (max - min) is more than five (the number of
- * items shown on the selector wheel) the selector wheel wrapping is
- * enabled.
+ * By default if the range (max - min) is more than {@link #SELECTOR_WHEEL_ITEM_COUNT}
+ * (the number of items shown on the selector wheel) the selector wheel
+ * wrapping is enabled.
* </p>
* <p>
- * <strong>Note:</strong> If the number of items, i.e. the range
- * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than
- * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not
- * wrap. Hence, in such a case calling this method is a NOP.
+ * <strong>Note:</strong> If the number of items, i.e. the range (
+ * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
+ * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not wrap.
+ * Hence, in such a case calling this method is a NOP.
* </p>
+ *
* @param wrapSelectorWheel Whether to wrap.
*/
public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
mWrapSelectorWheel = wrapSelectorWheel;
- updateIncrementAndDecrementButtonsVisibilityState();
}
}
@@ -1224,6 +1258,7 @@ public class NumberPicker extends LinearLayout {
initializeSelectorWheelIndices();
updateInputTextView();
tryComputeMaxWidth();
+ invalidate();
}
/**
@@ -1256,6 +1291,7 @@ public class NumberPicker extends LinearLayout {
initializeSelectorWheelIndices();
updateInputTextView();
tryComputeMaxWidth();
+ invalidate();
}
/**
@@ -1300,102 +1336,49 @@ public class NumberPicker extends LinearLayout {
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- // make sure we show the controls only the very
- // first time the user sees this widget
- if (mFlingable && !isInEditMode()) {
- // animate a bit slower the very first time
- showInputControls(mShowInputControlsAnimimationDuration * 2);
- }
- }
-
- @Override
protected void onDetachedFromWindow() {
removeAllCallbacks();
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- // There is a good reason for doing this. See comments in draw().
- }
-
- @Override
- public void draw(Canvas canvas) {
- // Dispatch draw to our children only if we are not currently running
- // the animation for simultaneously dimming the scroll wheel and
- // showing in the buttons. This class takes advantage of the View
- // implementation of fading edges effect to draw the selector wheel.
- // However, in View.draw(), the fading is applied after all the children
- // have been drawn and we do not want this fading to be applied to the
- // buttons. Therefore, we draw our children after we have completed
- // drawing ourselves.
- super.draw(canvas);
-
- // Draw our children if we are not showing the selector wheel of fading
- // it out
- if (mShowInputControlsAnimator.isRunning()
- || mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) {
- long drawTime = getDrawingTime();
- for (int i = 0, count = getChildCount(); i < count; i++) {
- View child = getChildAt(i);
- if (!child.isShown()) {
- continue;
- }
- drawChild(canvas, getChildAt(i), drawTime);
- }
- }
- }
-
- @Override
protected void onDraw(Canvas canvas) {
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
+ if (!mHasSelectorWheel) {
+ super.onDraw(canvas);
return;
}
-
float x = (mRight - mLeft) / 2;
float y = mCurrentScrollOffset;
- final int restoreCount = canvas.save();
-
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_SMALL) {
- Rect clipBounds = canvas.getClipBounds();
- clipBounds.inset(0, mSelectorElementHeight);
- canvas.clipRect(clipBounds);
- }
-
// draw the selector wheel
int[] selectorIndices = mSelectorIndices;
for (int i = 0; i < selectorIndices.length; i++) {
int selectorIndex = selectorIndices[i];
String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
- // Do not draw the middle item if input is visible since the input is shown only
- // if the wheel is static and it covers the middle item. Otherwise, if the user
- // starts editing the text via the IME he may see a dimmed version of the old
- // value intermixed with the new one.
+ // Do not draw the middle item if input is visible since the input
+ // is shown only if the wheel is static and it covers the middle
+ // item. Otherwise, if the user starts editing the text via the
+ // IME he may see a dimmed version of the old value intermixed
+ // with the new one.
if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) {
canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
}
y += mSelectorElementHeight;
}
- // draw the selection dividers (only if scrolling and drawable specified)
+ // draw the selection dividers
if (mSelectionDivider != null) {
// draw the top divider
- int topOfTopDivider =
- (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2;
+ int topOfTopDivider = mTopSelectionDividerTop;
int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
mSelectionDivider.draw(canvas);
// draw the bottom divider
- int topOfBottomDivider = topOfTopDivider + mSelectorElementHeight;
- int bottomOfBottomDivider = bottomOfTopDivider + mSelectorElementHeight;
+ int bottomOfBottomDivider = mBottomSelectionDividerBottom;
+ int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
mSelectionDivider.draw(canvas);
}
-
- canvas.restoreToCount(restoreCount);
}
@Override
@@ -1408,12 +1391,20 @@ public class NumberPicker extends LinearLayout {
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(NumberPicker.class.getName());
+ event.setScrollable(true);
+ event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
+ event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(NumberPicker.class.getName());
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ if (!mHasSelectorWheel) {
+ return super.getAccessibilityNodeProvider();
+ }
+ if (mAccessibilityNodeProvider == null) {
+ mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
+ }
+ return mAccessibilityNodeProvider;
}
/**
@@ -1442,17 +1433,17 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Utility to reconcile a desired size and state, with constraints imposed by
- * a MeasureSpec. Tries to respect the min size, unless a different size is
- * imposed by the constraints.
+ * Utility to reconcile a desired size and state, with constraints imposed
+ * by a MeasureSpec. Tries to respect the min size, unless a different size
+ * is imposed by the constraints.
*
* @param minSize The minimal desired size.
* @param measuredSize The currently measured size.
* @param measureSpec The current measure spec.
* @return The resolved size and state.
*/
- private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize,
- int measureSpec) {
+ private int resolveSizeAndStateRespectingMinSize(
+ int minSize, int measuredSize, int measureSpec) {
if (minSize != SIZE_UNSPECIFIED) {
final int desiredWidth = Math.max(minSize, measuredSize);
return resolveSizeAndState(desiredWidth, measureSpec, 0);
@@ -1462,8 +1453,8 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Resets the selector indices and clear the cached
- * string representation of these indices.
+ * Resets the selector indices and clear the cached string representation of
+ * these indices.
*/
private void initializeSelectorWheelIndices() {
mSelectorIndexToStringCache.clear();
@@ -1480,39 +1471,44 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Sets the current value of this NumberPicker, and sets mPrevious to the
- * previous value. If current is greater than mEnd less than mStart, the
- * value of mCurrent is wrapped around. Subclasses can override this to
- * change the wrapping behavior
+ * Sets the current value of this NumberPicker.
*
- * @param current the new value of the NumberPicker
+ * @param current The new value of the NumberPicker.
+ * @param notifyChange Whether to notify if the current value changed.
*/
- private void changeCurrent(int current) {
+ private void setValueInternal(int current, boolean notifyChange) {
if (mValue == current) {
return;
}
// Wrap around the values if we go past the start or end
if (mWrapSelectorWheel) {
current = getWrappedSelectorIndex(current);
+ } else {
+ current = Math.max(current, mMinValue);
+ current = Math.min(current, mMaxValue);
}
int previous = mValue;
- setValue(current);
- notifyChange(previous, current);
+ mValue = current;
+ updateInputTextView();
+ if (notifyChange) {
+ notifyChange(previous, current);
+ }
}
/**
* Changes the current value by one which is increment or
* decrement based on the passes argument.
+ * decrement the current value.
*
* @param increment True to increment, false to decrement.
*/
- private void changeCurrentByOne(boolean increment) {
- if (mFlingable) {
- mDimSelectorWheelAnimator.cancel();
+ private void changeValueByOne(boolean increment) {
+ if (mHasSelectorWheel) {
mInputText.setVisibility(View.INVISIBLE);
- mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
+ if (!moveToFinalScrollerPosition(mFlingScroller)) {
+ moveToFinalScrollerPosition(mAdjustScroller);
+ }
mPreviousScrollerY = 0;
- forceCompleteChangeCurrentByOneViaScroll();
if (increment) {
mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight,
CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
@@ -1523,81 +1519,26 @@ public class NumberPicker extends LinearLayout {
invalidate();
} else {
if (increment) {
- changeCurrent(mValue + 1);
+ setValueInternal(mValue + 1, true);
} else {
- changeCurrent(mValue - 1);
+ setValueInternal(mValue - 1, true);
}
}
}
- /**
- * Ensures that if we are in the process of changing the current value
- * by one via scrolling the scroller gets to its final state and the
- * value is updated.
- */
- private void forceCompleteChangeCurrentByOneViaScroll() {
- Scroller scroller = mFlingScroller;
- if (!scroller.isFinished()) {
- final int yBeforeAbort = scroller.getCurrY();
- scroller.abortAnimation();
- final int yDelta = scroller.getCurrY() - yBeforeAbort;
- scrollBy(0, yDelta);
- }
- }
-
- /**
- * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector
- * wheel.
- */
- @SuppressWarnings("unused")
- // Called via reflection
- private void setSelectorPaintAlpha(int alpha) {
- mSelectorWheelPaint.setAlpha(alpha);
- invalidate();
- }
-
- /**
- * @return If the <code>event</code> is in the visible <code>view</code>.
- */
- private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) {
- if (view.getVisibility() == VISIBLE) {
- view.getHitRect(mTempRect);
- return mTempRect.contains((int) event.getX(), (int) event.getY());
- }
- return false;
- }
-
- /**
- * Sets the <code>selectorWheelState</code>.
- */
- private void setSelectorWheelState(int selectorWheelState) {
- mSelectorWheelState = selectorWheelState;
- if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
- mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
- }
-
- if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE
- && AccessibilityManager.getInstance(mContext).isEnabled()) {
- AccessibilityManager.getInstance(mContext).interrupt();
- String text = mContext.getString(R.string.number_picker_increment_scroll_action);
- mInputText.setContentDescription(text);
- mInputText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- mInputText.setContentDescription(null);
- }
- }
-
private void initializeSelectorWheel() {
initializeSelectorWheelIndices();
int[] selectorIndices = mSelectorIndices;
int totalTextHeight = selectorIndices.length * mTextSize;
float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
- float textGapCount = selectorIndices.length - 1;
+ float textGapCount = selectorIndices.length;
mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
- // Ensure that the middle item is positioned the same as the text in mInputText
+ // Ensure that the middle item is positioned the same as the text in
+ // mInputText
int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
- mInitialScrollOffset = editTextTextPosition -
- (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
+ mInitialScrollOffset = editTextTextPosition
+ - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
mCurrentScrollOffset = mInitialScrollOffset;
updateInputTextView();
}
@@ -1612,16 +1553,14 @@ public class NumberPicker extends LinearLayout {
*/
private void onScrollerFinished(Scroller scroller) {
if (scroller == mFlingScroller) {
- if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
- postAdjustScrollerCommand(0);
- onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- } else {
+ if (!ensureScrollWheelAdjusted()) {
updateInputTextView();
- fadeSelectorWheel(mShowInputControlsAnimimationDuration);
}
+ onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
- updateInputTextView();
- showInputControls(mShowInputControlsAnimimationDuration);
+ if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ updateInputTextView();
+ }
}
}
@@ -1654,56 +1593,6 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Hides the input controls which is the up/down arrows and the text field.
- */
- private void hideInputControls() {
- mShowInputControlsAnimator.cancel();
- mIncrementButton.setVisibility(INVISIBLE);
- mDecrementButton.setVisibility(INVISIBLE);
- mInputText.setVisibility(INVISIBLE);
- }
-
- /**
- * Show the input controls by making them visible and animating the alpha
- * property up/down arrows.
- *
- * @param animationDuration The duration of the animation.
- */
- private void showInputControls(long animationDuration) {
- updateIncrementAndDecrementButtonsVisibilityState();
- mInputText.setVisibility(VISIBLE);
- mShowInputControlsAnimator.setDuration(animationDuration);
- mShowInputControlsAnimator.start();
- }
-
- /**
- * Fade the selector wheel via an animation.
- *
- * @param animationDuration The duration of the animation.
- */
- private void fadeSelectorWheel(long animationDuration) {
- mInputText.setVisibility(VISIBLE);
- mDimSelectorWheelAnimator.setDuration(animationDuration);
- mDimSelectorWheelAnimator.start();
- }
-
- /**
- * Updates the visibility state of the increment and decrement buttons.
- */
- private void updateIncrementAndDecrementButtonsVisibilityState() {
- if (mWrapSelectorWheel || mValue < mMaxValue) {
- mIncrementButton.setVisibility(VISIBLE);
- } else {
- mIncrementButton.setVisibility(INVISIBLE);
- }
- if (mWrapSelectorWheel || mValue > mMinValue) {
- mDecrementButton.setVisibility(VISIBLE);
- } else {
- mDecrementButton.setVisibility(INVISIBLE);
- }
- }
-
- /**
* @return The wrapped index <code>selectorIndex</code> value.
*/
private int getWrappedSelectorIndex(int selectorIndex) {
@@ -1749,8 +1638,7 @@ public class NumberPicker extends LinearLayout {
/**
* Ensures we have a cached string representation of the given <code>
- * selectorIndex</code>
- * to avoid multiple instantiations of the same string.
+ * selectorIndex</code> to avoid multiple instantiations of the same string.
*/
private void ensureCachedScrollSelectorValue(int selectorIndex) {
SparseArray<String> cache = mSelectorIndexToStringCache;
@@ -1783,7 +1671,7 @@ public class NumberPicker extends LinearLayout {
} else {
// Check the new value and ensure it's in range
int current = getSelectedPos(str.toString());
- changeCurrent(current);
+ setValueInternal(current, true);
}
}
@@ -1792,25 +1680,23 @@ public class NumberPicker extends LinearLayout {
* the string corresponding to the index specified by the current value will
* be returned. Otherwise, the formatter specified in {@link #setFormatter}
* will be used to format the number.
+ *
+ * @return Whether the text was updated.
*/
- private void updateInputTextView() {
+ private boolean updateInputTextView() {
/*
* If we don't have displayed values then use the current number else
* find the correct value in the displayed values for the current
* number.
*/
- if (mDisplayedValues == null) {
- mInputText.setText(formatNumber(mValue));
- } else {
- mInputText.setText(mDisplayedValues[mValue - mMinValue]);
+ String text = (mDisplayedValues == null) ? formatNumber(mValue)
+ : mDisplayedValues[mValue - mMinValue];
+ if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
+ mInputText.setText(text);
+ return true;
}
- mInputText.setSelection(mInputText.getText().length());
- if (mFlingable && AccessibilityManager.getInstance(mContext).isEnabled()) {
- String text = mContext.getString(R.string.number_picker_increment_scroll_mode,
- mInputText.getText());
- mInputText.setContentDescription(text);
- }
+ return false;
}
/**
@@ -1828,14 +1714,45 @@ public class NumberPicker extends LinearLayout {
*
* @param increment Whether to increment or decrement the value.
*/
- private void postChangeCurrentByOneFromLongPress(boolean increment) {
- mInputText.clearFocus();
- removeAllCallbacks();
+ private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
if (mChangeCurrentByOneFromLongPressCommand == null) {
mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
+ } else {
+ removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
+ }
+ mChangeCurrentByOneFromLongPressCommand.setStep(increment);
+ postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
+ }
+
+ /**
+ * Removes the command for changing the current value by one.
+ */
+ private void removeChangeCurrentByOneFromLongPress() {
+ if (mChangeCurrentByOneFromLongPressCommand != null) {
+ removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
+ }
+ }
+
+ /**
+ * Posts a command for beginning an edit of the current value via IME on
+ * long press.
+ */
+ private void postBeginSoftInputOnLongPressCommand() {
+ if (mBeginSoftInputOnLongPressCommand == null) {
+ mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
+ } else {
+ removeCallbacks(mBeginSoftInputOnLongPressCommand);
+ }
+ postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
+ }
+
+ /**
+ * Removes the command for beginning an edit of the current value via IME.
+ */
+ private void removeBeginSoftInputCommand() {
+ if (mBeginSoftInputOnLongPressCommand != null) {
+ removeCallbacks(mBeginSoftInputOnLongPressCommand);
}
- mChangeCurrentByOneFromLongPressCommand.setIncrement(increment);
- post(mChangeCurrentByOneFromLongPressCommand);
}
/**
@@ -1845,12 +1762,12 @@ public class NumberPicker extends LinearLayout {
if (mChangeCurrentByOneFromLongPressCommand != null) {
removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
}
- if (mAdjustScrollerCommand != null) {
- removeCallbacks(mAdjustScrollerCommand);
- }
if (mSetSelectionCommand != null) {
removeCallbacks(mSetSelectionCommand);
}
+ if (mBeginSoftInputOnLongPressCommand != null) {
+ removeCallbacks(mBeginSoftInputOnLongPressCommand);
+ }
}
/**
@@ -1888,8 +1805,7 @@ public class NumberPicker extends LinearLayout {
/**
* Posts an {@link SetSelectionCommand} from the given <code>selectionStart
- * </code> to
- * <code>selectionEnd</code>.
+ * </code> to <code>selectionEnd</code>.
*/
private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
if (mSetSelectionCommand == null) {
@@ -1903,20 +1819,6 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Posts an {@link AdjustScrollerCommand} within the given <code>
- * delayMillis</code>
- * .
- */
- private void postAdjustScrollerCommand(int delayMillis) {
- if (mAdjustScrollerCommand == null) {
- mAdjustScrollerCommand = new AdjustScrollerCommand();
- } else {
- removeCallbacks(mAdjustScrollerCommand);
- }
- postDelayed(mAdjustScrollerCommand, delayMillis);
- }
-
- /**
* Filter for accepting only valid indices or prefixes of the string
* representation of valid indices.
*/
@@ -1934,8 +1836,8 @@ public class NumberPicker extends LinearLayout {
}
@Override
- public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
- int dstart, int dend) {
+ public CharSequence filter(
+ CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (mDisplayedValues == null) {
CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
if (filtered == null) {
@@ -1981,6 +1883,27 @@ public class NumberPicker extends LinearLayout {
}
/**
+ * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
+ * middle element is in the middle of the widget.
+ *
+ * @return Whether an adjustment has been made.
+ */
+ private boolean ensureScrollWheelAdjusted() {
+ // adjust to the closest value
+ int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
+ if (deltaY != 0) {
+ mPreviousScrollerY = 0;
+ if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
+ deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
+ }
+ mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Command for setting the input text selection.
*/
class SetSelectionCommand implements Runnable {
@@ -1994,39 +1917,18 @@ public class NumberPicker extends LinearLayout {
}
/**
- * Command for adjusting the scroller to show in its center the closest of
- * the displayed items.
- */
- class AdjustScrollerCommand implements Runnable {
- public void run() {
- mPreviousScrollerY = 0;
- if (mInitialScrollOffset == mCurrentScrollOffset) {
- updateInputTextView();
- showInputControls(mShowInputControlsAnimimationDuration);
- return;
- }
- // adjust to the closest value
- int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
- if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
- deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
- }
- mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
- invalidate();
- }
- }
-
- /**
* Command for changing the current value from a long press by one.
*/
class ChangeCurrentByOneFromLongPressCommand implements Runnable {
private boolean mIncrement;
- private void setIncrement(boolean increment) {
+ private void setStep(boolean increment) {
mIncrement = increment;
}
+ @Override
public void run() {
- changeCurrentByOne(mIncrement);
+ changeValueByOne(mIncrement);
postDelayed(this, mLongPressUpdateInterval);
}
}
@@ -2048,4 +1950,248 @@ public class NumberPicker extends LinearLayout {
}
}
}
+
+ /**
+ * Command for beginning soft input on long press.
+ */
+ class BeginSoftInputOnLongPressCommand implements Runnable {
+
+ @Override
+ public void run() {
+ showSoftInput();
+ mIngonreMoveEvents = true;
+ }
+ }
+
+ class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
+ private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
+
+ private static final int VIRTUAL_VIEW_ID_INPUT = 2;
+
+ private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
+
+ private final Rect mTempRect = new Rect();
+
+ private final int[] mTempArray = new int[2];
+
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
+ mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
+ case VIRTUAL_VIEW_ID_DECREMENT:
+ return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
+ getVirtualDecrementButtonText(), mScrollX, mScrollY,
+ mScrollX + (mRight - mLeft),
+ mTopSelectionDividerTop + mSelectionDividerHeight);
+ case VIRTUAL_VIEW_ID_INPUT:
+ return createAccessibiltyNodeInfoForInputText();
+ case VIRTUAL_VIEW_ID_INCREMENT:
+ return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
+ getVirtualIncrementButtonText(), mScrollX,
+ mBottomSelectionDividerBottom - mSelectionDividerHeight,
+ mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
+ }
+ return super.createAccessibilityNodeInfo(virtualViewId);
+ }
+
+ @Override
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
+ int virtualViewId) {
+ if (TextUtils.isEmpty(searched)) {
+ return Collections.emptyList();
+ }
+ String searchedLowerCase = searched.toLowerCase();
+ List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
+ switch (virtualViewId) {
+ case View.NO_ID: {
+ findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+ VIRTUAL_VIEW_ID_DECREMENT, result);
+ findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+ VIRTUAL_VIEW_ID_INPUT, result);
+ findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+ VIRTUAL_VIEW_ID_INCREMENT, result);
+ return result;
+ }
+ case VIRTUAL_VIEW_ID_DECREMENT:
+ case VIRTUAL_VIEW_ID_INCREMENT:
+ case VIRTUAL_VIEW_ID_INPUT: {
+ findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
+ result);
+ return result;
+ }
+ }
+ return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, int virtualViewId) {
+ switch (virtualViewId) {
+ case VIRTUAL_VIEW_ID_INPUT: {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ if (!mInputText.isFocused()) {
+ return mInputText.requestFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ if (mInputText.isFocused()) {
+ mInputText.clearFocus();
+ return true;
+ }
+ } break;
+ }
+ } break;
+ }
+ return super.performAccessibilityAction(action, virtualViewId);
+ }
+
+ public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
+ switch (virtualViewId) {
+ case VIRTUAL_VIEW_ID_DECREMENT: {
+ sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+ getVirtualDecrementButtonText());
+ } break;
+ case VIRTUAL_VIEW_ID_INPUT: {
+ sendAccessibilityEventForVirtualText(eventType);
+ } break;
+ case VIRTUAL_VIEW_ID_INCREMENT: {
+ sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+ getVirtualIncrementButtonText());
+ } break;
+ }
+ }
+
+ private void sendAccessibilityEventForVirtualText(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ mInputText.onInitializeAccessibilityEvent(event);
+ mInputText.onPopulateAccessibilityEvent(event);
+ event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+ requestSendAccessibilityEvent(NumberPicker.this, event);
+ }
+
+ private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
+ String text) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setClassName(Button.class.getName());
+ event.setPackageName(mContext.getPackageName());
+ event.getText().add(text);
+ event.setEnabled(NumberPicker.this.isEnabled());
+ event.setSource(NumberPicker.this, virtualViewId);
+ requestSendAccessibilityEvent(NumberPicker.this, event);
+ }
+
+ private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
+ int virtualViewId, List<AccessibilityNodeInfo> outResult) {
+ switch (virtualViewId) {
+ case VIRTUAL_VIEW_ID_DECREMENT: {
+ String text = getVirtualDecrementButtonText();
+ if (!TextUtils.isEmpty(text)
+ && text.toString().toLowerCase().contains(searchedLowerCase)) {
+ outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
+ }
+ } return;
+ case VIRTUAL_VIEW_ID_INPUT: {
+ CharSequence text = mInputText.getText();
+ if (!TextUtils.isEmpty(text) &&
+ text.toString().toLowerCase().contains(searchedLowerCase)) {
+ outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
+ return;
+ }
+ CharSequence contentDesc = mInputText.getText();
+ if (!TextUtils.isEmpty(contentDesc) &&
+ contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
+ outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
+ return;
+ }
+ } break;
+ case VIRTUAL_VIEW_ID_INCREMENT: {
+ String text = getVirtualIncrementButtonText();
+ if (!TextUtils.isEmpty(text)
+ && text.toString().toLowerCase().contains(searchedLowerCase)) {
+ outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
+ }
+ } return;
+ }
+ }
+
+ private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
+ AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
+ info.setLongClickable(true);
+ info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+ return info;
+ }
+
+ private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
+ String text, int left, int top, int right, int bottom) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setClassName(Button.class.getName());
+ info.setPackageName(mContext.getPackageName());
+ info.setSource(NumberPicker.this, virtualViewId);
+ info.setParent(NumberPicker.this);
+ info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
+ info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+ info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
+ info.setText(text);
+ info.setClickable(true);
+ info.setLongClickable(true);
+ info.setEnabled(NumberPicker.this.isEnabled());
+ Rect boundsInParent = mTempRect;
+ boundsInParent.set(left, top, right, bottom);
+ info.setBoundsInParent(boundsInParent);
+ Rect boundsInScreen = boundsInParent;
+ int[] locationOnScreen = mTempArray;
+ getLocationOnScreen(locationOnScreen);
+ boundsInScreen.offsetTo(0, 0);
+ boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+ info.setBoundsInScreen(boundsInScreen);
+ return info;
+ }
+
+ private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
+ int right, int bottom) {
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ info.setClassName(Button.class.getName());
+ info.setPackageName(mContext.getPackageName());
+ info.setSource(NumberPicker.this);
+ info.setParent((View) getParent());
+ info.setEnabled(NumberPicker.this.isEnabled());
+ info.setScrollable(true);
+ Rect boundsInParent = mTempRect;
+ boundsInParent.set(left, top, right, bottom);
+ info.setBoundsInParent(boundsInParent);
+ Rect boundsInScreen = boundsInParent;
+ int[] locationOnScreen = mTempArray;
+ getLocationOnScreen(locationOnScreen);
+ boundsInScreen.offsetTo(0, 0);
+ boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+ info.setBoundsInScreen(boundsInScreen);
+ return info;
+ }
+
+ private String getVirtualDecrementButtonText() {
+ int value = mValue - 1;
+ if (mWrapSelectorWheel) {
+ value = getWrappedSelectorIndex(value);
+ }
+ if (value >= mMinValue) {
+ return (mDisplayedValues == null) ? formatNumber(value)
+ : mDisplayedValues[value - mMinValue];
+ }
+ return null;
+ }
+
+ private String getVirtualIncrementButtonText() {
+ int value = mValue + 1;
+ if (mWrapSelectorWheel) {
+ value = getWrappedSelectorIndex(value);
+ }
+ if (value <= mMaxValue) {
+ return (mDisplayedValues == null) ? formatNumber(value)
+ : mDisplayedValues[value - mMinValue];
+ }
+ return null;
+ }
+ }
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index e4b8f34..29cf000 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -219,6 +219,20 @@ public class RelativeLayout extends ViewGroup {
}
/**
+ * Describes how the child views are positioned.
+ *
+ * @return the gravity.
+ *
+ * @see #setGravity(int)
+ * @see android.view.Gravity
+ *
+ * @attr ref android.R.styleable#RelativeLayout_gravity
+ */
+ public int getGravity() {
+ return mGravity;
+ }
+
+ /**
* Describes how the child views are positioned. Defaults to
* <code>Gravity.LEFT | Gravity.TOP</code>.
*
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index b870cee..6331b6d 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -232,6 +232,8 @@ public class TableLayout extends LinearLayout {
* <p>Indicates whether all columns are shrinkable or not.</p>
*
* @return true if all columns are shrinkable, false otherwise
+ *
+ * @attr ref android.R.styleable#TableLayout_shrinkColumns
*/
public boolean isShrinkAllColumns() {
return mShrinkAllColumns;
@@ -252,6 +254,8 @@ public class TableLayout extends LinearLayout {
* <p>Indicates whether all columns are stretchable or not.</p>
*
* @return true if all columns are stretchable, false otherwise
+ *
+ * @attr ref android.R.styleable#TableLayout_stretchColumns
*/
public boolean isStretchAllColumns() {
return mStretchAllColumns;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b8db848..9941c95 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -139,6 +139,7 @@ import android.view.textservice.TextServicesManager;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.widget.EditableInputConnection;
@@ -1214,6 +1215,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (imm != null) imm.restartInput(this);
}
+ // Will change text color
if (mEditor != null) getEditor().invalidateTextDisplayList();
prepareCursorControllers();
@@ -2328,7 +2330,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
- if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
}
@@ -2349,6 +2350,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShadowDx = dx;
mShadowDy = dy;
+ // Will change text clip region
if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
@@ -2841,6 +2843,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (inval) {
+ // Text needs to be redrawn with the new color
if (mEditor != null) getEditor().invalidateTextDisplayList();
invalidate();
}
@@ -3332,7 +3335,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
invalidate();
}
- // Invalidate display list if hint will be used
+ // Invalidate display list if hint is currently used
if (mEditor != null && mText.length() == 0 && mHint != null) {
getEditor().invalidateTextDisplayList();
}
@@ -8274,6 +8277,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (getEditor().mPositionListener != null) {
getEditor().mPositionListener.onScrollChanged();
}
+ // Internal scroll affects the clip boundaries
getEditor().invalidateTextDisplayList();
}
}
@@ -11299,7 +11303,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
InputContentType mInputContentType;
InputMethodState mInputMethodState;
- DisplayList mTextDisplayList;
+ DisplayList[] mTextDisplayLists;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -11545,7 +11549,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void sendOnTextChanged(int start, int after) {
updateSpellCheckSpans(start, start + after, false);
- invalidateTextDisplayList();
// Hide the controllers as soon as text is modified (typing, procedural...)
// We do not hide the span controllers, since they can be added when a new text is
@@ -11702,31 +11705,91 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical,
firstLine, lastLine);
- if (mTextDisplayList == null || !mTextDisplayList.isValid()) {
- if (mTextDisplayList == null) {
- mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
- }
+ if (mTextDisplayLists == null) {
+ mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
+ }
+ if (! (layout instanceof DynamicLayout)) {
+ Log.e(LOG_TAG, "Editable TextView is not using a DynamicLayout");
+ return;
+ }
- final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
- try {
- hardwareCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- hardwareCanvas.onPreDraw(null);
- hardwareCanvas.translate(-mScrollX, -mScrollY);
- layout.drawText(hardwareCanvas, firstLine, lastLine);
- //layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
- hardwareCanvas.translate(mScrollX, mScrollY);
- } finally {
- hardwareCanvas.onPostDraw();
- mTextDisplayList.end();
+ DynamicLayout dynamicLayout = (DynamicLayout) layout;
+ int[] blockEnds = dynamicLayout.getBlockEnds();
+ int[] blockIndices = dynamicLayout.getBlockIndices();
+ final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
+
+ canvas.translate(mScrollX, mScrollY);
+ int endOfPreviousBlock = -1;
+ int searchStartIndex = 0;
+ for (int i = 0; i < numberOfBlocks; i++) {
+ int blockEnd = blockEnds[i];
+ int blockIndex = blockIndices[i];
+
+ final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX;
+ if (blockIsInvalid) {
+ blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks,
+ searchStartIndex);
+ // Dynamic layout internal block indices structure is updated from Editor
+ blockIndices[i] = blockIndex;
+ searchStartIndex = blockIndex + 1;
+ }
+
+ DisplayList blockDisplayList = mTextDisplayLists[blockIndex];
+ if (blockDisplayList == null) {
+ blockDisplayList = mTextDisplayLists[blockIndex] =
+ getHardwareRenderer().createDisplayList("Text " + blockIndex);
+ } else {
+ if (blockIsInvalid) blockDisplayList.invalidate();
+ }
+
+ if (!blockDisplayList.isValid()) {
+ final HardwareCanvas hardwareCanvas = blockDisplayList.start();
+ try {
+ hardwareCanvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ hardwareCanvas.onPreDraw(null);
+ hardwareCanvas.translate(-mScrollX, -mScrollY);
+ layout.drawText(hardwareCanvas, endOfPreviousBlock + 1, blockEnd);
+ hardwareCanvas.translate(mScrollX, mScrollY);
+ } finally {
+ hardwareCanvas.onPostDraw();
+ blockDisplayList.end();
+ if (USE_DISPLAY_LIST_PROPERTIES) {
+ blockDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+ }
+ }
}
+
+ ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, width, height, null,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ endOfPreviousBlock = blockEnd;
}
- canvas.translate(mScrollX, mScrollY);
- ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
- DisplayList.FLAG_CLIP_CHILDREN);
canvas.translate(-mScrollX, -mScrollY);
}
+ private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks,
+ int searchStartIndex) {
+ int length = mTextDisplayLists.length;
+ for (int i = searchStartIndex; i < length; i++) {
+ boolean blockIndexFound = false;
+ for (int j = 0; j < numberOfBlocks; j++) {
+ if (blockIndices[j] == i) {
+ blockIndexFound = true;
+ break;
+ }
+ }
+ if (blockIndexFound) continue;
+ return i;
+ }
+
+ // No available index found, the pool has to grow
+ int newSize = ArrayUtils.idealIntArraySize(length + 1);
+ DisplayList[] displayLists = new DisplayList[newSize];
+ System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length);
+ mTextDisplayLists = displayLists;
+ return length;
+ }
+
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
@@ -11737,7 +11800,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void invalidateTextDisplayList() {
- if (mTextDisplayList != null) mTextDisplayList.invalidate();
+ if (mTextDisplayLists != null) {
+ for (int i = 0; i < mTextDisplayLists.length; i++) {
+ if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate();
+ }
+ }
}
private void updateCursorsPositions() {
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 7eff1aa..bc88b62 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -532,21 +532,28 @@ public class TimePicker extends FrameLayout {
private void setContentDescriptions() {
// Minute
- String text = mContext.getString(R.string.time_picker_increment_minute_button);
- mMinuteSpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.time_picker_decrement_minute_button);
- mMinuteSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mMinuteSpinner, R.id.increment,
+ R.string.time_picker_increment_minute_button);
+ trySetContentDescription(mMinuteSpinner, R.id.decrement,
+ R.string.time_picker_decrement_minute_button);
// Hour
- text = mContext.getString(R.string.time_picker_increment_hour_button);
- mHourSpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.time_picker_decrement_hour_button);
- mHourSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mHourSpinner, R.id.increment,
+ R.string.time_picker_increment_hour_button);
+ trySetContentDescription(mHourSpinner, R.id.decrement,
+ R.string.time_picker_decrement_hour_button);
// AM/PM
if (mAmPmSpinner != null) {
- text = mContext.getString(R.string.time_picker_increment_set_pm_button);
- mAmPmSpinner.findViewById(R.id.increment).setContentDescription(text);
- text = mContext.getString(R.string.time_picker_decrement_set_am_button);
- mAmPmSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ trySetContentDescription(mAmPmSpinner, R.id.increment,
+ R.string.time_picker_increment_set_pm_button);
+ trySetContentDescription(mAmPmSpinner, R.id.decrement,
+ R.string.time_picker_decrement_set_am_button);
+ }
+ }
+
+ private void trySetContentDescription(View root, int viewId, int contDescResId) {
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ target.setContentDescription(mContext.getString(contDescResId));
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 53516c0..cdd2ad1 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -50,6 +50,10 @@ public class RuntimeInit {
private static volatile boolean mCrashing = false;
+ private static final native void nativeZygoteInit();
+ private static final native void nativeFinishInit();
+ private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
+
/**
* Use this to log a message when a thread exits due to an uncaught
* exception. The framework catches these for the main threads, so
@@ -91,13 +95,6 @@ public class RuntimeInit {
/* set default handler; this applies to all threads in the VM */
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
- int hasQwerty = getQwertyKeyboard();
-
- if (DEBUG) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
- if (hasQwerty == 1) {
- System.setProperty("qwerty", "1");
- }
-
/*
* Install a TimezoneGetter subclass for ZoneInfo.db
*/
@@ -235,16 +232,14 @@ public class RuntimeInit {
* Now that we're running in interpreted code, call back into native code
* to run the system.
*/
- finishInit();
+ nativeFinishInit();
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- public static final native void finishInit();
-
/**
* The main function called when started through the zygote process. This
- * could be unified with main(), if the native code in finishInit()
+ * could be unified with main(), if the native code in nativeFinishInit()
* were rationalized with Zygote startup.<p>
*
* Current recognized args:
@@ -262,7 +257,7 @@ public class RuntimeInit {
redirectLogStreams();
commonInit();
- zygoteInitNative();
+ nativeZygoteInit();
applicationInit(targetSdkVersion, argv);
}
@@ -287,6 +282,13 @@ public class RuntimeInit {
private static void applicationInit(int targetSdkVersion, String[] argv)
throws ZygoteInit.MethodAndArgsCaller {
+ // If the application calls System.exit(), terminate the process
+ // immediately without running any shutdown hooks. It is not possible to
+ // shutdown an Android application gracefully. Among other things, the
+ // Android runtime shutdown hooks close the Binder driver, which can cause
+ // leftover running threads to crash before the process actually exits.
+ nativeSetExitWithoutCleanup(true);
+
// We want to be fairly aggressive about heap utilization, to avoid
// holding on to a lot of memory that isn't needed.
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
@@ -315,24 +317,6 @@ public class RuntimeInit {
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
- public static final native void zygoteInitNative();
-
- /**
- * Returns 1 if the computer is on. If the computer isn't on, the value returned by this method is undefined.
- */
- public static final native int isComputerOn();
-
- /**
- * Turns the computer on if the computer is off. If the computer is on, the behavior of this method is undefined.
- */
- public static final native void turnComputerOn();
-
- /**
- *
- * @return 1 if the device has a qwerty keyboard
- */
- public static native int getQwertyKeyboard();
-
/**
* Report a serious error in the current process. May or may not cause
* the process to terminate (depends on system settings).
diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java
index 70c0be8..ddecbed 100644
--- a/core/java/com/android/internal/util/BitwiseOutputStream.java
+++ b/core/java/com/android/internal/util/BitwiseOutputStream.java
@@ -77,6 +77,7 @@ public class BitwiseOutputStream {
byte[] newBuf = new byte[(mPos + bits) >>> 2];
System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3);
mBuf = newBuf;
+ mEnd = newBuf.length << 3;
}
/**
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 04147ab..2564921 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -382,7 +382,10 @@ public final class MenuItemImpl implements MenuItem {
}
if (mIconResId != NO_ICON) {
- return mMenu.getResources().getDrawable(mIconResId);
+ Drawable icon = mMenu.getResources().getDrawable(mIconResId);
+ mIconResId = NO_ICON;
+ mIconDrawable = icon;
+ return icon;
}
return null;
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index d51ced1..f2b6e45 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -164,9 +164,10 @@ public class MultiWaveView extends View {
mFeedbackCount = a.getInt(R.styleable.MultiWaveView_feedbackCount,
mFeedbackCount);
mHandleDrawable = new TargetDrawable(res,
- a.getDrawable(R.styleable.MultiWaveView_handleDrawable));
+ a.peekValue(R.styleable.MultiWaveView_handleDrawable).resourceId);
mTapRadius = mHandleDrawable.getWidth()/2;
- mOuterRing = new TargetDrawable(res, a.getDrawable(R.styleable.MultiWaveView_waveDrawable));
+ mOuterRing = new TargetDrawable(res,
+ a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId);
// Read chevron animation drawables
final int chevrons[] = { R.styleable.MultiWaveView_leftChevronDrawable,
@@ -174,11 +175,12 @@ public class MultiWaveView extends View {
R.styleable.MultiWaveView_topChevronDrawable,
R.styleable.MultiWaveView_bottomChevronDrawable
};
+
for (int chevron : chevrons) {
- Drawable chevronDrawable = a.getDrawable(chevron);
+ TypedValue typedValue = a.peekValue(chevron);
for (int i = 0; i < mFeedbackCount; i++) {
mChevronDrawables.add(
- chevronDrawable != null ? new TargetDrawable(res, chevronDrawable) : null);
+ typedValue != null ? new TargetDrawable(res, typedValue.resourceId) : null);
}
}
@@ -519,8 +521,8 @@ public class MultiWaveView extends View {
int count = array.length();
ArrayList<TargetDrawable> targetDrawables = new ArrayList<TargetDrawable>(count);
for (int i = 0; i < count; i++) {
- Drawable drawable = array.getDrawable(i);
- targetDrawables.add(new TargetDrawable(res, drawable));
+ TypedValue value = array.peekValue(i);
+ targetDrawables.add(new TargetDrawable(res, value != null ? value.resourceId : 0));
}
array.recycle();
mTargetResourceId = resourceId;
@@ -679,7 +681,7 @@ public class MultiWaveView extends View {
if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
switchToState(STATE_FINISH, event.getX(), event.getY());
}
-
+
private void handleCancel(MotionEvent event) {
if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
mActiveTarget = -1; // Drop the active target if canceled.
@@ -723,7 +725,7 @@ public class MultiWaveView extends View {
float dx = limitX - target.getX();
float dy = limitY - target.getY();
float dist2 = dx*dx + dy*dy;
- if (target.isValid() && dist2 < hitRadius2 && dist2 < best) {
+ if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
activeTarget = i;
best = dist2;
}
@@ -968,4 +970,34 @@ public class MultiWaveView extends View {
array.recycle();
return targetContentDescriptions;
}
+
+ public int getResourceIdForTarget(int index) {
+ final TargetDrawable drawable = mTargetDrawables.get(index);
+ return drawable == null ? 0 : drawable.getResourceId();
+ }
+
+ public void setEnableTarget(int resourceId, boolean enabled) {
+ for (int i = 0; i < mTargetDrawables.size(); i++) {
+ final TargetDrawable target = mTargetDrawables.get(i);
+ if (target.getResourceId() == resourceId) {
+ target.setEnabled(enabled);
+ break; // should never be more than one match
+ }
+ }
+ }
+
+ /**
+ * Gets the position of a target in the array that matches the given resource.
+ * @param resourceId
+ * @return the index or -1 if not found
+ */
+ public int getTargetPosition(int resourceId) {
+ for (int i = 0; i < mTargetDrawables.size(); i++) {
+ final TargetDrawable target = mTargetDrawables.get(i);
+ if (target.getResourceId() == resourceId) {
+ return i; // should never be more than one match
+ }
+ }
+ return -1;
+ }
}
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
index aa9fa45..ec2c945 100644
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
@@ -40,6 +40,8 @@ public class TargetDrawable {
private float mScaleY = 1.0f;
private float mAlpha = 1.0f;
private Drawable mDrawable;
+ private boolean mEnabled = true;
+ private int mResourceId;
/* package */ static class DrawableWithAlpha extends Drawable {
private float mAlpha = 1.0f;
@@ -72,10 +74,8 @@ public class TargetDrawable {
}
public TargetDrawable(Resources res, int resId) {
- this(res, resId == 0 ? null : res.getDrawable(resId));
- }
-
- public TargetDrawable(Resources res, Drawable drawable) {
+ mResourceId = resId;
+ Drawable drawable = resId == 0 ? null : res.getDrawable(resId);
// Mutate the drawable so we can animate shared drawable properties.
mDrawable = drawable != null ? drawable.mutate() : null;
resizeDrawables();
@@ -122,8 +122,8 @@ public class TargetDrawable {
*
* @return
*/
- public boolean isValid() {
- return mDrawable != null;
+ public boolean isEnabled() {
+ return mDrawable != null && mEnabled;
}
/**
@@ -205,7 +205,7 @@ public class TargetDrawable {
}
public void draw(Canvas canvas) {
- if (mDrawable == null) {
+ if (mDrawable == null || !mEnabled) {
return;
}
canvas.save(Canvas.MATRIX_SAVE_FLAG);
@@ -216,4 +216,12 @@ public class TargetDrawable {
mDrawable.draw(canvas);
canvas.restore();
}
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ public int getResourceId() {
+ return mResourceId;
+ }
}