diff options
Diffstat (limited to 'core/java')
55 files changed, 1430 insertions, 696 deletions
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java index c363f04..d6fcbb1 100644 --- a/core/java/android/app/LauncherActivity.java +++ b/core/java/android/app/LauncherActivity.java @@ -18,7 +18,6 @@ package android.app; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -31,9 +30,7 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; -import android.os.AsyncTask; import android.os.Bundle; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -145,17 +142,6 @@ public abstract class LauncherActivity extends ListActivity { return view; } - private char getCandidateLetter(ResolveInfo info) { - PackageManager pm = LauncherActivity.this.getPackageManager(); - CharSequence label = info.loadLabel(pm); - - if (label == null) { - label = info.activityInfo.name; - } - - return Character.toLowerCase(label.charAt(0)); - } - private void bindView(View view, ListItem item) { TextView text = (TextView) view; text.setText(item.label); @@ -191,7 +177,6 @@ public abstract class LauncherActivity extends ListActivity { results.count = list.size(); } } else { - final PackageManager pm = LauncherActivity.this.getPackageManager(); final String prefixString = prefix.toString().toLowerCase(); ArrayList<ListItem> values = mOriginalValues; @@ -243,8 +228,6 @@ public abstract class LauncherActivity extends ListActivity { private int mIconWidth = -1; private int mIconHeight = -1; - private final Paint mPaint = new Paint(); - private final Rect mBounds = new Rect(); private final Rect mOldBounds = new Rect(); private Canvas mCanvas = new Canvas(); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 64f1ba2..5744ddc 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -835,9 +835,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // also guard against possible race conditions (late arrival after dismiss) if (mSearchable != null) { handled = doSuggestionsKey(v, keyCode, event); - if (!handled) { - handled = refocusingKeyListener(v, keyCode, event); - } } return handled; } @@ -1024,6 +1021,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @param jamQuery True means to set the query, false means to reset it to the user's choice */ private void jamSuggestionQuery(boolean jamQuery, AdapterView<?> parent, int position) { + // quick check against race conditions + if (mSearchable == null) { + return; + } + mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing if (jamQuery) { CursorAdapter ca = getSuggestionsAdapter(parent); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 52aae0d..c4d3f9d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1112,6 +1112,8 @@ public class Intent implements Parcelable { * <p>My include the following extras: * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package. + * <li> {@link #EXTRA_REPLACING} is set to true if this is following + * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package. * </ul> */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) diff --git a/core/java/android/gadget/GadgetHost.java b/core/java/android/gadget/GadgetHost.java index 9176d18..31aed32 100644 --- a/core/java/android/gadget/GadgetHost.java +++ b/core/java/android/gadget/GadgetHost.java @@ -38,6 +38,7 @@ import com.android.internal.gadget.IGadgetService; public class GadgetHost { static final int HANDLE_UPDATE = 1; + static final int HANDLE_PROVIDER_CHANGED = 2; static Object sServiceLock = new Object(); static IGadgetService sService; @@ -52,6 +53,13 @@ public class GadgetHost { msg.obj = views; msg.sendToTarget(); } + + public void providerChanged(int gadgetId, GadgetProviderInfo info) { + Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED); + msg.arg1 = gadgetId; + msg.obj = info; + msg.sendToTarget(); + } } Handler mHandler = new Handler() { @@ -61,6 +69,10 @@ public class GadgetHost { updateGadgetView(msg.arg1, (RemoteViews)msg.obj); break; } + case HANDLE_PROVIDER_CHANGED: { + onProviderChanged(msg.arg1, (GadgetProviderInfo)msg.obj); + break; + } } } }; @@ -183,7 +195,8 @@ public class GadgetHost { } } - public final GadgetHostView createView(Context context, int gadgetId, GadgetInfo gadget) { + public final GadgetHostView createView(Context context, int gadgetId, + GadgetProviderInfo gadget) { GadgetHostView view = onCreateView(context, gadgetId, gadget); view.setGadget(gadgetId, gadget); synchronized (mViews) { @@ -203,9 +216,16 @@ public class GadgetHost { * Called to create the GadgetHostView. Override to return a custom subclass if you * need it. {@more} */ - protected GadgetHostView onCreateView(Context context, int gadgetId, GadgetInfo gadget) { + protected GadgetHostView onCreateView(Context context, int gadgetId, + GadgetProviderInfo gadget) { return new GadgetHostView(context); } + + /** + * Called when the gadget provider for a gadget has been upgraded to a new apk. + */ + protected void onProviderChanged(int gadgetId, GadgetProviderInfo gadget) { + } void updateGadgetView(int gadgetId, RemoteViews views) { GadgetHostView v; diff --git a/core/java/android/gadget/GadgetHostView.java b/core/java/android/gadget/GadgetHostView.java index d92c123..a985bd4 100644 --- a/core/java/android/gadget/GadgetHostView.java +++ b/core/java/android/gadget/GadgetHostView.java @@ -18,7 +18,6 @@ package android.gadget; import android.content.Context; import android.content.pm.PackageManager; -import android.gadget.GadgetInfo; import android.graphics.Color; import android.util.Config; import android.util.Log; @@ -26,12 +25,18 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import android.widget.FrameLayout; import android.widget.RemoteViews; import android.widget.TextView; import android.widget.ViewAnimator; -public class GadgetHostView extends ViewAnimator { +/** + * Provides the glue to show gadget views. This class offers automatic animation + * between updates, and will try recycling old views for each incoming + * {@link RemoteViews}. + */ +public class GadgetHostView extends ViewAnimator implements Animation.AnimationListener { static final String TAG = "GadgetHostView"; static final boolean LOGD = Config.LOGD || true; @@ -42,57 +47,93 @@ public class GadgetHostView extends ViewAnimator { return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); } }; - + + Context mLocalContext; + int mGadgetId; - GadgetInfo mInfo; - View mActiveView; - View mStaleView; + GadgetProviderInfo mInfo; - protected int mDefaultGravity = Gravity.CENTER; - + View mActiveView = null; + View mStaleView = null; + + int mActiveLayoutId = -1; + int mStaleLayoutId = -1; + + /** + * Last set of {@link RemoteViews} applied to {@link #mActiveView} + */ + RemoteViews mActiveActions = null; + + /** + * Flag indicating that {@link #mActiveActions} has been applied to + * {@link #mStaleView}, meaning it's readyto recycle. + */ + boolean mStalePrepared = false; + + /** + * Create a host view. Uses default fade animations. + */ public GadgetHostView(Context context) { - super(context); + this(context, android.R.anim.fade_in, android.R.anim.fade_out); } - public void setGadget(int gadgetId, GadgetInfo info) { - if (LOGD) Log.d(TAG, "setGadget is incoming with info=" + info); + /** + * Create a host view. Uses specified animations when pushing + * {@link #updateGadget(RemoteViews)}. + * + * @param animationIn Resource ID of in animation to use + * @param animationOut Resource ID of out animation to use + */ + public GadgetHostView(Context context, int animationIn, int animationOut) { + super(context); + mLocalContext = context; + + // Prepare our default transition animations + setAnimateFirstView(true); + setInAnimation(context, animationIn); + setOutAnimation(context, animationOut); + + // Watch for animation events to prepare recycling + Animation inAnimation = getInAnimation(); + if (inAnimation != null) { + inAnimation.setAnimationListener(this); + } + } + + /** + * Set the gadget that will be displayed by this view. + */ + public void setGadget(int gadgetId, GadgetProviderInfo info) { if (mInfo != null) { // TODO: remove the old view, or whatever } mGadgetId = gadgetId; mInfo = info; - - View defaultView = getDefaultView(); - flipUpdate(defaultView); } - /** - * Trigger actual animation between current and new content in the - * {@link ViewAnimator}. - */ - protected void flipUpdate(View newContent) { - if (LOGD) Log.d(TAG, "pushing an update to surface"); - - // Take requested dimensions from parent, but apply default gravity. - ViewGroup.LayoutParams requested = newContent.getLayoutParams(); - if (requested == null) { - requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, - LayoutParams.FILL_PARENT); + public int getGadgetId() { + return mGadgetId; + } + + public GadgetProviderInfo getGadgetInfo() { + return mInfo; + } + + public void onAnimationEnd(Animation animation) { + // When our transition animation finishes, we should try bringing our + // newly-stale view up to the current view. + if (mActiveActions != null && + mStaleLayoutId == mActiveActions.getLayoutId()) { + if (LOGD) Log.d(TAG, "after animation, layoutId matched so we're recycling old view"); + mActiveActions.reapply(mLocalContext, mStaleView); + mStalePrepared = true; } - - FrameLayout.LayoutParams params = - new FrameLayout.LayoutParams(requested.width, requested.height); - params.gravity = mDefaultGravity; - newContent.setLayoutParams(params); - - // Add new content and animate to it - addView(newContent); - showNext(); - - // Dispose old stale view - removeView(mStaleView); - mStaleView = mActiveView; - mActiveView = newContent; + } + + public void onAnimationRepeat(Animation animation) { + } + + public void onAnimationStart(Animation animation) { } /** @@ -100,26 +141,42 @@ public class GadgetHostView extends ViewAnimator { * gadget provider. Will animate into these new views as needed. */ public void updateGadget(RemoteViews remoteViews) { - if (LOGD) Log.d(TAG, "updateGadget() with remoteViews = " + remoteViews); + if (LOGD) Log.d(TAG, "updateGadget called"); + boolean recycled = false; View newContent = null; Exception exception = null; - try { - if (remoteViews == null) { - // there is no remoteViews (yet), so use the initial layout - newContent = getDefaultView(); - } else { - // use the RemoteViews - // TODO: try applying RemoteViews to existing staleView if available - newContent = remoteViews.apply(mContext, this); + if (remoteViews == null) { + newContent = getDefaultView(); + } + + // If our stale view has been prepared to match active, and the new + // layout matches, try recycling it + if (newContent == null && mStalePrepared && + remoteViews.getLayoutId() == mStaleLayoutId) { + try { + remoteViews.reapply(mLocalContext, mStaleView); + newContent = mStaleView; + recycled = true; + if (LOGD) Log.d(TAG, "was able to recycled existing layout"); + } catch (RuntimeException e) { + exception = e; + } + } + + // Try normal RemoteView inflation + if (newContent == null) { + try { + newContent = remoteViews.apply(mLocalContext, this); + if (LOGD) Log.d(TAG, "had to inflate new layout"); + } catch (RuntimeException e) { + exception = e; } - } catch (RuntimeException e) { - exception = e; } if (exception != null && LOGD) { - Log.w(TAG, "Error inflating gadget " + mInfo, exception); + Log.w(TAG, "Error inflating gadget " + getGadgetInfo(), exception); } if (newContent == null) { @@ -128,8 +185,44 @@ public class GadgetHostView extends ViewAnimator { if (LOGD) Log.d(TAG, "updateGadget couldn't find any view, so inflating error"); newContent = getErrorView(); } - - flipUpdate(newContent); + + if (!recycled) { + prepareView(newContent); + addView(newContent); + } + + showNext(); + + if (!recycled) { + removeView(mStaleView); + } + + mStalePrepared = false; + mActiveActions = remoteViews; + + mStaleView = mActiveView; + mActiveView = newContent; + + mStaleLayoutId = mActiveLayoutId; + mActiveLayoutId = (remoteViews == null) ? -1 : remoteViews.getLayoutId(); + } + + /** + * Prepare the given view to be shown. This might include adjusting + * {@link FrameLayout.LayoutParams} before inserting. + */ + protected void prepareView(View view) { + // Take requested dimensions from parent, but apply default gravity. + ViewGroup.LayoutParams requested = view.getLayoutParams(); + if (requested == null) { + requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT); + } + + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams(requested.width, requested.height); + params.gravity = Gravity.CENTER; + view.setLayoutParams(params); } /** @@ -141,7 +234,7 @@ public class GadgetHostView extends ViewAnimator { try { if (mInfo != null) { - Context theirContext = mContext.createPackageContext( + Context theirContext = mLocalContext.createPackageContext( mInfo.provider.getPackageName(), 0 /* no flags */); LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -173,11 +266,10 @@ public class GadgetHostView extends ViewAnimator { * Inflate and return a view that represents an error state. */ protected View getErrorView() { - TextView tv = new TextView(mContext); + TextView tv = new TextView(mLocalContext); // TODO: move this error string and background color into resources tv.setText("Error inflating gadget"); tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); return tv; } } - diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java index 20f4014..a9a2c80 100644 --- a/core/java/android/gadget/GadgetManager.java +++ b/core/java/android/gadget/GadgetManager.java @@ -38,68 +38,142 @@ public class GadgetManager { static final String TAG = "GadgetManager"; /** - * Send this when you want to pick a gadget to display. + * Send this from your gadget host activity when you want to pick a gadget to display. + * The gadget picker activity will be launched. + * <p> + * You must supply the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_GADGET_ID}</td> + * <td>A newly allocated gadgetId, which will be bound to the gadget provider + * once the user has selected one.</td> + * </tr> + * </table> * * <p> * The system will respond with an onActivityResult call with the following extras in * the intent: - * <ul> - * <li><b>gadgetIds</b></li> - * <li><b>hostId</b></li> - * </ul> - * TODO: Add constants for these. - * TODO: Where does this go? + * <table> + * <tr> + * <td>{@link #EXTRA_GADGET_ID}</td> + * <td>The gadgetId that you supplied in the original intent.</td> + * </tr> + * </table> + * <p> + * When you receive the result from the gadget pick activity, if the resultCode is + * {@link android.app.Activity#RESULT_OK}, a gadget has been selected. You should then + * check the GadgetProviderInfo for the returned gadget, and if it has one, launch its configuration + * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete + * the gadgetId. + * + * @see #ACTION_GADGET_CONFIGURE + */ + public static final String ACTION_GADGET_PICK = "android.gadget.action.GADGET_PICK"; + + /** + * Sent when it is time to configure your gadget while it is being added to a host. + * This action is not sent as a broadcast to the gadget provider, but as a startActivity + * to the activity specified in the {@link GadgetProviderInfo GadgetProviderInfo meta-data}. + * + * <p> + * The intent will contain the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_GADGET_ID}</td> + * <td>The gadgetId to configure.</td> + * </tr> + * </table> + * + * <p>If you return {@link android.app.Activity#RESULT_OK} using + * {@link android.app.Activity#setResult Activity.setResult()}, the gadget will be added, + * and you will receive an {@link #ACTION_GADGET_UPDATE} broadcast for this gadget. + * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add + * and not display this gadget, and you will receive a {@link #ACTION_GADGET_DELETED} broadcast. */ - public static final String GADGET_PICK_ACTION = "android.gadget.action.PICK_GADGET"; + public static final String ACTION_GADGET_CONFIGURE = "android.gadget.action.GADGET_CONFIGURE"; + /** + * An intent extra that contains one gadgetId. + * <p> + * The value will be an int that can be retrieved like this: + * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/GadgetHostActivity.java getExtra_EXTRA_GADGET_ID} + */ public static final String EXTRA_GADGET_ID = "gadgetId"; + + /** + * An intent extra that contains multiple gadgetIds. + * <p> + * The value will be an int array that can be retrieved like this: + * {@sample frameworks/base/tests/gadgets/GadgetHostTest/src/com/android/tests/gadgethost/TestGadgetProvider.java getExtra_EXTRA_GADGET_IDS} + */ public static final String EXTRA_GADGET_IDS = "gadgetIds"; - public static final String EXTRA_HOST_ID = "hostId"; + + /** + * A sentiel value that the gadget manager will never return as a gadgetId. + */ + public static final int INVALID_GADGET_ID = 0; /** * Sent when it is time to update your gadget. * * <p>This may be sent in response to a new instance for this gadget provider having - * been instantiated, the requested {@link GadgetInfo#updatePeriodMillis update interval} + * been instantiated, the requested {@link GadgetProviderInfo#updatePeriodMillis update interval} * having lapsed, or the system booting. - */ - public static final String GADGET_UPDATE_ACTION = "android.gadget.action.GADGET_UPDATE"; - - /** - * Sent when it is time to configure your gadget. This action is not sent as a broadcast - * to the gadget provider, but as a startActivity to the activity specified in the - * {@link GadgetInfo GadgetInfo meta-data}. * - * <p>The {@link #EXTRA_GADGET_ID} extra contains the gadget ID. + * <p> + * The intent will contain the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_GADGET_IDS}</td> + * <td>The gadgetIds to update. This may be all of the gadgets created for this + * provider, or just a subset. The system tries to send updates for as few gadget + * instances as possible.</td> + * </tr> + * </table> + * + * @see GadgetProvider#onUpdate GadgetProvider.onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) */ - public static final String GADGET_CONFIGURE_ACTION = "android.gadget.action.GADGET_CONFIGURE"; + public static final String ACTION_GADGET_UPDATE = "android.gadget.action.GADGET_UPDATE"; /** - * Sent when the gadget is added to a host for the first time. This broadcast is sent at - * boot time if there is a gadget host installed with an instance for this provider. + * Sent when an instance of a gadget is deleted from its host. + * + * @see GadgetProvider#onDeleted GadgetProvider.onDeleted(Context context, int[] gadgetIds) */ - public static final String GADGET_ENABLED_ACTION = "android.gadget.action.GADGET_ENABLED"; + public static final String ACTION_GADGET_DELETED = "android.gadget.action.GADGET_DELETED"; /** - * Sent when an instances of a gadget is deleted from the host. + * Sent when an instance of a gadget is removed from the last host. + * + * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context) */ - public static final String GADGET_DELETED_ACTION = "android.gadget.action.GADGET_DELETED"; + public static final String ACTION_GADGET_DISABLED = "android.gadget.action.GADGET_DISABLED"; /** - * Sent when the gadget is removed from the last host. + * Sent when an instance of a gadget is added to a host for the first time. + * This broadcast is sent at boot time if there is a gadget host installed with + * an instance for this provider. + * + * @see GadgetProvider#onEnabled GadgetProvider.onEnabled(Context context) */ - public static final String GADGET_DISABLED_ACTION = "android.gadget.action.GADGET_DISABLED"; + public static final String ACTION_GADGET_ENABLED = "android.gadget.action.GADGET_ENABLED"; /** * Field for the manifest meta-data tag. + * + * @see GadgetProviderInfo */ - public static final String GADGET_PROVIDER_META_DATA = "android.gadget.provider"; + public static final String META_DATA_GADGET_PROVIDER = "android.gadget.provider"; static WeakHashMap<Context, WeakReference<GadgetManager>> sManagerCache = new WeakHashMap(); static IGadgetService sService; Context mContext; + /** + * Get the GadgetManager instance to use for the supplied {@link android.content.Context + * Context} object. + */ public static GadgetManager getInstance(Context context) { synchronized (sManagerCache) { if (sService == null) { @@ -125,9 +199,11 @@ public class GadgetManager { } /** - * Call this with the new RemoteViews for your gadget whenever you need to. + * Set the RemoteViews to use for the specified gadgetIds. * * <p> + * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. * This method will only work when called from the uid that owns the gadget provider. * * @param gadgetIds The gadget instances for which to set the RemoteViews. @@ -143,9 +219,26 @@ public class GadgetManager { } /** - * Call this with the new RemoteViews for your gadget whenever you need to. + * Set the RemoteViews to use for the specified gadgetId. * * <p> + * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the gadget provider. + * + * @param gadgetId The gadget instance for which to set the RemoteViews. + * @param views The RemoteViews object to show. + */ + public void updateGadget(int gadgetId, RemoteViews views) { + updateGadget(new int[] { gadgetId }, views); + } + + /** + * Set the RemoteViews to use for all gadget instances for the supplied gadget provider. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_GADGET_UPDATE} broadcast, + * and outside of the handler. * This method will only work when called from the uid that owns the gadget provider. * * @param provider The {@link ComponentName} for the {@link @@ -165,7 +258,7 @@ public class GadgetManager { /** * Return a list of the gadget providers that are currently installed. */ - public List<GadgetInfo> getInstalledProviders() { + public List<GadgetProviderInfo> getInstalledProviders() { try { return sService.getInstalledProviders(); } @@ -175,12 +268,12 @@ public class GadgetManager { } /** - * Get the available info about the gadget. If the gadgetId has not been bound yet, - * this method will return null. + * Get the available info about the gadget. * - * TODO: throws GadgetNotFoundException ??? if not valid + * @return A gadgetId. If the gadgetId has not been bound to a provider yet, or + * you don't have access to that gadgetId, null is returned. */ - public GadgetInfo getGadgetInfo(int gadgetId) { + public GadgetProviderInfo getGadgetInfo(int gadgetId) { try { return sService.getGadgetInfo(gadgetId); } @@ -190,7 +283,14 @@ public class GadgetManager { } /** - * Set the component for a given gadgetId. You need the GADGET_LIST permission. + * Set the component for a given gadgetId. + * + * <p class="note">You need the GADGET_LIST permission. This method is to be used by the + * gadget picker. + * + * @param gadgetId The gadget instance for which to set the RemoteViews. + * @param provider The {@link android.content.BroadcastReceiver} that will be the gadget + * provider for this gadget. */ public void bindGadgetId(int gadgetId, ComponentName provider) { try { diff --git a/core/java/android/gadget/GadgetProvider.java b/core/java/android/gadget/GadgetProvider.java index 1ddfe3f..7e10e78 100755 --- a/core/java/android/gadget/GadgetProvider.java +++ b/core/java/android/gadget/GadgetProvider.java @@ -55,7 +55,7 @@ public class GadgetProvider extends BroadcastReceiver { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); - if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); @@ -64,7 +64,7 @@ public class GadgetProvider extends BroadcastReceiver { } } } - else if (GadgetManager.GADGET_DELETED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] gadgetIds = extras.getIntArray(GadgetManager.EXTRA_GADGET_IDS); @@ -73,102 +73,81 @@ public class GadgetProvider extends BroadcastReceiver { } } } - else if (GadgetManager.GADGET_ENABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_ENABLED.equals(action)) { this.onEnabled(context); } - else if (GadgetManager.GADGET_DISABLED_ACTION.equals(action)) { + else if (GadgetManager.ACTION_GADGET_DISABLED.equals(action)) { this.onDisabled(context); } } // END_INCLUDE(onReceive) /** - * Called in response to the {@link GadgetManager#GADGET_UPDATE_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_UPDATE} broadcast when * this gadget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews} * for a set of gadgets. Override this method to implement your own gadget functionality. * * {@more} - * <p class="note">If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_UPDATE_ACTION intent action. - * For example: - * <font color=red>TODO: SAMPLE CODE GOES HERE</font> - * </p> * * @param context The {@link android.content.Context Context} in which this receiver is * running. * @param gadgetManager A {@link GadgetManager} object you can call {@link - * GadgetManager#updateGadgets} on. + * GadgetManager#updateGadget} on. * @param gadgetIds The gadgetsIds for which an update is needed. Note that this * may be all of the gadget instances for this provider, or just * a subset of them. * - * @see GadgetManager#GADGET_UPDATE_ACTION + * @see GadgetManager#ACTION_GADGET_UPDATE */ public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) { } /** - * Called in response to the {@link GadgetManager#GADGET_DELETED_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_DELETED} broadcast when * one or more gadget instances have been deleted. Override this method to implement * your own gadget functionality. * * {@more} - * <p class="note">If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_DELETED_ACTION intent action. - * For example: - * <font color=red>TODO: SAMPLE CODE GOES HERE</font> - * </p> * * @param context The {@link android.content.Context Context} in which this receiver is * running. * @param gadgetIds The gadgetsIds that have been deleted from their host. * - * @see GadgetManager#GADGET_DELETED_ACTION + * @see GadgetManager#ACTION_GADGET_DELETED */ public void onDeleted(Context context, int[] gadgetIds) { } /** - * Called in response to the {@link GadgetManager#GADGET_ENABLED_ACTION} broadcast when + * Called in response to the {@link GadgetManager#ACTION_GADGET_ENABLED} broadcast when * the a gadget for this provider is instantiated. Override this method to implement your * own gadget functionality. * * {@more} * When the last gadget for this provider is deleted, - * {@link GadgetManager#GADGET_DISABLED_ACTION} is sent and {@link #onDisabled} - * is called. If after that, a gadget for this provider is created again, onEnabled() will - * be called again. + * {@link GadgetManager#ACTION_GADGET_DISABLED} is sent by the gadget manager, and + * {@link #onDisabled} is called. If after that, a gadget for this provider is created + * again, onEnabled() will be called again. * - * <p class="note">If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_ENABLED_ACTION intent action. - * For example: - * <font color=red>TODO: SAMPLE CODE GOES HERE</font> - * </p> - * * @param context The {@link android.content.Context Context} in which this receiver is * running. * - * @see GadgetManager#GADGET_ENABLED_ACTION + * @see GadgetManager#ACTION_GADGET_ENABLED */ public void onEnabled(Context context) { } /** - * Called in response to the {@link GadgetManager#GADGET_DISABLED_ACTION} broadcast, which + * Called in response to the {@link GadgetManager#ACTION_GADGET_DISABLED} broadcast, which * is sent when the last gadget instance for this provider is deleted. Override this method * to implement your own gadget functionality. * * {@more} - * <p class="note">If you want this method called, you must declare in an intent-filter in - * your AndroidManifest.xml file that you accept the GADGET_DISABLED_ACTION intent action. - * For example: - * <font color=red>TODO: SAMPLE CODE GOES HERE</font> - * </p> * * @param context The {@link android.content.Context Context} in which this receiver is * running. * - * @see GadgetManager#GADGET_DISABLED_ACTION + * @see GadgetManager#ACTION_GADGET_DISABLED */ public void onDisabled(Context context) { } diff --git a/core/java/android/gadget/GadgetInfo.aidl b/core/java/android/gadget/GadgetProviderInfo.aidl index 7231545..589f886 100644 --- a/core/java/android/gadget/GadgetInfo.aidl +++ b/core/java/android/gadget/GadgetProviderInfo.aidl @@ -16,4 +16,4 @@ package android.gadget; -parcelable GadgetInfo; +parcelable GadgetProviderInfo; diff --git a/core/java/android/gadget/GadgetInfo.java b/core/java/android/gadget/GadgetProviderInfo.java index 5ac3da9..95c0432 100644 --- a/core/java/android/gadget/GadgetInfo.java +++ b/core/java/android/gadget/GadgetProviderInfo.java @@ -21,60 +21,88 @@ import android.os.Parcelable; import android.content.ComponentName; /** - * Describes the meta data for an installed gadget. + * Describes the meta data for an installed gadget provider. The fields in this class + * correspond to the fields in the <code><gadget-provider></code> xml tag. */ -public class GadgetInfo implements Parcelable { +public class GadgetProviderInfo implements Parcelable { /** * Identity of this gadget component. This component should be a {@link * android.content.BroadcastReceiver}, and it will be sent the Gadget intents * {@link android.gadget as described in the gadget package documentation}. + * + * <p>This field corresponds to the <code>android:name</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. */ public ComponentName provider; /** * Minimum width of the gadget, in dp. + * + * <p>This field corresponds to the <code>android:minWidth</code> attribute in + * the gadget meta-data file. */ public int minWidth; /** * Minimum height of the gadget, in dp. + * + * <p>This field corresponds to the <code>android:minHeight</code> attribute in + * the gadget meta-data file. */ public int minHeight; /** * How often, in milliseconds, that this gadget wants to be updated. * The gadget manager may place a limit on how often a gadget is updated. + * + * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in + * the gadget meta-data file. */ public int updatePeriodMillis; /** * The resource id of the initial layout for this gadget. This should be * displayed until the RemoteViews for the gadget is available. + * + * <p>This field corresponds to the <code>android:initialLayout</code> attribute in + * the gadget meta-data file. */ public int initialLayout; /** * The activity to launch that will configure the gadget. + * + * <p>This class name of field corresponds to the <code>android:configure</code> attribute in + * the gadget meta-data file. The package name always corresponds to the package containing + * the gadget provider. */ public ComponentName configure; /** - * The label to display to the user. + * The label to display to the user in the gadget picker. If not supplied in the + * xml, the application label will be used. + * + * <p>This field corresponds to the <code>android:label</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. */ public String label; /** - * The icon to display for this gadget in the picker list. + * The icon to display for this gadget in the gadget picker. If not supplied in the + * xml, the application icon will be used. + * + * <p>This field corresponds to the <code>android:icon</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. */ public int icon; - public GadgetInfo() { + public GadgetProviderInfo() { } /** - * Unflatten the GadgetInfo from a parcel. + * Unflatten the GadgetProviderInfo from a parcel. */ - public GadgetInfo(Parcel in) { + public GadgetProviderInfo(Parcel in) { if (0 != in.readInt()) { this.provider = new ComponentName(in); } @@ -116,24 +144,24 @@ public class GadgetInfo implements Parcelable { } /** - * Parcelable.Creator that instantiates GadgetInfo objects + * Parcelable.Creator that instantiates GadgetProviderInfo objects */ - public static final Parcelable.Creator<GadgetInfo> CREATOR - = new Parcelable.Creator<GadgetInfo>() + public static final Parcelable.Creator<GadgetProviderInfo> CREATOR + = new Parcelable.Creator<GadgetProviderInfo>() { - public GadgetInfo createFromParcel(Parcel parcel) + public GadgetProviderInfo createFromParcel(Parcel parcel) { - return new GadgetInfo(parcel); + return new GadgetProviderInfo(parcel); } - public GadgetInfo[] newArray(int size) + public GadgetProviderInfo[] newArray(int size) { - return new GadgetInfo[size]; + return new GadgetProviderInfo[size]; } }; public String toString() { - return "GadgetInfo(provider=" + this.provider + ")"; + return "GadgetProviderInfo(provider=" + this.provider + ")"; } } diff --git a/core/java/android/gadget/package.html b/core/java/android/gadget/package.html index 4b8b9d9..4c04396 100644 --- a/core/java/android/gadget/package.html +++ b/core/java/android/gadget/package.html @@ -1,41 +1,126 @@ <body> -{@hide} <p>Android allows applications to publish views to be embedded in other applications. These views are called gadgets, and are published by "gadget providers." The component that can -contain gadgets is called a "gadget host." See the links below for more information. +contain gadgets is called a "gadget host." </p> -<h3><a href="{@toroot}reference/android/gadget/package-descr.html#providers">Gadget Providers</a></h3> +<h3><a href="package-descr.html#providers">Gadget Providers</a></h3> <ul> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#provider_manifest">Declaring a gadget in the AndroidManifest</a></li> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#provider_meta_data">Adding the {@link android.gadget.GadgetInfo GadgetInfo} meta-data</a></li> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#provider_GadgetProvider">Using the {@link android.gadget.GadgetProvider GadgetProvider} class</a></li> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#provider_configuration">Gadget Configuration UI</a></li> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#provider_broadcasts">Gadget Broadcast Intents</a></li> -</ul> -<h3><a href="{@toroot}reference/android/gadget/package-descr.html#">Gadget Hosts</a></h3> -<ul> - <li><a href="{@toroot}reference/android/gadget/package-descr.html#">asdf</a></li> + <li><a href="package-descr.html#provider_manifest">Declaring a gadget in the AndroidManifest</a></li> + <li><a href="package-descr.html#provider_meta_data">Adding the GadgetProviderInfo meta-data</a></li> + <li><a href="package-descr.html#provider_GadgetProvider">Using the GadgetProvider class</a></li> + <li><a href="package-descr.html#provider_configuration">Gadget Configuration UI</a></li> + <li><a href="package-descr.html#provider_broadcasts">Gadget Broadcast Intents</a></li> </ul> +<h3><a href="package-descr.html#">Gadget Hosts</a></h3> + + {@more} + + <h2><a name="providers"></a>Gadget Providers</h2> -<p>Any application can publish gadgets. All an application needs to do to publish a gadget is +<p> +Any application can publish gadgets. All an application needs to do to publish a gadget is to have a {@link android.content.BroadcastReceiver} that receives the {@link -android.gadget.GadgetManager#GADGET_UPDATE_ACTION GadgetManager.GADGET_UPDATE_ACTION} intent, -and provide some meta-data about the gadget. +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} intent, +and provide some meta-data about the gadget. Android provides the +{@link android.gadget.GadgetProvider} class, which extends BroadcastReceiver, as a convenience +class to aid in handling the broadcasts. <h3><a name="provider_manifest"></a>Declaring a gadget in the AndroidManifest</h3> -<h3><a name="provider_meta_data"></a>Adding the {@link android.gadget.GadgetInfo GadgetInfo} meta-data</h3> +<p> +First, declare the {@link android.content.BroadcastReceiver} in your application's +<code>AndroidManifest.xml</code> file. + +{@sample frameworks/base/tests/gadgets/GadgetHostTest/AndroidManifest.xml GadgetProvider} + +<p> +The <b><code><receiver></b> element has the following attributes: +<ul> + <li><b><code>android:name</code> -</b> which specifies the + {@link android.content.BroadcastReceiver} or {@link android.gadget.GadgetProvider} + class.</li> + <li><b><code>android:label</code> -</b> which specifies the string resource that + will be shown by the gadget picker as the label.</li> + <li><b><code>android:icon</code> -</b> which specifies the drawable resource that + will be shown by the gadget picker as the icon.</li> +</ul> + +<p> +The <b><code><intent-filter></b> element tells the {@link android.content.pm.PackageManager} +that this {@link android.content.BroadcastReceiver} receives the {@link +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast. +The gadget manager will send other broadcasts directly to your gadget provider as required. +It is only necessary to explicitly declare that you accept the {@link +android.gadget.GadgetManager#ACTION_GADGET_UPDATE GadgetManager.ACTION_GADGET_UPDATE} broadcast. + +<p> +The <b><code><meta-data></code></b> element tells the gadget manager which xml resource to +read to find the {@link android.gadget.GadgetProviderInfo} for your gadget provider. It has the following +attributes: +<ul> + <li><b><code>android:name="android.gadget.provider"</code> -</b> identifies this meta-data + as the {@link android.gadget.GadgetProviderInfo} descriptor.</li> + <li><b><code>android:resource</code> -</b> is the xml resource to use as that descriptor.</li> +</ul> + + +<h3><a name="provider_meta_data"></a>Adding the {@link android.gadget.GadgetProviderInfo GadgetProviderInfo} meta-data</h3> + +<p> +For a gadget, the values in the {@link android.gadget.GadgetProviderInfo} structure are supplied +in an XML resource. In the example above, the xml resource is referenced with +<code>android:resource="@xml/gadget_info"</code>. That XML file would go in your application's +directory at <code>res/xml/gadget_info.xml</code>. Here is a simple example. + +{@sample frameworks/base/tests/gadgets/GadgetHostTest/res/xml/gadget_info.xml GadgetProviderInfo} + +<p> +The attributes are as documented in the {@link android.gadget.GadgetProviderInfo GagetInfo} class. (86400000 milliseconds means once per day) + <h3><a name="provider_GadgetProvider"></a>Using the {@link android.gadget.GadgetProvider GadgetProvider} class</h3> +<p>The GadgetProvider class is the easiest way to handle the gadget provider intent broadcasts. +See the <code>src/com/example/android/apis/gadget/ExampleGadgetProvider.java</code> +sample class in ApiDemos for an example. + +<p class="note">Keep in mind that since the the GadgetProvider is a BroadcastReceiver, +your process is not guaranteed to keep running after the callback methods return. See +<a href="../../../guide/topics/fundamentals.html#broadlife">Application Fundamentals > +Broadcast Receiver Lifecycle</a> for more information. + + + <h3><a name="provider_configuration"></a>Gadget Configuration UI</h3> +<p> +Gadget hosts have the ability to start a configuration activity when a gadget is instantiated. +The activity should be declared as normal in AndroidManifest.xml, and it should be listed in +the GadgetProviderInfo XML file in the <code>android:configure</code> attribute. + +<p>The activity you specified will be launched with the {@link +android.gadget.GadgetManager#ACTION_GADGET_CONFIGURE} action. See the documentation for that +action for more info. + +<p>See the <code>src/com/example/android/apis/gadget/ExampleGadgetConfigure.java</code> +sample class in ApiDemos for an example. + + + <h3><a name="providers_broadcasts"></a>Gadget Broadcast Intents</h3> -<p>{@link GadgetProvider} is just a convenience class. If you would like to receive the -gadget broadcasts directly, you can. By way of example, the implementation of -{@link GadgetProvider.onReceive} is quite simple:</p> +<p>{@link android.gadget.GadgetProvider} is just a convenience class. If you would like +to receive the gadget broadcasts directly, you can. The four intents you need to care about are: +<ul> + <li>{@link android.gadget.GadgetManager#ACTION_GADGET_UPDATE}</li> + <li>{@link android.gadget.GadgetManager#ACTION_GADGET_DELETED}</li> + <li>{@link android.gadget.GadgetManager#ACTION_GADGET_ENABLED}</li> + <li>{@link android.gadget.GadgetManager#ACTION_GADGET_DISABLED}</li> +</ul> + +<p>By way of example, the implementation of +{@link android.gadget.GadgetProvider#onReceive} is quite simple:</p> {@sample frameworks/base/core/java/android/gadget/GadgetProvider.java onReceive} diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index c09567c..40a5b47 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -18,6 +18,7 @@ package android.hardware; import java.lang.ref.WeakReference; import java.util.HashMap; +import java.util.StringTokenizer; import java.io.IOException; import android.util.Log; @@ -494,11 +495,17 @@ public class Camera { */ public void unflatten(String flattened) { mMap.clear(); - String[] pairs = flattened.split(";"); - for (String p : pairs) { - String[] kv = p.split("="); - if (kv.length == 2) - mMap.put(kv[0], kv[1]); + + StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); + while (tokenizer.hasMoreElements()) { + String kv = tokenizer.nextToken(); + int pos = kv.indexOf('='); + if (pos == -1) { + continue; + } + String k = kv.substring(0, pos); + String v = kv.substring(pos + 1); + mMap.put(k, v); } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ea5f741..c884120 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -206,6 +206,8 @@ public class InputMethodService extends AbstractInputMethodService { static final String TAG = "InputMethodService"; static final boolean DEBUG = false; + InputMethodManager mImm; + LayoutInflater mInflater; View mRootView; SoftInputWindow mWindow; @@ -293,6 +295,8 @@ public class InputMethodService extends AbstractInputMethodService { mInputConnection = binding.getConnection(); if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding + " ic=" + mInputConnection); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) ic.reportFullscreenMode(mIsFullscreen); initialize(); onBindInput(); } @@ -423,7 +427,7 @@ public class InputMethodService extends AbstractInputMethodService { * of the application behind. This value is relative to the top edge * of the input method window. */ - int contentTopInsets; + public int contentTopInsets; /** * This is the top part of the UI that is visibly covering the @@ -436,7 +440,7 @@ public class InputMethodService extends AbstractInputMethodService { * needed to make the focus visible. This value is relative to the top edge * of the input method window. */ - int visibleTopInsets; + public int visibleTopInsets; /** * Option for {@link #touchableInsets}: the entire window frame @@ -469,6 +473,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void onCreate() { super.onCreate(); + mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this); @@ -554,7 +559,6 @@ public class InputMethodService extends AbstractInputMethodService { boolean visible = mWindowVisible; boolean showingInput = mShowInputRequested; boolean showingForced = mShowInputForced; - boolean showingCandidates = mCandidatesVisibility == View.VISIBLE; initViews(); mInputViewStarted = false; mCandidatesViewStarted = false; @@ -577,9 +581,6 @@ public class InputMethodService extends AbstractInputMethodService { // Otherwise just put it back for its candidates. showWindow(false); } - if (showingCandidates) { - setCandidatesViewShown(true); - } } } @@ -670,6 +671,8 @@ public class InputMethodService extends AbstractInputMethodService { if (mIsFullscreen != isFullscreen || !mFullscreenApplied) { changed = true; mIsFullscreen = isFullscreen; + InputConnection ic = getCurrentInputConnection(); + if (ic != null) ic.reportFullscreenMode(isFullscreen); mFullscreenApplied = true; initialize(); Drawable bg = onCreateBackgroundDrawable(); @@ -860,12 +863,14 @@ public class InputMethodService extends AbstractInputMethodService { return isFullscreenMode() ? View.GONE : View.INVISIBLE; } - public void setStatusIcon(int iconResId) { + public void showStatusIcon(int iconResId) { mStatusIcon = iconResId; - InputConnection ic = getCurrentInputConnection(); - if (ic != null && mWindowVisible) { - ic.showStatusIcon(getPackageName(), iconResId); - } + mImm.showStatusIcon(mToken, getPackageName(), iconResId); + } + + public void hideStatusIcon() { + mStatusIcon = 0; + mImm.hideStatusIcon(mToken); } /** @@ -876,8 +881,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param id Unique identifier of the new input method ot start. */ public void switchInputMethod(String id) { - ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) - .setInputMethod(mToken, id); + mImm.setInputMethod(mToken, id); } public void setExtractView(View view) { @@ -1149,15 +1153,9 @@ public class InputMethodService extends AbstractInputMethodService { if (!wasVisible) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); + onWindowShown(); mWindow.show(); } - - if (!wasVisible || !wasCreated) { - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ic.showStatusIcon(getPackageName(), mStatusIcon); - } - } } public void hideWindow() { @@ -1173,14 +1171,26 @@ public class InputMethodService extends AbstractInputMethodService { if (mWindowVisible) { mWindow.hide(); mWindowVisible = false; - InputConnection ic = getCurrentInputConnection(); - if (ic != null) { - ic.hideStatusIcon(); - } + onWindowHidden(); } } /** + * Called when the input method window has been shown to the user, after + * previously not being visible. This is done after all of the UI setup + * for the window has occurred (creating its views etc). + */ + public void onWindowShown() { + } + + /** + * Called when the input method window has been hidden from the user, + * after previously being visible. + */ + public void onWindowHidden() { + } + + /** * Called when a new client has bound to the input method. This * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} * and {@link #onFinishInput()} calls as the user navigates through its @@ -1341,8 +1351,7 @@ public class InputMethodService extends AbstractInputMethodService { * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. */ public void dismissSoftInput(int flags) { - ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)) - .hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethod(mToken, flags); } /** @@ -1447,17 +1456,19 @@ public class InputMethodService extends AbstractInputMethodService { return true; } } else { - KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); - if (movement.onKeyDown(eet, - (Spannable)eet.getText(), keyCode, down)) { - KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); - movement.onKeyUp(eet, - (Spannable)eet.getText(), keyCode, up); - while (--count > 0) { - movement.onKeyDown(eet, - (Spannable)eet.getText(), keyCode, down); + if (!movement.onKeyOther(eet, (Spannable)eet.getText(), event)) { + KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + if (movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down)) { + KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); movement.onKeyUp(eet, (Spannable)eet.getText(), keyCode, up); + while (--count > 0) { + movement.onKeyDown(eet, + (Spannable)eet.getText(), keyCode, down); + movement.onKeyUp(eet, + (Spannable)eet.getText(), keyCode, up); + } } } } @@ -1593,5 +1604,9 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mExtractedToken=" + mExtractedToken); p.println(" mIsInputViewShown=" + mIsInputViewShown + " mStatusIcon=" + mStatusIcon); + p.println("Last computed insets:"); + p.println(" contentTopInsets=" + mTmpInsets.contentTopInsets + + " visibleTopInsets=" + mTmpInsets.visibleTopInsets + + " touchableInsets=" + mTmpInsets.touchableInsets); } } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index b2c74f2..b8bd10d 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -1084,6 +1084,10 @@ public class KeyboardView extends View implements View.OnClickListener { if (mPreviewPopup.isShowing()) { mPreviewPopup.dismiss(); } + mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); + mHandler.removeMessages(MSG_SHOW_PREVIEW); + dismissPopupKeyboard(); } diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java index 70e50b7..a6efcdd 100644 --- a/core/java/android/net/UrlQuerySanitizer.java +++ b/core/java/android/net/UrlQuerySanitizer.java @@ -23,7 +23,7 @@ import java.util.Set; import java.util.StringTokenizer; /** - * + * * Sanitizes the Query portion of a URL. Simple example: * <code> * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(); @@ -32,7 +32,7 @@ import java.util.StringTokenizer; * String name = sanitizer.getValue("name")); * // name now contains "Joe_User" * </code> - * + * * Register ValueSanitizers to customize the way individual * parameters are sanitized: * <code> @@ -46,7 +46,7 @@ import java.util.StringTokenizer; * unregistered parameter sanitizer does not allow any special characters, * and ' ' is a special character.) * </code> - * + * * There are several ways to create ValueSanitizers. In order of increasing * sophistication: * <ol> @@ -56,7 +56,7 @@ import java.util.StringTokenizer; * <li>Subclass UrlQuerySanitizer.ValueSanitizer to define your own value * sanitizer. * </ol> - * + * */ public class UrlQuerySanitizer { @@ -84,7 +84,7 @@ public class UrlQuerySanitizer { */ public String mValue; } - + final private HashMap<String, ValueSanitizer> mSanitizers = new HashMap<String, ValueSanitizer>(); final private HashMap<String, String> mEntries = @@ -95,9 +95,9 @@ public class UrlQuerySanitizer { private boolean mPreferFirstRepeatedParameter; private ValueSanitizer mUnregisteredParameterValueSanitizer = getAllIllegal(); - + /** - * A functor used to sanitize a single query value. + * A functor used to sanitize a single query value. * */ public static interface ValueSanitizer { @@ -108,7 +108,7 @@ public class UrlQuerySanitizer { */ public String sanitize(String value); } - + /** * Sanitize values based on which characters they contain. Illegal * characters are replaced with either space or '_', depending upon @@ -117,7 +117,7 @@ public class UrlQuerySanitizer { public static class IllegalCharacterValueSanitizer implements ValueSanitizer { private int mFlags; - + /** * Allow space (' ') characters. */ @@ -165,21 +165,21 @@ public class UrlQuerySanitizer { * such as "javascript:" or "vbscript:" */ public final static int SCRIPT_URL_OK = 1 << 10; - + /** * Mask with all fields set to OK */ public final static int ALL_OK = 0x7ff; - + /** * Mask with both regular space and other whitespace OK */ public final static int ALL_WHITESPACE_OK = SPACE_OK | OTHER_WHITESPACE_OK; - + // Common flag combinations: - + /** * <ul> * <li>Deny all special characters. @@ -262,18 +262,18 @@ public class UrlQuerySanitizer { */ public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL = ALL_OK & ~(NUL_OK | LT_OK | GT_OK); - + /** * Script URL definitions */ - + private final static String JAVASCRIPT_PREFIX = "javascript:"; - + private final static String VBSCRIPT_PREFIX = "vbscript:"; - + private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min( JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length()); - + /** * Construct a sanitizer. The parameters set the behavior of the * sanitizer. @@ -312,7 +312,7 @@ public class UrlQuerySanitizer { } } } - + // If whitespace isn't OK, get rid of whitespace at beginning // and end of value. if ( (mFlags & ALL_WHITESPACE_OK) == 0) { @@ -337,7 +337,7 @@ public class UrlQuerySanitizer { } return stringBuilder.toString(); } - + /** * Trim whitespace from the beginning and end of a string. * <p> @@ -361,7 +361,7 @@ public class UrlQuerySanitizer { } return value.substring(start, end + 1); } - + /** * Check if c is whitespace. * @param c character to test @@ -380,7 +380,7 @@ public class UrlQuerySanitizer { return false; } } - + /** * Check whether an individual character is legal. Uses the * flag bit-set passed into the constructor. @@ -400,11 +400,11 @@ public class UrlQuerySanitizer { case '%' : return (mFlags & PCT_OK) != 0; case '\0': return (mFlags & NUL_OK) != 0; default : return (c >= 32 && c < 127) || - (c >= 128 && c <= 255 && ((mFlags & NON_7_BIT_ASCII_OK) != 0)); - } + ((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0)); + } } } - + /** * Get the current value sanitizer used when processing * unregistered parameter values. @@ -412,14 +412,14 @@ public class UrlQuerySanitizer { * <b>Note:</b> The default unregistered parameter value sanitizer is * one that doesn't allow any special characters, similar to what * is returned by calling createAllIllegal. - * + * * @return the current ValueSanitizer used to sanitize unregistered * parameter values. */ public ValueSanitizer getUnregisteredParameterValueSanitizer() { return mUnregisteredParameterValueSanitizer; } - + /** * Set the value sanitizer used when processing unregistered * parameter values. @@ -430,46 +430,46 @@ public class UrlQuerySanitizer { ValueSanitizer sanitizer) { mUnregisteredParameterValueSanitizer = sanitizer; } - - + + // Private fields for singleton sanitizers: - + private static final ValueSanitizer sAllIllegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_ILLEGAL); - + private static final ValueSanitizer sAllButNulLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL); - + private static final ValueSanitizer sAllButWhitespaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL); - + private static final ValueSanitizer sURLLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.URL_LEGAL); - + private static final ValueSanitizer sUrlAndSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL); - + private static final ValueSanitizer sAmpLegal = new IllegalCharacterValueSanitizer( - IllegalCharacterValueSanitizer.AMP_LEGAL); - + IllegalCharacterValueSanitizer.AMP_LEGAL); + private static final ValueSanitizer sAmpAndSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL); - + private static final ValueSanitizer sSpaceLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.SPACE_LEGAL); - + private static final ValueSanitizer sAllButNulAndAngleBracketsLegal = new IllegalCharacterValueSanitizer( IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL); - + /** * Return a value sanitizer that does not allow any special characters, * and also does not allow script URLs. @@ -478,7 +478,7 @@ public class UrlQuerySanitizer { public static final ValueSanitizer getAllIllegal() { return sAllIllegal; } - + /** * Return a value sanitizer that allows everything except Nul ('\0') * characters. Script URLs are allowed. @@ -547,7 +547,7 @@ public class UrlQuerySanitizer { public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() { return sAllButNulAndAngleBracketsLegal; } - + /** * Constructs a UrlQuerySanitizer. * <p> @@ -560,7 +560,7 @@ public class UrlQuerySanitizer { */ public UrlQuerySanitizer() { } - + /** * Constructs a UrlQuerySanitizer and parse a URL. * This constructor is provided for convenience when the @@ -585,7 +585,7 @@ public class UrlQuerySanitizer { setAllowUnregisteredParamaters(true); parseUrl(url); } - + /** * Parse the query parameters out of an encoded URL. * Works by extracting the query portion from the URL and then @@ -604,7 +604,7 @@ public class UrlQuerySanitizer { } parseQuery(query); } - + /** * Parse a query. A query string is any number of parameter-value clauses * separated by any non-zero number of ampersands. A parameter-value clause @@ -631,7 +631,7 @@ public class UrlQuerySanitizer { } } } - + /** * Get a set of all of the parameters found in the sanitized query. * <p> @@ -641,7 +641,7 @@ public class UrlQuerySanitizer { public Set<String> getParameterSet() { return mEntries.keySet(); } - + /** * An array list of all of the parameter value pairs in the sanitized * query, in the order they appeared in the query. May contain duplicate @@ -691,7 +691,7 @@ public class UrlQuerySanitizer { } mSanitizers.put(parameter, valueSanitizer); } - + /** * Register a value sanitizer for an array of parameters. * @param parameters An array of unencoded parameter names. @@ -705,7 +705,7 @@ public class UrlQuerySanitizer { mSanitizers.put(parameters[i], valueSanitizer); } } - + /** * Set whether or not unregistered parameters are allowed. If they * are not allowed, then they will be dropped when a query is sanitized. @@ -718,7 +718,7 @@ public class UrlQuerySanitizer { boolean allowUnregisteredParamaters) { mAllowUnregisteredParamaters = allowUnregisteredParamaters; } - + /** * Get whether or not unregistered parameters are allowed. If not * allowed, they will be dropped when a query is parsed. @@ -728,10 +728,10 @@ public class UrlQuerySanitizer { public boolean getAllowUnregisteredParamaters() { return mAllowUnregisteredParamaters; } - + /** * Set whether or not the first occurrence of a repeated parameter is - * preferred. True means the first repeated parameter is preferred. + * preferred. True means the first repeated parameter is preferred. * False means that the last repeated parameter is preferred. * <p> * The preferred parameter is the one that is returned when getParameter @@ -746,7 +746,7 @@ public class UrlQuerySanitizer { boolean preferFirstRepeatedParameter) { mPreferFirstRepeatedParameter = preferFirstRepeatedParameter; } - + /** * Get whether or not the first occurrence of a repeated parameter is * preferred. @@ -757,10 +757,10 @@ public class UrlQuerySanitizer { public boolean getPreferFirstRepeatedParameter() { return mPreferFirstRepeatedParameter; } - + /** * Parse an escaped parameter-value pair. The default implementation - * unescapes both the parameter and the value, then looks up the + * unescapes both the parameter and the value, then looks up the * effective value sanitizer for the parameter and uses it to sanitize * the value. If all goes well then addSanitizedValue is called with * the unescaped parameter and the sanitized unescaped value. @@ -779,7 +779,7 @@ public class UrlQuerySanitizer { String sanitizedValue = valueSanitizer.sanitize(unescapedValue); addSanitizedEntry(unescapedParameter, sanitizedValue); } - + /** * Record a sanitized parameter-value pair. Override if you want to * do additional filtering or validation. @@ -796,7 +796,7 @@ public class UrlQuerySanitizer { } mEntries.put(parameter, value); } - + /** * Get the value sanitizer for a parameter. Returns null if there * is no value sanitizer registered for the parameter. @@ -807,7 +807,7 @@ public class UrlQuerySanitizer { public ValueSanitizer getValueSanitizer(String parameter) { return mSanitizers.get(parameter); } - + /** * Get the effective value sanitizer for a parameter. Like getValueSanitizer, * except if there is no value sanitizer registered for a parameter, and @@ -823,7 +823,7 @@ public class UrlQuerySanitizer { } return sanitizer; } - + /** * Unescape an escaped string. * <ul> @@ -867,7 +867,7 @@ public class UrlQuerySanitizer { } return stringBuilder.toString(); } - + /** * Test if a character is a hexidecimal digit. Both upper case and lower * case hex digits are allowed. @@ -877,7 +877,7 @@ public class UrlQuerySanitizer { protected boolean isHexDigit(char c) { return decodeHexDigit(c) >= 0; } - + /** * Convert a character that represents a hexidecimal digit into an integer. * If the character is not a hexidecimal digit, then -1 is returned. @@ -885,7 +885,7 @@ public class UrlQuerySanitizer { * @param c the hexidecimal digit. * @return the integer value of the hexidecimal digit. */ - + protected int decodeHexDigit(char c) { if (c >= '0' && c <= '9') { return c - '0'; @@ -900,7 +900,7 @@ public class UrlQuerySanitizer { return -1; } } - + /** * Clear the existing entries. Called to get ready to parse a new * query string. diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java index c6615da..1a287c8 100644 --- a/core/java/android/pim/RecurrenceSet.java +++ b/core/java/android/pim/RecurrenceSet.java @@ -140,7 +140,6 @@ public class RecurrenceSet { recurrence = recurrence.substring(tzidx + 1); } Time time = new Time(tz); - boolean rdateNotInUtc = !tz.equals(Time.TIMEZONE_UTC); String[] rawDates = recurrence.split(","); int n = rawDates.length; long[] dates = new long[n]; diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index 05c2952..02ab1da 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -88,6 +88,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { mPreferenceGroup = preferenceGroup; + // If this group gets or loses any children, let us know + mPreferenceGroup.setOnPreferenceChangeInternalListener(this); + mPreferenceList = new ArrayList<Preference>(); mPreferenceClassNames = new ArrayList<String>(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 054da1d..c6a7b40 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -879,15 +879,6 @@ public final class Settings { public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios"; /** - * The interval in milliseconds after which Wi-Fi is considered idle. - * When idle, it is possible for the device to be switched from Wi-Fi to - * the mobile data network. - * - * @hide pending API Council approval - */ - public static final String WIFI_IDLE_MS = "wifi_idle_ms"; - - /** * The policy for deciding when Wi-Fi should go to sleep (which will in * turn switch to using the mobile data as an Internet connection). * <p> @@ -1288,6 +1279,12 @@ public final class Settings { */ public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled"; + /** + * Whether the haptic feedback (long presses, ...) are enabled. The value is + * boolean (1 or 0). + */ + public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; + // Settings moved to Settings.Secure /** @@ -2732,6 +2729,13 @@ public final class Settings { "gprs_register_check_period_ms"; /** + * The interval in milliseconds after which Wi-Fi is considered idle. + * When idle, it is possible for the device to be switched from Wi-Fi to + * the mobile data network. + */ + public static final String WIFI_IDLE_MS = "wifi_idle_ms"; + + /** * Screen timeout in milliseconds corresponding to the * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest * possible screen timeout behavior.) diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index d149761..7c15045 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -25,8 +25,8 @@ package android.server; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; // just for dump() import android.bluetooth.BluetoothError; +import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDevice; import android.bluetooth.IBluetoothDeviceCallback; @@ -35,23 +35,20 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Log; import android.os.Binder; import android.os.Handler; import android.os.Message; +import android.os.RemoteException; import android.os.SystemService; +import android.provider.Settings; +import android.util.Log; -import java.io.IOException; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class BluetoothDeviceService extends IBluetoothDevice.Stub { @@ -119,7 +116,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized boolean disable() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - + if (mEnableThread != null && mEnableThread.isAlive()) { return false; } @@ -229,9 +226,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { long origCallerIdentityToken = Binder.clearCallingIdentity(); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, bluetoothOn ? 1 : 0); - Binder.restoreCallingIdentity(origCallerIdentityToken); + Binder.restoreCallingIdentity(origCallerIdentityToken); } - + private native int enableNative(); private native int disableNative(); @@ -247,6 +244,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public class BondState { private final HashMap<String, Integer> mState = new HashMap<String, Integer>(); private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>(); + private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>(); public synchronized void loadBondState() { if (!mIsEnabled) { @@ -281,8 +279,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { intent.putExtra(BluetoothIntent.BOND_PREVIOUS_STATE, oldState); if (state == BluetoothDevice.BOND_NOT_BONDED) { if (reason <= 0) { - Log.w(TAG, "setBondState() called to unbond device with invalid reason code " + - "Setting reason = BOND_RESULT_REMOVED"); + Log.w(TAG, "setBondState() called to unbond device, but reason code is " + + "invalid. Overriding reason code with BOND_RESULT_REMOVED"); reason = BluetoothDevice.UNBOND_REASON_REMOVED; } intent.putExtra(BluetoothIntent.REASON, reason); @@ -290,11 +288,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } else { mState.put(address, state); } - if (state == BluetoothDevice.BOND_BONDING) { - mPinAttempt.put(address, Integer.valueOf(0)); - } else { - mPinAttempt.remove(address); - } + mContext.sendBroadcast(intent, BLUETOOTH_PERM); } @@ -316,6 +310,24 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { return result.toArray(new String[result.size()]); } + public synchronized void addAutoPairingFailure(String address) { + if (!mAutoPairingFailures.contains(address)) { + mAutoPairingFailures.add(address); + } + } + + public synchronized boolean isAutoPairingAttemptsInProgress(String address) { + return getAttempt(address) != 0; + } + + public synchronized void clearPinAttempts(String address) { + mPinAttempt.remove(address); + } + + public synchronized boolean hasAutoPairingFailed(String address) { + return mAutoPairingFailures.contains(address); + } + public synchronized int getAttempt(String address) { Integer attempt = mPinAttempt.get(address); if (attempt == null) { @@ -326,10 +338,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized void attempt(String address) { Integer attempt = mPinAttempt.get(address); + int newAttempt; if (attempt == null) { - return; + newAttempt = 1; + } else { + newAttempt = attempt.intValue() + 1; } - mPinAttempt.put(address, new Integer(attempt.intValue() + 1)); + mPinAttempt.put(address, new Integer(newAttempt)); } } @@ -508,7 +523,11 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { return false; } address = address.toUpperCase(); - if (mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) { + + // Check for bond state only if we are not performing auto + // pairing exponential back-off attempts. + if (!mBondState.isAutoPairingAttemptsInProgress(address) && + mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) { return false; } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 0f60fae..b5e4090 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -24,6 +24,8 @@ import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDeviceCallback; import android.content.Context; import android.content.Intent; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; import android.util.Log; @@ -48,9 +50,33 @@ class BluetoothEventLoop { private BluetoothDeviceService mBluetoothService; private Context mContext; + private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; + + // The time (in millisecs) to delay the pairing attempt after the first + // auto pairing attempt fails. We use an exponential delay with + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. + private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; + private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: + String address = (String)msg.obj; + if (address != null) { + mBluetoothService.createBond(address); + return; + } + break; + } + } + }; + static { classInitNative(); } private static native void classInitNative(); @@ -149,16 +175,6 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_PERM); } - private void onPairingRequest() { - Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - - private void onPairingCancel() { - Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); - mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); - } - private void onRemoteDeviceFound(String address, int deviceClass, short rssi) { Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); @@ -214,12 +230,55 @@ class BluetoothEventLoop { address = address.toUpperCase(); if (result == BluetoothError.SUCCESS) { mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); + if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + mBluetoothService.getBondState().clearPinAttempts(address); + } + } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && + mBluetoothService.getBondState().getAttempt(address) == 1) { + mBluetoothService.getBondState().addAutoPairingFailure(address); + pairingAttempt(address, result); + } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && + mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + pairingAttempt(address, result); } else { mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED, result); + if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { + mBluetoothService.getBondState().clearPinAttempts(address); + } } } + private void pairingAttempt(String address, int result) { + // This happens when our initial guess of "0000" as the pass key + // fails. Try to create the bond again and display the pin dialog + // to the user. Use back-off while posting the delayed + // message. The initial value is + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is + // reached, display an error to the user. + int attempt = mBluetoothService.getBondState().getAttempt(address); + if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > + MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { + mBluetoothService.getBondState().clearPinAttempts(address); + mBluetoothService.getBondState().setBondState(address, + BluetoothDevice.BOND_NOT_BONDED, result); + return; + } + + Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + message.obj = address; + boolean postResult = mHandler.sendMessageDelayed(message, + attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + if (!postResult) { + mBluetoothService.getBondState().clearPinAttempts(address); + mBluetoothService.getBondState().setBondState(address, + BluetoothDevice.BOND_NOT_BONDED, result); + return; + } + mBluetoothService.getBondState().attempt(address); + } + private void onBondingCreated(String address) { mBluetoothService.getBondState().setBondState(address.toUpperCase(), BluetoothDevice.BOND_BONDED); @@ -253,12 +312,12 @@ class BluetoothEventLoop { case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - if (mBluetoothService.getBondState().getAttempt(address) < 1) { + if (!mBluetoothService.getBondState().hasAutoPairingFailed(address)) { mBluetoothService.getBondState().attempt(address); mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); return; } - } + } } Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index a559b9d..6df0b35 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -16,6 +16,7 @@ package android.text.method; +import android.util.Log; import android.view.KeyEvent; import android.text.*; import android.widget.TextView; @@ -185,15 +186,9 @@ implements MovementMethod if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { int repeat = event.getRepeatCount(); - boolean first = true; boolean handled = false; while ((--repeat) > 0) { - if (first && executeDown(view, text, code)) { - handled = true; - MetaKeyKeyListener.adjustMetaAfterKeypress(text); - MetaKeyKeyListener.resetLockedMeta(text); - } - first = false; + handled |= executeDown(view, text, code); } return handled; } diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index d89fbec..39ad976 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -287,10 +287,10 @@ public abstract class MetaKeyKeyListener { } public static void clearMetaKeyState(Editable content, int states) { - if ((states&META_SHIFT_ON) != 0) resetLock(content, CAP); - if ((states&META_ALT_ON) != 0) resetLock(content, ALT); - if ((states&META_SYM_ON) != 0) resetLock(content, SYM); - if ((states&META_SELECTING) != 0) resetLock(content, SELECTING); + if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP); + if ((states&META_ALT_ON) != 0) content.removeSpan(ALT); + if ((states&META_SYM_ON) != 0) content.removeSpan(SYM); + if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING); } /** diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java index 85adabd..fad4f64 100644 --- a/core/java/android/text/method/PasswordTransformationMethod.java +++ b/core/java/android/text/method/PasswordTransformationMethod.java @@ -105,8 +105,10 @@ implements TransformationMethod, TextWatcher sp.removeSpan(old[i]); } - sp.setSpan(new Visible(sp, this), start, start + count, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (count == 1) { + sp.setSpan(new Visible(sp, this), start, start + count, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } } } diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java new file mode 100644 index 0000000..cc3563c --- /dev/null +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * Constants to be used to perform haptic feedback effects via + * {@link View#performHapticFeedback(int)} + */ +public class HapticFeedbackConstants { + + private HapticFeedbackConstants() {} + + public static final int LONG_PRESS = 0; + + /** @hide pending API council */ + public static final int ZOOM_RING_TICK = 1; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the setting in the + * view for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the global setting + * for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002; +} diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 7276f17..1156856 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -106,5 +106,6 @@ interface IWindowSession { void setInTouchMode(boolean showFocus); boolean getInTouchMode(); + + boolean performHapticFeedback(IWindow window, int effectId, boolean always); } - diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a51b564..1d5e7cd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -836,6 +836,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback { public static final int SOUND_EFFECTS_ENABLED = 0x08000000; /** + * View flag indicating whether this view should have haptic feedback + * enabled for events such as long presses. + */ + public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; + + /** * Use with {@link #focusSearch}. Move focus to the previous selectable * item. */ @@ -1637,6 +1643,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; + mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED; ++sInstanceCount; } @@ -1703,9 +1710,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback { int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; - viewFlagValues |= SOUND_EFFECTS_ENABLED; - viewFlagMasks |= SOUND_EFFECTS_ENABLED; - final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -1801,6 +1805,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback { viewFlagValues &= ~SOUND_EFFECTS_ENABLED; viewFlagMasks |= SOUND_EFFECTS_ENABLED; } + case com.android.internal.R.styleable.View_hapticFeedbackEnabled: + if (!a.getBoolean(attr, true)) { + viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED; + viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED; + } case R.styleable.View_scrollbars: final int scrollbars = a.getInt(attr, SCROLLBARS_NONE); if (scrollbars != SCROLLBARS_NONE) { @@ -2182,6 +2191,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (!handled) { handled = showContextMenu(); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } return handled; } @@ -2742,7 +2754,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * Set whether this view should have sound effects enabled for events such as * clicking and touching. * - * You may wish to disable sound effects for a view if you already play sounds, + * <p>You may wish to disable sound effects for a view if you already play sounds, * for instance, a dial key that plays dtmf tones. * * @param soundEffectsEnabled whether sound effects are enabled for this view. @@ -2768,6 +2780,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * Set whether this view should have haptic feedback for events such as + * long presses. + * + * <p>You may wish to disable haptic feedback if your view already controls + * its own haptic feedback. + * + * @param hapticFeedbackEnabled whether haptic feedback enabled for this view. + * @see #isHapticFeedbackEnabled() + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) { + setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED); + } + + /** + * @return whether this view should have haptic feedback enabled for events + * long presses. + * + * @see #setHapticFeedbackEnabled(boolean) + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + @ViewDebug.ExportedProperty + public boolean isHapticFeedbackEnabled() { + return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED); + } + + /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. @@ -7312,20 +7353,57 @@ public class View implements Drawable.Callback, KeyEvent.Callback { /** * Play a sound effect for this view. * - * The framework will play sound effects for some built in actions, such as + * <p>The framework will play sound effects for some built in actions, such as * clicking, but you may wish to play these effects in your widget, * for instance, for internal navigation. * - * The sound effect will only be played if sound effects are enabled by the user, and + * <p>The sound effect will only be played if sound effects are enabled by the user, and * {@link #isSoundEffectsEnabled()} is true. * * @param soundConstant One of the constants defined in {@link SoundEffectConstants} */ - protected void playSoundEffect(int soundConstant) { - if (mAttachInfo == null || mAttachInfo.mSoundEffectPlayer == null || !isSoundEffectsEnabled()) { + public void playSoundEffect(int soundConstant) { + if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { return; } - mAttachInfo.mSoundEffectPlayer.playSoundEffect(soundConstant); + mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant); + } + + /** + * Provide haptic feedback to the user for this view. + * + * <p>The framework will provide haptic feedback for some built in actions, + * such as long presses, but you may wish to provide feedback for your + * own widget. + * + * <p>The feedback will only be performed if + * {@link #isHapticFeedbackEnabled()} is true. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + */ + public boolean performHapticFeedback(int feedbackConstant) { + return performHapticFeedback(feedbackConstant, 0); + } + + /** + * Like {@link #performHapticFeedback(int)}, with additional options. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + * @param flags Additional flags as per {@link HapticFeedbackConstants}. + */ + public boolean performHapticFeedback(int feedbackConstant, int flags) { + if (mAttachInfo == null) { + return false; + } + if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0 + && !isHapticFeedbackEnabled()) { + return false; + } + return mAttachInfo.mRootCallbacks.performHapticFeedback( + feedbackConstant, + (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0); } /** @@ -7704,8 +7782,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { */ static class AttachInfo { - interface SoundEffectPlayer { + interface Callbacks { void playSoundEffect(int effectId); + boolean performHapticFeedback(int effectId, boolean always); } /** @@ -7775,7 +7854,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { final IBinder mWindowToken; - final SoundEffectPlayer mSoundEffectPlayer; + final Callbacks mRootCallbacks; /** * The top view of the hierarchy. @@ -7922,12 +8001,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * @param handler the events handler the view must use */ AttachInfo(IWindowSession session, IWindow window, - Handler handler, SoundEffectPlayer effectPlayer) { + Handler handler, Callbacks effectPlayer) { mSession = session; mWindow = window; mWindowToken = window.asBinder(); mHandler = handler; - mSoundEffectPlayer = effectPlayer; + mRootCallbacks = effectPlayer; } } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 4e46397..ccfa6bf 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -61,7 +61,7 @@ import static javax.microedition.khronos.opengles.GL10.*; */ @SuppressWarnings({"EmptyCatchBlock"}) public final class ViewRoot extends Handler implements ViewParent, - View.AttachInfo.SoundEffectPlayer { + View.AttachInfo.Callbacks { private static final String TAG = "ViewRoot"; private static final boolean DBG = false; @SuppressWarnings({"ConstantConditionalExpression"}) @@ -1637,7 +1637,7 @@ public final class ViewRoot extends Handler implements ViewParent, dispatchDetachedFromWindow(); break; case DISPATCH_KEY_FROM_IME: - if (true) Log.v( + if (LOCAL_LOGV) Log.v( "ViewRoot", "Dispatching key " + msg.obj + " from IME to " + mView); deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); @@ -2238,6 +2238,17 @@ public final class ViewRoot extends Handler implements ViewParent, /** * {@inheritDoc} */ + public boolean performHapticFeedback(int effectId, boolean always) { + try { + return sWindowSession.performHapticFeedback(mWindow, effectId, always); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@inheritDoc} + */ public View focusSearch(View focused, int direction) { checkThread(); if (!(mView instanceof ViewGroup)) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d08a6fa..406af3e3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -925,7 +925,7 @@ public interface WindowManager extends ViewManager { sb.append(Integer.toHexString(windowAnimations)); } if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { - sb.append("or="); + sb.append(" or="); sb.append(screenOrientation); } sb.append('}'); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 542b35f..051f823 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -771,4 +771,15 @@ public interface WindowManagerPolicy { public boolean isCheekPressedAgainstScreen(MotionEvent ev); public void setCurrentOrientation(int newOrientation); + + /** + * Call from application to perform haptic feedback on its window. + */ + public boolean performHapticFeedback(WindowState win, int effectId, boolean always); + + /** + * Called when we have stopped keeping the screen on because a window + * requesting this is no longer visible. + */ + public void screenOnStopped(); } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 56c6c92..9509b15 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -371,6 +371,14 @@ public class BaseInputConnection implements InputConnection { if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); final Editable content = getEditable(); if (content == null) return false; + int len = content.length(); + if (start > len || end > len) { + // If the given selection is out of bounds, just ignore it. + // Most likely the text was changed out from under the IME, + // the the IME is going to have to update all of its state + // anyway. + return true; + } Selection.setSelection(content, start, end); return true; } @@ -396,20 +404,10 @@ public class BaseInputConnection implements InputConnection { } /** - * Provides standard implementation for hiding the status icon associated - * with the current input method. + * Updates InputMethodManager with the current fullscreen mode. */ - public boolean hideStatusIcon() { - mIMM.updateStatusIcon(0, null); - return true; - } - - /** - * Provides standard implementation for showing the status icon associated - * with the current input method. - */ - public boolean showStatusIcon(String packageName, int resId) { - mIMM.updateStatusIcon(resId, packageName); + public boolean reportFullscreenMode(boolean enabled) { + mIMM.setFullscreenMode(enabled); return true; } @@ -420,7 +418,11 @@ public class BaseInputConnection implements InputConnection { Editable content = getEditable(); if (content != null) { - if (content.length() == 1) { + final int N = content.length(); + if (N == 0) { + return; + } + if (N == 1) { // If it's 1 character, we have a chance of being // able to generate normal key events... if (mKeyCharacterMap == null) { diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 8c30d3f..13173f6 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -267,6 +267,13 @@ public interface InputConnection { public boolean clearMetaKeyStates(int states); /** + * Called by the IME to tell the client when it switches between fullscreen + * and normal modes. This will normally be called for you by the standard + * implementation of {@link android.inputmethodservice.InputMethodService}. + */ + public boolean reportFullscreenMode(boolean enabled); + + /** * API to send private commands from an input method to its connected * editor. This can be used to provide domain-specific features that are * only known between certain input methods and their clients. Note that @@ -284,23 +291,4 @@ public interface InputConnection { * valid. */ public boolean performPrivateCommand(String action, Bundle data); - - /** - * Show an icon in the status bar. - * - * @param packageName The package holding the icon resource to be shown. - * @param resId The resource id of the icon to show. - * - * @return Returns true on success, false if the input connection is no longer - * valid. - */ - public boolean showStatusIcon(String packageName, int resId); - - /** - * Hide the icon shown in the status bar. - * - * @return Returns true on success, false if the input connection is no longer - * valid. - */ - public boolean hideStatusIcon(); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 99d5aa5..fe14166 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -214,6 +214,11 @@ public final class InputMethodManager { */ boolean mActive = false; + /** + * As reported by IME through InputConnection. + */ + boolean mFullscreenMode; + // ----------------------------------------------------------- /** @@ -374,6 +379,7 @@ public final class InputMethodManager { public void setActive(boolean active) { mActive = active; + mFullscreenMode = false; } }; @@ -443,14 +449,36 @@ public final class InputMethodManager { } } - public void updateStatusIcon(int iconId, String iconPackage) { + public void showStatusIcon(IBinder imeToken, String packageName, int iconId) { + try { + mService.updateStatusIcon(imeToken, packageName, iconId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void hideStatusIcon(IBinder imeToken) { try { - mService.updateStatusIcon(iconId, iconPackage); + mService.updateStatusIcon(imeToken, null, 0); } catch (RemoteException e) { throw new RuntimeException(e); } } + /** @hide */ + public void setFullscreenMode(boolean enabled) { + mFullscreenMode = true; + } + + /** + * Allows you to discover whether the attached input method is running + * in fullscreen mode. Return true if it is fullscreen, entirely covering + * your UI, else returns false. + */ + public boolean isFullscreenMode() { + return mFullscreenMode; + } + /** * Return true if the given view is the currently active view for the * input method. @@ -503,7 +531,6 @@ public final class InputMethodManager { void finishInputLocked() { if (mServedView != null) { if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); - updateStatusIcon(0, null); if (mCurrentTextBoxAttribute != null) { try { diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 5a37f04..07c1a5d 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -171,6 +171,10 @@ public final class CookieManager { boolean pathMatch(String urlPath) { if (urlPath.startsWith(path)) { int len = path.length(); + if (len == 0) { + Log.w(LOGTAG, "Empty cookie path"); + return false; + } int urlLen = urlPath.length(); if (path.charAt(len-1) != PATH_DELIM && urlLen > len) { // make sure /wee doesn't match /we @@ -864,7 +868,10 @@ public final class CookieManager { "illegal format for max-age: " + value); } } else if (name.equals(PATH)) { - cookie.path = value; + // only allow non-empty path value + if (value.length() > 0) { + cookie.path = value; + } } else if (name.equals(DOMAIN)) { int lastPeriod = value.lastIndexOf(PERIOD); if (lastPeriod == 0) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 3306700..bdbf38a 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -260,19 +260,8 @@ public class WebView extends AbsoluteLayout // Whether we are in the drag tap mode, which exists starting at the second // tap's down, through its move, and includes its up. These events should be // given to the method on the zoom controller. - private boolean mInZoomTapDragMode; - - // The event time of the previous touch up. - private long mPreviousUpTime; - - private Runnable mRemoveReleaseSingleTap = new Runnable() { - public void run() { - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - } - }; - + private boolean mInZoomTapDragMode = false; + // Whether to prevent drag during touch. The initial value depends on // mForwardTouchEvents. If WebCore wants touch events, we assume it will // take control of touch events unless it says no for touch down event. @@ -517,6 +506,11 @@ public class WebView extends AbsoluteLayout private ZoomRingController mZoomRingController; + // These keep track of the center point of the zoom ring. They are used to + // determine the point around which we should zoom. + private float mZoomCenterX; + private float mZoomCenterY; + private ZoomRingController.OnZoomListener mZoomListener = new ZoomRingController.OnZoomListener() { @@ -554,12 +548,9 @@ public class WebView extends AbsoluteLayout deltaZoomLevel == 0) { return false; } - - int deltaX = centerX - getViewWidth() / 2; - int deltaY = centerY - getViewHeight() / 2; + mZoomCenterX = (float) centerX; + mZoomCenterY = (float) centerY; - pinScrollBy(deltaX, deltaY, false, 0); - while (deltaZoomLevel != 0) { if (deltaZoomLevel > 0) { if (!zoomIn()) return false; @@ -569,15 +560,16 @@ public class WebView extends AbsoluteLayout deltaZoomLevel++; } } - - pinScrollBy(-deltaX, -deltaY, false, 0); - + return true; } public void onSimpleZoom(boolean zoomIn) { - if (zoomIn) zoomIn(); - else zoomOut(); + if (zoomIn) { + zoomIn(); + } else { + zoomOut(); + } } }; @@ -1586,8 +1578,8 @@ public class WebView extends AbsoluteLayout int oldX = mScrollX; int oldY = mScrollY; float ratio = scale * mInvActualScale; // old inverse - float sx = ratio * oldX + (ratio - 1) * getViewWidth() * 0.5f; - float sy = ratio * oldY + (ratio - 1) * getViewHeight() * 0.5f; + float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; + float sy = ratio * oldY + (ratio - 1) * mZoomCenterY; // now update our new scale and inverse if (scale != mActualScale && !mPreviewZoomOnly) { @@ -2264,8 +2256,8 @@ public class WebView extends AbsoluteLayout zoomScale = mZoomScale; } float scale = (mActualScale - zoomScale) * mInvActualScale; - float tx = scale * ((getLeft() + getRight()) * 0.5f + mScrollX); - float ty = scale * ((getTop() + getBottom()) * 0.5f + mScrollY); + float tx = scale * (mZoomCenterX + mScrollX); + float ty = scale * (mZoomCenterY + mScrollY); // this block pins the translate to "legal" bounds. This makes the // animation a bit non-obvious, but it means we won't pop when the @@ -3025,8 +3017,8 @@ public class WebView extends AbsoluteLayout (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0); break; case KeyEvent.KEYCODE_9: - debugDump(); - break; + nativeInstrumentReport(); + return true; } } @@ -3161,6 +3153,7 @@ public class WebView extends AbsoluteLayout * @hide */ public void emulateShiftHeld() { + mExtendSelection = false; mShiftIsPressed = true; } @@ -3176,6 +3169,7 @@ public class WebView extends AbsoluteLayout mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection); copiedSomething = true; } + mExtendSelection = false; } mShiftIsPressed = false; if (mTouchMode == TOUCH_SELECT_MODE) { @@ -3218,6 +3212,11 @@ public class WebView extends AbsoluteLayout } } + /** + * @deprecated WebView should not have implemented + * ViewTreeObserver.OnGlobalFocusChangeListener. This method + * does nothing now. + */ @Deprecated public void onGlobalFocusChanged(View oldFocus, View newFocus) { } @@ -3281,7 +3280,11 @@ public class WebView extends AbsoluteLayout @Override protected void onSizeChanged(int w, int h, int ow, int oh) { super.onSizeChanged(w, h, ow, oh); - + // Center zooming to the center of the screen. This is appropriate for + // this case of zooming, and it also sets us up properly if we remove + // the new zoom ring controller + mZoomCenterX = getViewWidth() * .5f; + mZoomCenterY = getViewHeight() * .5f; // we always force, in case our height changed, in which case we still // want to send the notification over to webkit setNewZoomScale(mActualScale, true); @@ -3342,25 +3345,12 @@ public class WebView extends AbsoluteLayout + mTouchMode); } - if (mZoomRingController.isVisible()) { - if (mInZoomTapDragMode) { - mZoomRingController.handleDoubleTapEvent(ev); - if (ev.getAction() == MotionEvent.ACTION_UP) { - // Just released the second tap, no longer in tap-drag mode - mInZoomTapDragMode = false; - } - return true; - } else { - // TODO: properly do this. - /* - * When the zoom widget is showing, the user can tap outside of - * it to dismiss it. Furthermore, he can drag outside of it to - * pan the browser. However, we do not want a tap on a link to - * open the link. - */ - post(mRemoveReleaseSingleTap); - // Continue through to normal processing + if (mZoomRingController.isVisible() && mInZoomTapDragMode) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + // Just released the second tap, no longer in tap-drag mode + mInZoomTapDragMode = false; } + return mZoomRingController.handleDoubleTapEvent(ev); } int action = ev.getAction(); @@ -3418,21 +3408,19 @@ public class WebView extends AbsoluteLayout , viewToContent(mSelectY), false); mTouchSelection = mExtendSelection = true; } else if (!ZoomRingController.useOldZoom(mContext) && - eventTime - mPreviousUpTime < DOUBLE_TAP_TIMEOUT && - getSettings().supportZoom() && - mMinZoomScale < mMaxZoomScale) { + mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { // Found doubletap, invoke the zoom controller - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); mZoomRingController.setVisible(true); mInZoomTapDragMode = true; - mZoomRingController.handleDoubleTapEvent(ev); + return mZoomRingController.handleDoubleTapEvent(ev); } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; } - if (mTouchMode == TOUCH_INIT_MODE) { + // don't trigger the link if zoom ring is visible + if (mTouchMode == TOUCH_INIT_MODE + && !mZoomRingController.isVisible()) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); } @@ -3485,9 +3473,6 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); } - // Prevent double-tap from being invoked - mPreviousUpTime = 0; - // if it starts nearly horizontal or vertical, enforce it int ax = Math.abs(deltaX); int ay = Math.abs(deltaY); @@ -3597,6 +3582,10 @@ public class WebView extends AbsoluteLayout case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_INIT_MODE: // tap + if (mZoomRingController.isVisible()) { + // don't trigger the link if zoom ring is visible + break; + } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); if (getSettings().supportZoom() && (mMinZoomScale < mMaxZoomScale)) { @@ -3611,7 +3600,7 @@ public class WebView extends AbsoluteLayout break; case TOUCH_SELECT_MODE: commitCopy(); - mTouchSelection = mExtendSelection = false; + mTouchSelection = false; break; case SCROLL_ZOOM_ANIMATION_IN: case SCROLL_ZOOM_ANIMATION_OUT: @@ -3679,7 +3668,6 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } - mPreviousUpTime = eventTime; break; } case MotionEvent.ACTION_CANCEL: { @@ -4110,6 +4098,14 @@ public class WebView extends AbsoluteLayout } /** + * @hide pending API council? Assuming we make ZoomRingController itself + * public, which I think we will. + */ + public ZoomRingController getZoomRingController() { + return mZoomRingController; + } + + /** * Perform zoom in in the webview * @return TRUE if zoom in succeeds. FALSE if no zoom changes. */ @@ -4193,16 +4189,15 @@ public class WebView extends AbsoluteLayout return; } switchOutDrawHistory(); - // FIXME: we don't know if the current (x,y) is on a focus node or - // not -- so playing the sound effect here is premature - if (nativeUpdateFocusNode()) { - playSoundEffect(SoundEffectConstants.CLICK); - } // mLastTouchX and mLastTouchY are the point in the current viewport int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop(); nativeMotionUp(contentX, contentY, contentSize, true); + if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField + && !mFocusNode.mIsTextArea) { + playSoundEffect(SoundEffectConstants.CLICK); + } } @Override @@ -5013,6 +5008,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeUpdateFocusNode(); private native Rect nativeGetFocusRingBounds(); private native Rect nativeGetNavBounds(); + private native void nativeInstrumentReport(); private native void nativeMarkNodeInvalid(int node); private native void nativeMotionUp(int x, int y, int slop, boolean isClick); // returns false if it handled the key diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 8f78887..b979032 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -330,8 +330,7 @@ final class WebViewCore { String currentText, int keyCode, int keyValue, boolean down, boolean cap, boolean fn, boolean sym); - private native void nativeSaveDocumentState(int frame, int node, int x, - int y); + private native void nativeSaveDocumentState(int frame); private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x, int y, boolean block); @@ -777,8 +776,7 @@ final class WebViewCore { case SAVE_DOCUMENT_STATE: { FocusData fDat = (FocusData) msg.obj; - nativeSaveDocumentState(fDat.mFrame, fDat.mNode, - fDat.mX, fDat.mY); + nativeSaveDocumentState(fDat.mFrame); break; } diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 96f3698..1004e30 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -531,33 +531,34 @@ public class WebViewDatabase { * @param url The url * @return CacheResult The CacheManager.CacheResult */ - @SuppressWarnings("deprecation") CacheResult getCache(String url) { if (url == null || mCacheDatabase == null) { return null; } - CacheResult ret = null; - final String s = "SELECT filepath, lastmodify, etag, expires, mimetype, encoding, httpstatus, location, contentlength FROM cache WHERE url = "; - StringBuilder sb = new StringBuilder(256); - sb.append(s); - DatabaseUtils.appendEscapedSQLString(sb, url); - Cursor cursor = mCacheDatabase.rawQuery(sb.toString(), null); + Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " + + "mimetype, encoding, httpstatus, location, contentlength " + + "FROM cache WHERE url = ?", + new String[] { url }); - if (cursor.moveToFirst()) { - ret = new CacheResult(); - ret.localPath = cursor.getString(0); - ret.lastModified = cursor.getString(1); - ret.etag = cursor.getString(2); - ret.expires = cursor.getLong(3); - ret.mimeType = cursor.getString(4); - ret.encoding = cursor.getString(5); - ret.httpStatusCode = cursor.getInt(6); - ret.location = cursor.getString(7); - ret.contentLength = cursor.getLong(8); + try { + if (cursor.moveToFirst()) { + CacheResult ret = new CacheResult(); + ret.localPath = cursor.getString(0); + ret.lastModified = cursor.getString(1); + ret.etag = cursor.getString(2); + ret.expires = cursor.getLong(3); + ret.mimeType = cursor.getString(4); + ret.encoding = cursor.getString(5); + ret.httpStatusCode = cursor.getInt(6); + ret.location = cursor.getString(7); + ret.contentLength = cursor.getLong(8); + return ret; + } + } finally { + if (cursor != null) cursor.close(); } - cursor.close(); - return ret; + return null; } /** @@ -565,16 +566,12 @@ public class WebViewDatabase { * * @param url The url */ - @SuppressWarnings("deprecation") void removeCache(String url) { if (url == null || mCacheDatabase == null) { return; } - StringBuilder sb = new StringBuilder(256); - sb.append("DELETE FROM cache WHERE url = "); - DatabaseUtils.appendEscapedSQLString(sb, url); - mCacheDatabase.execSQL(sb.toString()); + mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); } /** diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 378d218..c012e25 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -31,6 +31,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -1622,6 +1623,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId); handled = super.showContextMenuForChild(AbsListView.this); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } return handled; } diff --git a/core/java/android/widget/CursorFilter.java b/core/java/android/widget/CursorFilter.java index afd5b10..dbded69 100644 --- a/core/java/android/widget/CursorFilter.java +++ b/core/java/android/widget/CursorFilter.java @@ -60,11 +60,10 @@ class CursorFilter extends Filter { } @Override - protected void publishResults(CharSequence constraint, - FilterResults results) { + protected void publishResults(CharSequence constraint, FilterResults results) { Cursor oldCursor = mClient.getCursor(); - if (results.values != oldCursor) { + if (results.values != null && results.values != oldCursor) { mClient.changeCursor((Cursor) results.values); } } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 67010b2..54f2707 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -47,11 +47,8 @@ public class DatePicker extends FrameLayout { /* UI Components */ private final NumberPicker mDayPicker; private final NumberPicker mMonthPicker; - private final NumberPicker mYearPicker; - - private final int mStartYear; - private final int mEndYear; - + private final NumberPicker mYearPicker; + /** * How we notify users the date has changed. */ @@ -87,12 +84,9 @@ public class DatePicker extends FrameLayout { public DatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.date_picker, - this, // we are the parent - true); - + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.date_picker, this, true); + mDayPicker = (NumberPicker) findViewById(R.id.day); mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); mDayPicker.setSpeed(100); @@ -134,20 +128,17 @@ public class DatePicker extends FrameLayout { }); // attributes - TypedArray a = context - .obtainStyledAttributes(attrs, R.styleable.DatePicker); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); - mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); - mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); + int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); mYearPicker.setRange(mStartYear, mEndYear); a.recycle(); // initialize to current date Calendar cal = Calendar.getInstance(); - init(cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH), null); + init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); // re-order the number pickers to match the current date format reorderPickers(); diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java index 7f1601e..a2316cf 100644 --- a/core/java/android/widget/Filter.java +++ b/core/java/android/widget/Filter.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.util.Log; /** * <p>A filter constrains data with a filtering pattern.</p> @@ -36,6 +37,8 @@ import android.os.Message; * @see android.widget.Filterable */ public abstract class Filter { + private static final String LOG_TAG = "Filter"; + private static final String THREAD_NAME = "Filter"; private static final int FILTER_TOKEN = 0xD0D0F00D; private static final int FINISH_TOKEN = 0xDEADBEEF; @@ -221,6 +224,9 @@ public abstract class Filter { RequestArguments args = (RequestArguments) msg.obj; try { args.results = performFiltering(args.constraint); + } catch (Exception e) { + args.results = new FilterResults(); + Log.w(LOG_TAG, "An exception occured during performFiltering()!", e); } finally { message = mResultHandler.obtainMessage(what); message.obj = args; diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index ffabb02..e7b303a 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -27,6 +27,7 @@ import android.util.Config; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -994,6 +995,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList return; } + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); long id = getItemIdAtPosition(mDownTouchPosition); dispatchLongPress(mDownTouchView, mDownTouchPosition, id); } @@ -1086,6 +1088,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList handled = super.showContextMenuForChild(this); } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + return handled; } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index b5d4e2d..4ae322e 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -48,6 +48,7 @@ import android.widget.RemoteViews.RemoteView; * @attr ref android.R.styleable#ImageView_maxHeight * @attr ref android.R.styleable#ImageView_tint * @attr ref android.R.styleable#ImageView_scaleType + * @attr ref android.R.styleable#ImageView_cropToPadding */ @RemoteView public class ImageView extends View { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a1023bd..25afee8 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -35,6 +35,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater.Filter; import android.view.View.OnClickListener; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -548,6 +550,54 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()} + * or {@link android.widget.ViewFlipper#stopFlipping()} along with + * {@link android.widget.ViewFlipper#setFlipInterval(int)}. + */ + private class SetFlipping extends Action { + public SetFlipping(int id, boolean flipping, int milliseconds) { + this.viewId = id; + this.flipping = flipping; + this.milliseconds = milliseconds; + } + + public SetFlipping(Parcel parcel) { + viewId = parcel.readInt(); + flipping = parcel.readInt() != 0; + milliseconds = parcel.readInt(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeInt(flipping ? 1 : 0); + dest.writeInt(milliseconds); + } + + @Override + public void apply(View root) { + final View target = root.findViewById(viewId); + if (target instanceof ViewFlipper) { + final ViewFlipper flipper = (ViewFlipper) target; + if (milliseconds != -1) { + flipper.setFlipInterval(milliseconds); + } + if (flipping) { + flipper.startFlipping(); + } else { + flipper.stopFlipping(); + } + } + } + + int viewId; + boolean flipping; + int milliseconds; + + public final static int TAG = 10; + } + + /** * Create a new RemoteViews object that will display the views contained * in the specified layout file. * @@ -603,6 +653,9 @@ public class RemoteViews implements Parcelable, Filter { case SetTextColor.TAG: mActions.add(new SetTextColor(parcel)); break; + case SetFlipping.TAG: + mActions.add(new SetFlipping(parcel)); + break; default: throw new ActionException("Tag " + tag + "not found"); } @@ -769,6 +822,22 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()} + * or {@link android.widget.ViewFlipper#stopFlipping()} along with + * {@link android.widget.ViewFlipper#setFlipInterval(int)}. + * + * @param viewId The id of the view to apply changes to + * @param flipping True means we should + * {@link android.widget.ViewFlipper#startFlipping()}, otherwise + * {@link android.widget.ViewFlipper#stopFlipping()}. + * @param milliseconds How long to wait before flipping to the next view, or + * -1 to leave unchanged. + */ + public void setFlipping(int viewId, boolean flipping, int milliseconds) { + addAction(new SetFlipping(viewId, flipping, milliseconds)); + } + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d21c017..2ae5d4e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3844,7 +3844,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean doDown = true; if (otherEvent != null) { try { - boolean handled = mMovement.onKeyOther(this, (Editable) mText, + boolean handled = mMovement.onKeyOther(this, (Spannable) mText, otherEvent); doDown = false; if (handled) { diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 8c652e5..fa8935e 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -28,6 +28,9 @@ import android.view.animation.AnimationUtils; /** * Base class for a {@link FrameLayout} container that will perform animations * when switching between its views. + * + * @attr ref android.R.styleable#ViewAnimator_inAnimation + * @attr ref android.R.styleable#ViewAnimator_outAnimation */ public class ViewAnimator extends FrameLayout { diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index a3c15d9..e20bfdf 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -22,12 +22,16 @@ import android.content.res.TypedArray; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; +import android.widget.RemoteViews.RemoteView; /** * Simple {@link ViewAnimator} that will animate between two or more views * that have been added to it. Only one child is shown at a time. If * requested, can automatically flip between each child at a regular interval. + * + * @attr ref android.R.styleable#ViewFlipper_flipInterval */ +@RemoteView public class ViewFlipper extends ViewAnimator { private int mFlipInterval = 3000; private boolean mKeepFlipping = false; diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index df3f307..0df919d 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -57,6 +58,7 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() { @Override public void onLongPress(MotionEvent e) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); onLongClick(ZoomButton.this); } }); diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java index 20d6056..be3b1fb 100644 --- a/core/java/android/widget/ZoomRing.java +++ b/core/java/android/widget/ZoomRing.java @@ -6,10 +6,9 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; -import android.os.Handler; +import android.graphics.drawable.RotateDrawable; import android.util.AttributeSet; -import android.util.Log; -import android.view.KeyEvent; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -18,17 +17,20 @@ import android.view.ViewConfiguration; * @hide */ public class ZoomRing extends View { - + // TODO: move to ViewConfiguration? - private static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); + static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout(); // TODO: get from theme private static final int DISABLED_ALPHA = 160; - + private static final String TAG = "ZoomRing"; + // TODO: Temporary until the trail is done + private static final boolean DRAW_TRAIL = false; + // TODO: xml - private static final int THUMB_DISTANCE = 63; - + private static final int THUMB_DISTANCE = 63; + /** To avoid floating point calculations, we multiply radians by this value. */ public static final int RADIAN_INT_MULTIPLIER = 100000000; /** PI using our multiplier. */ @@ -36,68 +38,81 @@ public class ZoomRing extends View { /** PI/2 using our multiplier. */ private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2; + private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3; + private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 4; - + /** The cached X of our center. */ private int mCenterX; - /** The cached Y of our center. */ + /** The cached Y of our center. */ private int mCenterY; /** The angle of the thumb (in int radians) */ private int mThumbAngle; private boolean mIsThumbAngleValid; - private int mThumbCenterX; - private int mThumbCenterY; private int mThumbHalfWidth; private int mThumbHalfHeight; - - private int mCallbackThreshold = Integer.MAX_VALUE; - - /** The accumulated amount of drag for the thumb (in int radians). */ - private int mAcculumalatedThumbDrag = 0; - + /** The inner radius of the track. */ private int mBoundInnerRadiusSquared = 0; /** The outer radius of the track. */ private int mBoundOuterRadiusSquared = Integer.MAX_VALUE; - + private int mPreviousWidgetDragX; private int mPreviousWidgetDragY; - + private boolean mDrawThumb = true; private Drawable mThumbDrawable; - + private static final int MODE_IDLE = 0; private static final int MODE_DRAG_THUMB = 1; + /** + * User has his finger down, but we are waiting for him to pass the touch + * slop before going into the #MODE_MOVE_ZOOM_RING. This is a good time to + * show the movable hint. + */ + private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4; private static final int MODE_MOVE_ZOOM_RING = 2; private static final int MODE_TAP_DRAG = 3; private int mMode; - private long mPreviousTapTime; - - private Handler mHandler = new Handler(); - + private long mPreviousDownTime; + private int mPreviousDownX; + private int mPreviousDownY; + private Disabler mDisabler = new Disabler(); - + private OnZoomRingCallback mCallback; - + private int mPreviousCallbackAngle; + private int mCallbackThreshold = Integer.MAX_VALUE; + private boolean mResetThumbAutomatically = true; private int mThumbDragStartAngle; - + private final int mTouchSlop; + private Drawable mTrail; + private double mAcculumalatedTrailAngle; + public ZoomRing(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO get drawable from style instead + + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + mTouchSlop = viewConfiguration.getScaledTouchSlop(); + + // TODO get drawables from style instead Resources res = context.getResources(); mThumbDrawable = res.getDrawable(R.drawable.zoom_ring_thumb); - + if (DRAW_TRAIL) { + mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate(); + } + // TODO: add padding to drawable setBackgroundResource(R.drawable.zoom_ring_track); // TODO get from style setBounds(30, Integer.MAX_VALUE); - + mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2; mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2; - + mCallbackThreshold = PI_INT_MULTIPLIED / 6; } @@ -108,7 +123,7 @@ public class ZoomRing extends View { public ZoomRing(Context context) { this(context, null); } - + public void setCallback(OnZoomRingCallback callback) { mCallback = callback; } @@ -132,26 +147,49 @@ public class ZoomRing extends View { mBoundOuterRadiusSquared = Integer.MAX_VALUE; } } - + public void setThumbAngle(int angle) { mThumbAngle = angle; - mThumbCenterX = (int) (Math.cos(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - + mCenterX; - mThumbCenterY = (int) (Math.sin(1f * angle / RADIAN_INT_MULTIPLIER) * THUMB_DISTANCE) - * -1 + mCenterY; + int unoffsetAngle = angle + mZeroAngle; + int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) + mCenterX; + int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * + THUMB_DISTANCE) * -1 + mCenterY; + + mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth, + thumbCenterY - mThumbHalfHeight, + thumbCenterX + mThumbHalfWidth, + thumbCenterY + mThumbHalfHeight); + + if (DRAW_TRAIL) { + double degrees; + degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle)); + int level = (int) (10000.0 * degrees / 360.0); + + mTrail.setLevel((int) (10000.0 * + (-Math.toDegrees(angle / (double) RADIAN_INT_MULTIPLIER) - + degrees + 90) / 360.0)); + ((RotateDrawable) mTrail).getDrawable().setLevel(level); + } + invalidate(); } - + + public void resetThumbAngle(int angle) { + mPreviousCallbackAngle = angle; + setThumbAngle(angle); + } + public void resetThumbAngle() { if (mResetThumbAutomatically) { - setThumbAngle(HALF_PI_INT_MULTIPLIED); + resetThumbAngle(0); } } - + public void setResetThumbAutomatically(boolean resetThumbAutomatically) { mResetThumbAutomatically = resetThumbAutomatically; } - + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), @@ -162,7 +200,7 @@ public class ZoomRing extends View { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - + // Cache the center point mCenterX = (right - left) / 2; mCenterY = (bottom - top) / 2; @@ -172,8 +210,12 @@ public class ZoomRing extends View { if (mThumbAngle == Integer.MIN_VALUE) { resetThumbAngle(); } + + if (DRAW_TRAIL) { + mTrail.setBounds(0, 0, right - left, bottom - top); + } } - + @Override public boolean onTouchEvent(MotionEvent event) { return handleTouch(event.getAction(), event.getEventTime(), @@ -184,61 +226,66 @@ public class ZoomRing extends View { private void resetState() { mMode = MODE_IDLE; mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE; - mAcculumalatedThumbDrag = 0; + mAcculumalatedTrailAngle = 0.0; mIsThumbAngleValid = false; } - + public void setTapDragMode(boolean tapDragMode, int x, int y) { resetState(); mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE; mIsThumbAngleValid = false; - + if (tapDragMode && mCallback != null) { onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY)); } } - + public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) { switch (action) { - + case MotionEvent.ACTION_DOWN: - if (mPreviousTapTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { + if (mPreviousDownTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) { if (mCallback != null) { mCallback.onZoomRingDismissed(); } } else { - mPreviousTapTime = time; + mPreviousDownTime = time; + mPreviousDownX = x; + mPreviousDownY = y; } resetState(); return true; - + case MotionEvent.ACTION_MOVE: // Fall through to code below switch break; - + case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mCallback != null) { - if (mMode == MODE_MOVE_ZOOM_RING) { - mCallback.onZoomRingMovingStopped(); + if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(false); + if (mMode == MODE_MOVE_ZOOM_RING) { + mCallback.onZoomRingMovingStopped(); + } } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { onThumbDragStopped(getAngle(x - mCenterX, y - mCenterY)); } } mDisabler.setEnabling(true); return true; - + default: return false; } - + // local{X,Y} will be where the center of the widget is (0,0) int localX = x - mCenterX; int localY = y - mCenterY; boolean isTouchingThumb = true; boolean isInBounds = true; int touchAngle = getAngle(localX, localY); - + int radiusSquared = localX * localX + localY * localY; if (radiusSquared < mBoundInnerRadiusSquared || radiusSquared > mBoundOuterRadiusSquared) { @@ -246,7 +293,7 @@ public class ZoomRing extends View { isTouchingThumb = false; isInBounds = false; } - + int deltaThumbAndTouch = getDelta(touchAngle, mThumbAngle); int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ? deltaThumbAndTouch : -deltaThumbAndTouch; @@ -255,19 +302,35 @@ public class ZoomRing extends View { // Didn't grab close enough to the thumb isTouchingThumb = false; } - + if (mMode == MODE_IDLE) { - mMode = isTouchingThumb ? MODE_DRAG_THUMB : MODE_MOVE_ZOOM_RING; - + if (isTouchingThumb) { + mMode = MODE_DRAG_THUMB; + } else { + mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING; + } + if (mCallback != null) { if (mMode == MODE_DRAG_THUMB) { onThumbDragStarted(touchAngle); - } else if (mMode == MODE_MOVE_ZOOM_RING) { + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(true); + } + } + + } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + if (Math.abs(x - mPreviousDownX) > mTouchSlop || + Math.abs(y - mPreviousDownY) > mTouchSlop) { + /* Make sure the user has moved the slop amount before going into that mode. */ + mMode = MODE_MOVE_ZOOM_RING; + + if (mCallback != null) { mCallback.onZoomRingMovingStarted(); } } } - + + // Purposefully not an "else if" if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { if (isInBounds) { onThumbDragged(touchAngle, mIsThumbAngleValid ? deltaThumbAndTouch : 0); @@ -277,13 +340,13 @@ public class ZoomRing extends View { } else if (mMode == MODE_MOVE_ZOOM_RING) { onZoomRingMoved(rawX, rawY); } - + return true; } - + private int getDelta(int angle1, int angle2) { int delta = angle1 - angle2; - + // Assume this is a result of crossing over the discontinuous 0 -> 2pi if (delta > PI_INT_MULTIPLIED || delta < -PI_INT_MULTIPLIED) { // Bring both the radians and previous angle onto a continuous range @@ -295,7 +358,7 @@ public class ZoomRing extends View { delta -= PI_INT_MULTIPLIED * 2; } } - + return delta; } @@ -303,46 +366,69 @@ public class ZoomRing extends View { mThumbDragStartAngle = startAngle; mCallback.onZoomRingThumbDraggingStarted(startAngle); } - + private void onThumbDragged(int touchAngle, int deltaAngle) { - mAcculumalatedThumbDrag += deltaAngle; - if (mAcculumalatedThumbDrag > mCallbackThreshold - || mAcculumalatedThumbDrag < -mCallbackThreshold) { + mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); + int totalDeltaAngle = getDelta(touchAngle, mPreviousCallbackAngle); + if (totalDeltaAngle > mCallbackThreshold + || totalDeltaAngle < -mCallbackThreshold) { if (mCallback != null) { boolean canStillZoom = mCallback.onZoomRingThumbDragged( - mAcculumalatedThumbDrag / mCallbackThreshold, - mAcculumalatedThumbDrag, mThumbDragStartAngle, touchAngle); + totalDeltaAngle / mCallbackThreshold, + mThumbDragStartAngle, touchAngle); mDisabler.setEnabling(canStillZoom); + + if (canStillZoom) { + // TODO: we're trying the haptics to see how it goes with + // users, so we're ignoring the settings (for now) + performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } } - mAcculumalatedThumbDrag = 0; + + // Get the closest tick and lock on there + mPreviousCallbackAngle = getClosestTickAngle(touchAngle); } - + setThumbAngle(touchAngle); mIsThumbAngleValid = true; } - + + private int getClosestTickAngle(int angle) { + int smallerAngleDistance = angle % mCallbackThreshold; + int smallerAngle = angle - smallerAngleDistance; + if (smallerAngleDistance < mCallbackThreshold / 2) { + // Closer to the smaller angle + return smallerAngle; + } else { + // Closer to the bigger angle (premodding) + return (smallerAngle + mCallbackThreshold) % (PI_INT_MULTIPLIED * 2); + } + } + private void onThumbDragStopped(int stopAngle) { mCallback.onZoomRingThumbDraggingStopped(stopAngle); } - + private void onZoomRingMoved(int x, int y) { if (mPreviousWidgetDragX != Integer.MIN_VALUE) { int deltaX = x - mPreviousWidgetDragX; int deltaY = y - mPreviousWidgetDragY; - + if (mCallback != null) { mCallback.onZoomRingMoved(deltaX, deltaY); } } - + mPreviousWidgetDragX = x; mPreviousWidgetDragY = y; } - + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); - + if (!hasWindowFocus && mCallback != null) { mCallback.onZoomRingDismissed(); } @@ -353,22 +439,25 @@ public class ZoomRing extends View { // Convert from [-pi,pi] to {0,2pi] if (radians < 0) { - return -radians; + radians = -radians; } else if (radians > 0) { - return 2 * PI_INT_MULTIPLIED - radians; + radians = 2 * PI_INT_MULTIPLIED - radians; } else { - return 0; + radians = 0; } + + radians = radians - mZeroAngle; + return radians >= 0 ? radians : radians + 2 * PI_INT_MULTIPLIED; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - + if (mDrawThumb) { - mThumbDrawable.setBounds(mThumbCenterX - mThumbHalfWidth, mThumbCenterY - - mThumbHalfHeight, mThumbCenterX + mThumbHalfWidth, mThumbCenterY - + mThumbHalfHeight); + if (DRAW_TRAIL) { + mTrail.draw(canvas); + } mThumbDrawable.draw(canvas); } } @@ -409,12 +498,14 @@ public class ZoomRing extends View { } public interface OnZoomRingCallback { + void onZoomRingSetMovableHintVisible(boolean visible); + void onZoomRingMovingStarted(); boolean onZoomRingMoved(int deltaX, int deltaY); void onZoomRingMovingStopped(); void onZoomRingThumbDraggingStarted(int startAngle); - boolean onZoomRingThumbDragged(int numLevels, int dragAmount, int startAngle, int curAngle); + boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle); void onZoomRingThumbDraggingStopped(int endAngle); void onZoomRingDismissed(); diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java index 2ca0374..eb28767 100644 --- a/core/java/android/widget/ZoomRingController.java +++ b/core/java/android/widget/ZoomRingController.java @@ -17,14 +17,17 @@ package android.widget; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.os.Vibrator; import android.provider.Settings; import android.util.Log; import android.view.Gravity; @@ -42,6 +45,7 @@ import android.view.animation.DecelerateInterpolator; /** * TODO: Docs + * * @hide */ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, @@ -222,7 +226,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, public ZoomRingController(Context context, View ownerView) { mContext = context; mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - + mOwnerView = ownerView; mZoomRing = new ZoomRing(context); @@ -437,7 +441,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, case MotionEvent.ACTION_UP: mTouchMode = TOUCH_MODE_IDLE; + + /* + * This is a power-user feature that only shows the + * zoom while the user is performing the tap-drag. + * That means once it is released, the zoom ring + * should disappear. + */ mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY()); + dismissZoomRingDelayed(0); break; } break; @@ -560,10 +572,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY); } + public void onZoomRingSetMovableHintVisible(boolean visible) { + setPanningArrowsVisible(visible); + } + public void onZoomRingMovingStarted() { mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); mScroller.abortAnimation(); - setPanningArrowsVisible(true); } private void setPanningArrowsVisible(boolean visible) { @@ -641,8 +656,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, } } - public boolean onZoomRingThumbDragged(int numLevels, int dragAmount, int startAngle, - int curAngle) { + public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) { if (mCallback != null) { int deltaZoomLevel = -numLevels; int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() + @@ -650,7 +664,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() + mZoomRingHeight / 2; - return mCallback.onDragZoom(deltaZoomLevel, globalZoomCenterX - mOwnerViewBounds.left, + return mCallback.onDragZoom(deltaZoomLevel, + globalZoomCenterX - mOwnerViewBounds.left, globalZoomCenterY - mOwnerViewBounds.top, (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER, (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER); @@ -719,6 +734,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, ensureZoomRingIsCentered(); } + /** + * Shows a "tutorial" (some text) to the user teaching her the new zoom + * invocation method. + * <p> + * It checks the global system setting to ensure this has not been seen + * before. Furthermore, if the application does not have privilege to write + * to the system settings, it will store this bit locally in a shared + * preference. + * + * @hide This should only be used by our main apps--browser, maps, and + * gallery + */ + public static void showZoomTutorialOnce(Context context) { + ContentResolver cr = context.getContentResolver(); + if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) { + return; + } + + SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); + if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) { + return; + } + + try { + Settings.System.putInt(cr, SETTING_NAME_SHOWN_TOAST, 1); + } catch (SecurityException e) { + /* + * The app does not have permission to clear this global flag, make + * sure the user does not see the message when he comes back to this + * same app at least. + */ + sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit(); + } + + Toast.makeText(context, + com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short, + Toast.LENGTH_LONG).show(); + } + private class Panner implements Runnable { private static final int RUN_DELAY = 15; private static final float STOP_SLOWDOWN = 0.8f; diff --git a/core/java/com/android/internal/gadget/IGadgetHost.aidl b/core/java/com/android/internal/gadget/IGadgetHost.aidl index a5b8654..e7b5a1e 100644 --- a/core/java/com/android/internal/gadget/IGadgetHost.aidl +++ b/core/java/com/android/internal/gadget/IGadgetHost.aidl @@ -17,11 +17,12 @@ package com.android.internal.gadget; import android.content.ComponentName; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import android.widget.RemoteViews; /** {@hide} */ oneway interface IGadgetHost { void updateGadget(int gadgetId, in RemoteViews views); + void providerChanged(int gadgetId, in GadgetProviderInfo info); } diff --git a/core/java/com/android/internal/gadget/IGadgetService.aidl b/core/java/com/android/internal/gadget/IGadgetService.aidl index 1b3946f..a22f3f3 100644 --- a/core/java/com/android/internal/gadget/IGadgetService.aidl +++ b/core/java/com/android/internal/gadget/IGadgetService.aidl @@ -17,7 +17,7 @@ package com.android.internal.gadget; import android.content.ComponentName; -import android.gadget.GadgetInfo; +import android.gadget.GadgetProviderInfo; import com.android.internal.gadget.IGadgetHost; import android.widget.RemoteViews; @@ -41,8 +41,8 @@ interface IGadgetService { // void updateGadgetIds(in int[] gadgetIds, in RemoteViews views); void updateGadgetProvider(in ComponentName provider, in RemoteViews views); - List<GadgetInfo> getInstalledProviders(); - GadgetInfo getGadgetInfo(int gadgetId); + List<GadgetProviderInfo> getInstalledProviders(); + GadgetProviderInfo getGadgetInfo(int gadgetId); void bindGadgetId(int gadgetId, in ComponentName provider); } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index b0b00b2..ac72a20 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -32,8 +32,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_DELETE_SURROUNDING_TEXT = 80; private static final int DO_BEGIN_BATCH_EDIT = 90; private static final int DO_END_BATCH_EDIT = 95; - private static final int DO_HIDE_STATUS_ICON = 100; - private static final int DO_SHOW_STATUS_ICON = 110; + private static final int DO_REPORT_FULLSCREEN_MODE = 100; private static final int DO_PERFORM_PRIVATE_COMMAND = 120; private static final int DO_CLEAR_META_KEY_STATES = 130; @@ -133,12 +132,8 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessage(DO_END_BATCH_EDIT)); } - public void hideStatusIcon() { - dispatchMessage(obtainMessage(DO_HIDE_STATUS_ICON)); - } - - public void showStatusIcon(String packageName, int resId) { - dispatchMessage(obtainMessageIO(DO_SHOW_STATUS_ICON, resId, packageName)); + public void reportFullscreenMode(boolean enabled) { + dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0)); } public void performPrivateCommand(String action, Bundle data) { @@ -323,22 +318,13 @@ public class IInputConnectionWrapper extends IInputContext.Stub { ic.endBatchEdit(); return; } - case DO_HIDE_STATUS_ICON: { - InputConnection ic = mInputConnection.get(); - if (ic == null || !isActive()) { - Log.w(TAG, "hideStatusIcon on inactive InputConnection"); - return; - } - ic.hideStatusIcon(); - return; - } - case DO_SHOW_STATUS_ICON: { + case DO_REPORT_FULLSCREEN_MODE: { InputConnection ic = mInputConnection.get(); if (ic == null || !isActive()) { Log.w(TAG, "showStatusIcon on inactive InputConnection"); return; } - ic.showStatusIcon((String)msg.obj, msg.arg1); + ic.reportFullscreenMode(msg.arg1 != 1); return; } case DO_PERFORM_PRIVATE_COMMAND: { diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 7cc8ada..02b6044 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -56,13 +56,11 @@ import com.android.internal.view.IInputContextCallback; void endBatchEdit(); + void reportFullscreenMode(boolean enabled); + void sendKeyEvent(in KeyEvent event); void clearMetaKeyStates(int states); void performPrivateCommand(String action, in Bundle data); - - void showStatusIcon(String packageName, int resId); - - void hideStatusIcon(); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 2f5cd14..1b1c7f7 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -47,7 +47,7 @@ interface IInputMethodManager { void showInputMethodPickerFromClient(in IInputMethodClient client); void setInputMethod(in IBinder token, String id); void hideMySoftInput(in IBinder token, int flags); - void updateStatusIcon(int iconId, String iconPackage); + void updateStatusIcon(in IBinder token, String packageName, int iconId); boolean setInputMethodEnabled(String id, boolean enabled); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index af4ad25..32d9f3d 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -322,18 +322,9 @@ public class InputConnectionWrapper implements InputConnection { } } - public boolean hideStatusIcon() { + public boolean reportFullscreenMode(boolean enabled) { try { - mIInputContext.showStatusIcon(null, 0); - return true; - } catch (RemoteException e) { - return false; - } - } - - public boolean showStatusIcon(String packageName, int resId) { - try { - mIInputContext.showStatusIcon(packageName, resId); + mIInputContext.reportFullscreenMode(enabled); return true; } catch (RemoteException e) { return false; diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java index 20ea6a6..1647c20 100644 --- a/core/java/com/android/internal/widget/NumberPicker.java +++ b/core/java/com/android/internal/widget/NumberPicker.java @@ -28,12 +28,8 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnLongClickListener; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.EditText; -import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.ViewSwitcher; +import android.widget.LinearLayout; import com.android.internal.R; @@ -71,25 +67,18 @@ public class NumberPicker extends LinearLayout implements OnClickListener, private final Runnable mRunnable = new Runnable() { public void run() { if (mIncrement) { - changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation); + changeCurrent(mCurrent + 1); mHandler.postDelayed(this, mSpeed); } else if (mDecrement) { - changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation); + changeCurrent(mCurrent - 1); mHandler.postDelayed(this, mSpeed); } } }; - - private final LayoutInflater mInflater; + private final TextView mText; - private final InputFilter mInputFilter; private final InputFilter mNumberInputFilter; - - private final Animation mSlideUpOutAnimation; - private final Animation mSlideUpInAnimation; - private final Animation mSlideDownOutAnimation; - private final Animation mSlideDownInAnimation; - + private String[] mDisplayedValues; private int mStart; private int mEnd; @@ -110,14 +99,14 @@ public class NumberPicker extends LinearLayout implements OnClickListener, this(context, attrs, 0); } - public NumberPicker(Context context, AttributeSet attrs, - int defStyle) { + @SuppressWarnings({"UnusedDeclaration"}) + public NumberPicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); setOrientation(VERTICAL); - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mInflater.inflate(R.layout.number_picker, this, true); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.number_picker, this, true); mHandler = new Handler(); - mInputFilter = new NumberPickerInputFilter(); + InputFilter inputFilter = new NumberPickerInputFilter(); mNumberInputFilter = new NumberRangeKeyListener(); mIncrementButton = (NumberPickerButton) findViewById(R.id.increment); mIncrementButton.setOnClickListener(this); @@ -130,30 +119,9 @@ public class NumberPicker extends LinearLayout implements OnClickListener, mText = (TextView) findViewById(R.id.timepicker_input); mText.setOnFocusChangeListener(this); - mText.setFilters(new InputFilter[] { mInputFilter }); + mText.setFilters(new InputFilter[] {inputFilter}); mText.setRawInputType(InputType.TYPE_CLASS_NUMBER); - - mSlideUpOutAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, -100); - mSlideUpOutAnimation.setDuration(200); - mSlideUpInAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 100, - Animation.RELATIVE_TO_SELF, 0); - mSlideUpInAnimation.setDuration(200); - mSlideDownOutAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 100); - mSlideDownOutAnimation.setDuration(200); - mSlideDownInAnimation = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, - 0, Animation.RELATIVE_TO_SELF, -100, - Animation.RELATIVE_TO_SELF, 0); - mSlideDownInAnimation.setDuration(200); - + if (!isEnabled()) { setEnabled(false); } @@ -228,9 +196,9 @@ public class NumberPicker extends LinearLayout implements OnClickListener, // now perform the increment/decrement if (R.id.increment == v.getId()) { - changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation); + changeCurrent(mCurrent + 1); } else if (R.id.decrement == v.getId()) { - changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation); + changeCurrent(mCurrent - 1); } } @@ -240,7 +208,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener, : String.valueOf(value); } - private void changeCurrent(int current, Animation in, Animation out) { + private void changeCurrent(int current) { // Wrap around the values if we go past the start or end if (current > mEnd) { |