diff options
Diffstat (limited to 'core/java')
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; + } } |
