diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-13 13:04:22 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-13 13:04:22 -0700 |
commit | ba87e3e6c985e7175152993b5efcc7dd2f0e1c93 (patch) | |
tree | ee35f76532767dc29411a8738a434d1d88d330f2 /core/java | |
parent | c39a6e0c51e182338deb8b63d07933b585134929 (diff) | |
download | frameworks_base-ba87e3e6c985e7175152993b5efcc7dd2f0e1c93.zip frameworks_base-ba87e3e6c985e7175152993b5efcc7dd2f0e1c93.tar.gz frameworks_base-ba87e3e6c985e7175152993b5efcc7dd2f0e1c93.tar.bz2 |
auto import from //branches/cupcake_rel/...@138607
Diffstat (limited to 'core/java')
30 files changed, 1027 insertions, 395 deletions
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 15e0a4d..64288d2 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -126,14 +126,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // support for AutoCompleteTextView suggestions display private SuggestionsAdapter mSuggestionsAdapter; - private Handler mHandler = new Handler(); - private Runnable mInstallSuggestionAdapter = new Runnable() { - public void run() { - if (mSearchTextField != null) { - mSearchTextField.setAdapter(mSuggestionsAdapter); - } - } - }; /** * Constructor - fires it up and makes it look like the search UI. @@ -261,8 +253,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchTextField.setAdapter(mSuggestionsAdapter); mSearchTextField.setText(initialQuery); } else { - mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, - mHandler, mInstallSuggestionAdapter); + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, + mSearchTextField); mSearchTextField.setAdapter(mSuggestionsAdapter); // finally, load the user's initial text (which may trigger suggestions) @@ -1305,15 +1297,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // These private variables are shared by the filter thread and must be protected private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null); private boolean mNonUserQuery = false; - private Handler mHandler; - private Runnable mInstallSuggestionAdapter; + private AutoCompleteTextView mParentView; public SuggestionsAdapter(Context context, SearchableInfo searchable, - Handler handler, Runnable installSuggestionAdapter) { + AutoCompleteTextView actv) { super(context, -1, null, null, null); mSearchable = searchable; - mHandler = handler; - mInstallSuggestionAdapter = installSuggestionAdapter; + mParentView = actv; // set up provider resources (gives us icons, etc.) Context activityContext = mSearchable.getActivityContext(mContext); @@ -1426,13 +1416,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS to = ONE_LINE_TO; } } + // Force the underlying ListView to discard and reload all layouts + // (Note, this should be optimized for cases where layout/cursor remain same) + mParentView.resetListAndClearViews(); // Now actually set up the cursor, columns, and the list view changeCursorAndColumns(c, from, to); setViewResource(layout); - // Force the underlying ListView to discard and reload all layouts - // (Note, this could be optimized for cases where layout/cursor remain same) - mHandler.post(mInstallSuggestionAdapter); - } else { // Provide some help for developers instead of just silently discarding Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index e23545b..1dbe0cc 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -67,6 +67,11 @@ public class BluetoothHeadset { /** A headset is currently connected */ public static final int STATE_CONNECTED = 2; + /** A SCO audio channel is not established */ + public static final int AUDIO_STATE_DISCONNECTED = 0; + /** A SCO audio channel is established */ + public static final int AUDIO_STATE_CONNECTED = 1; + public static final int RESULT_FAILURE = 0; public static final int RESULT_SUCCESS = 1; /** Connection canceled before completetion. */ diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java index b66b06e..9273d0d 100644 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -45,6 +45,8 @@ public interface BluetoothIntent { "android.bluetooth.intent.HEADSET_STATE"; public static final String HEADSET_PREVIOUS_STATE = "android.bluetooth.intent.HEADSET_PREVIOUS_STATE"; + public static final String HEADSET_AUDIO_STATE = + "android.bluetooth.intent.HEADSET_AUDIO_STATE"; public static final String BOND_STATE = "android.bluetooth.intent.BOND_STATE"; public static final String BOND_PREVIOUS_STATE = @@ -122,7 +124,18 @@ public interface BluetoothIntent { public static final String BOND_STATE_CHANGED_ACTION = "android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION"; + /** + * TODO(API release): Move into BluetoothHeadset + */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String HEADSET_STATE_CHANGED_ACTION = "android.bluetooth.intent.action.HEADSET_STATE_CHANGED"; + + /** + * TODO(API release): Consider incorporating as new state in + * HEADSET_STATE_CHANGED + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String HEADSET_AUDIO_STATE_CHANGED_ACTION = + "android.bluetooth.intent.action.HEADSET_ADUIO_STATE_CHANGED"; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index bb80e10..90ff78a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1026,6 +1026,15 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND"; + + /** + * Activity Action: Start action associated with long pressing on the + * search key. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index f58b7ef..7a63c0c 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -16,34 +16,33 @@ package android.inputmethodservice; -import com.android.internal.R; - import android.content.Context; -import android.content.SharedPreferences; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Paint.Align; +import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard.Key; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; -import android.preference.PreferenceManager; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup.LayoutParams; -import android.widget.Button; import android.widget.PopupWindow; import android.widget.TextView; +import com.android.internal.R; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -221,6 +220,15 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int MULTITAP_INTERVAL = 800; // milliseconds private StringBuilder mPreviewLabel = new StringBuilder(1); + /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ + private boolean mDrawPending; + /** The dirty region in the keyboard bitmap */ + private Rect mDirtyRect = new Rect(); + /** The keyboard bitmap for faster updates */ + private Bitmap mBuffer; + /** The canvas for the above mutable keyboard bitmap */ + private Canvas mCanvas; + Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -396,7 +404,10 @@ public class KeyboardView extends View implements View.OnClickListener { List<Key> keys = mKeyboard.getKeys(); mKeys = keys.toArray(new Key[keys.size()]); requestLayout(); - invalidate(); + // Release buffer, just in case the new keyboard has a different size. + // It will be reallocated on the next draw. + mBuffer = null; + invalidateAll(); computeProximityThreshold(keyboard); mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views } @@ -420,7 +431,7 @@ public class KeyboardView extends View implements View.OnClickListener { if (mKeyboard != null) { if (mKeyboard.setShifted(shifted)) { // The whole keyboard probably needs to be redrawn - invalidate(); + invalidateAll(); return true; } } @@ -545,8 +556,30 @@ public class KeyboardView extends View implements View.OnClickListener { } @Override + public void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Release the buffer, if any and it will be reallocated on the next draw + mBuffer = null; + } + + @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); + if (mDrawPending || mBuffer == null) { + onBufferDraw(); + } + canvas.drawBitmap(mBuffer, 0, 0, null); + } + + private void onBufferDraw() { + if (mBuffer == null) { + mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); + invalidateAll(); + } + final Canvas canvas = mCanvas; + canvas.clipRect(mDirtyRect, Op.REPLACE); + if (mKeyboard == null) return; final Paint paint = mPaint; @@ -557,24 +590,20 @@ public class KeyboardView extends View implements View.OnClickListener { final int kbdPaddingTop = mPaddingTop; final Key[] keys = mKeys; final Key invalidKey = mInvalidatedKey; - //canvas.translate(0, mKeyboardPaddingTop); + paint.setAlpha(255); paint.setColor(mKeyTextColor); boolean drawSingleKey = false; if (invalidKey != null && canvas.getClipBounds(clipRegion)) { -// System.out.println("Key bounds = " + (invalidKey.x + mPaddingLeft) + "," -// + (invalidKey.y + mPaddingTop) + "," -// + (invalidKey.x + invalidKey.width + mPaddingLeft) + "," -// + (invalidKey.y + invalidKey.height + mPaddingTop)); -// System.out.println("Clip bounds =" + clipRegion.toShortString()); - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && - invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && - invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && - invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true; - } + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } } + canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); final int keyCount = keys.length; for (int i = 0; i < keyCount; i++) { final Key key = keys[i]; @@ -645,6 +674,9 @@ public class KeyboardView extends View implements View.OnClickListener { paint.setColor(0xFF00FF00); canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint); } + + mDrawPending = false; + mDirtyRect.setEmpty(); } private int getKeyIndices(int x, int y, int[] allKeys) { @@ -844,12 +876,21 @@ public class KeyboardView extends View implements View.OnClickListener { mPreviewText.setVisibility(VISIBLE); } + private void invalidateAll() { + mDirtyRect.union(0, 0, getWidth(), getHeight()); + mDrawPending = true; + invalidate(); + } + private void invalidateKey(int keyIndex) { if (keyIndex < 0 || keyIndex >= mKeys.length) { return; } final Key key = mKeys[keyIndex]; mInvalidatedKey = key; + mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop, + key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop); + onBufferDraw(); invalidate(key.x + mPaddingLeft, key.y + mPaddingTop, key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop); } @@ -952,7 +993,7 @@ public class KeyboardView extends View implements View.OnClickListener { mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y); mMiniKeyboardOnScreen = true; //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me)); - invalidate(); + invalidateAll(); return true; } return false; @@ -1066,11 +1107,11 @@ public class KeyboardView extends View implements View.OnClickListener { } showPreview(NOT_A_KEY); Arrays.fill(mKeyIndices, NOT_A_KEY); - invalidateKey(keyIndex); // If we're not on a repeating key (which sends on a DOWN event) if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { detectAndSendKey(touchX, touchY, eventTime); } + invalidateKey(keyIndex); mRepeatKeyIndex = NOT_A_KEY; break; } @@ -1110,7 +1151,8 @@ public class KeyboardView extends View implements View.OnClickListener { mHandler.removeMessages(MSG_SHOW_PREVIEW); dismissPopupKeyboard(); - + mBuffer = null; + mCanvas = null; mMiniKeyboardCache.clear(); } @@ -1124,10 +1166,10 @@ public class KeyboardView extends View implements View.OnClickListener { if (mPopupKeyboard.isShowing()) { mPopupKeyboard.dismiss(); mMiniKeyboardOnScreen = false; - invalidate(); + invalidateAll(); } } - + public boolean handleBack() { if (mPopupKeyboard.isShowing()) { dismissPopupKeyboard(); diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl index 88dae85..96d44b6 100644 --- a/core/java/android/os/IMountService.aidl +++ b/core/java/android/os/IMountService.aidl @@ -50,7 +50,7 @@ interface IMountService void unmountMedia(String mountPoint); /** - * Format external storage given a mount point + * Format external storage given a mount point. */ void formatMedia(String mountPoint); @@ -63,4 +63,16 @@ interface IMountService * Sets whether or not media notification sounds are played. */ void setPlayNotificationSounds(boolean value); + + /** + * Returns true if USB Mass Storage is automatically started + * when a UMS host is detected. + */ + boolean getAutoStartUms(); + + /** + * Sets whether or not USB Mass Storage is automatically started + * when a UMS host is detected. + */ + void setAutoStartUms(boolean value); } diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index 6e215dc..20702a1 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -194,13 +194,6 @@ public class VolumePreference extends SeekBarPreference implements } private void sample() { - - // Only play a preview sample when controlling the ringer stream - if (mStreamType != AudioManager.STREAM_RING - && mStreamType != AudioManager.STREAM_NOTIFICATION) { - return; - } - onSampleStarting(this); mRingtone.play(); } diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 76aa51d..c597b3c 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -43,6 +43,18 @@ public class Browser { */ public static final String INITIAL_ZOOM_LEVEL = "browser.initialZoomLevel"; + /** + * The name of the extra data when starting the Browser from another + * application. + * <p> + * The value is a unique identification string that will be used to + * indentify the calling application. The Browser will attempt to reuse the + * same window each time the application launches the Browser with the same + * identifier. + */ + public static final String EXTRA_APPLICATION_ID = + "com.android.browser.application_id"; + /* if you change column order you must also change indices below */ public static final String[] HISTORY_PROJECTION = new String[] { diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index d0bd2a5..2aa77ea 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -182,7 +182,7 @@ public class Contacts { * <p>Type: TEXT</P> */ public static final String PHONETIC_NAME = "phonetic_name"; - + /** * The display name. If name is not null name, else if number is not null number, * else if email is not null email. @@ -191,6 +191,14 @@ public class Contacts { public static final String DISPLAY_NAME = "display_name"; /** + * The field for sorting list phonetically. The content of this field + * may not be human readable but phonetically sortable. + * <P>Type: TEXT</p> + * @hide Used only in Contacts application for now. + */ + public static final String SORT_STRING = "sort_string"; + + /** * Notes about the person. * <P>Type: TEXT</P> */ @@ -231,7 +239,7 @@ public class Contacts { * The server version of the photo * <P>Type: TEXT (the version number portion of the photo URI)</P> */ - public static final String PHOTO_VERSION = "photo_version"; + public static final String PHOTO_VERSION = "photo_version"; } /** diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index e271909..9e9ba62 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -81,10 +81,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { */ public synchronized void init() { initializeNativeDataNative(); - mIsEnabled = (isEnabledNative() == 1); - if (mIsEnabled) { - mBondState.loadBondState(); + + if (isEnabledNative() == 1) { + Log.w(TAG, "Bluetooth daemons already running - runtime restart? "); + disableNative(); } + + mIsEnabled = false; mIsDiscovering = false; mEventLoop = new BluetoothEventLoop(mContext, this); registerForAirplaneMode(); diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index efb88a0..29c0c76 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -88,6 +88,7 @@ public class ImageSpan extends DynamicDrawableSpan { super(verticalAlignment); mContext = context; mContentUri = uri; + mSource = uri.toString(); } public ImageSpan(Context context, int resourceId) { @@ -117,6 +118,8 @@ public class ImageSpan extends DynamicDrawableSpan { mContentUri); bitmap = BitmapFactory.decodeStream(is); drawable = new BitmapDrawable(bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { Log.e("sms", "Failed to loaded content " + mContentUri, e); diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index f458611..d29bfb6 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -16,9 +16,11 @@ package android.text.style; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Parcel; +import android.provider.Browser; import android.text.ParcelableSpan; import android.text.TextUtils; import android.view.View; @@ -54,8 +56,9 @@ public class URLSpan extends ClickableSpan implements ParcelableSpan { @Override public void onClick(View widget) { Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - widget.getContext().startActivity(intent); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + context.startActivity(intent); } } diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java index 4c128ad..a349b82 100644 --- a/core/java/android/text/util/Regex.java +++ b/core/java/android/text/util/Regex.java @@ -66,9 +66,9 @@ public class Regex { public static final Pattern WEB_URL_PATTERN = Pattern.compile( "((?:(http|https|Http|Https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" - + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" - + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+)?\\@)?)?" - + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.)+" // named host + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host + "(?:" // plus top level domain + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])" + "|(?:biz|b[abdefghijmnorstvwyz])" @@ -122,12 +122,12 @@ public class Regex { public static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile( - "[a-zA-Z0-9\\+\\.\\_\\%\\-]+" + + "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" + "\\@" + - "[a-zA-Z0-9][a-zA-Z0-9\\-]*" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\." + - "[a-zA-Z0-9][a-zA-Z0-9\\-]*" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+" ); diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 0a043bd..6ea7a82 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -663,7 +663,7 @@ public class ViewDebug { public Object[] pre() { final DisplayMetrics metrics = view.getResources().getDisplayMetrics(); final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels, - metrics.heightPixels, Bitmap.Config.ARGB_8888); + metrics.heightPixels, Bitmap.Config.RGB_565); final Canvas canvas = new Canvas(bitmap); return new Object[] { bitmap, canvas }; } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 9b13d38..dd2b154 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -219,7 +219,7 @@ public final class ViewRoot extends Handler implements ViewParent, mVisRect = new Rect(); mVisPoint = new Point(); mWinFrame = new Rect(); - mWindow = new W(this); + mWindow = new W(this, context); mInputMethodCallback = new InputMethodCallback(this); mViewVisibility = View.GONE; mTransparentRegion = new Region(); @@ -2453,11 +2453,71 @@ public final class ViewRoot extends Handler implements ViewParent, } } + static class EventCompletion extends Handler { + final IWindow mWindow; + final KeyEvent mKeyEvent; + final boolean mIsPointer; + final MotionEvent mMotionEvent; + + EventCompletion(Looper looper, IWindow window, KeyEvent key, + boolean isPointer, MotionEvent motion) { + super(looper); + mWindow = window; + mKeyEvent = key; + mIsPointer = isPointer; + mMotionEvent = motion; + sendEmptyMessage(0); + } + + @Override + public void handleMessage(Message msg) { + if (mKeyEvent != null) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } else if (mIsPointer) { + boolean didFinish; + MotionEvent event = mMotionEvent; + if (event == null) { + try { + event = sWindowSession.getPendingPointerMove(mWindow); + } catch (RemoteException e) { + } + didFinish = true; + } else { + didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; + } + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + } else { + MotionEvent event = mMotionEvent; + if (event == null) { + try { + event = sWindowSession.getPendingTrackballMove(mWindow); + } catch (RemoteException e) { + } + } else { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + } + } + } + static class W extends IWindow.Stub { - private WeakReference<ViewRoot> mViewRoot; + private final WeakReference<ViewRoot> mViewRoot; + private final Looper mMainLooper; - public W(ViewRoot viewRoot) { + public W(ViewRoot viewRoot, Context context) { mViewRoot = new WeakReference<ViewRoot>(viewRoot); + mMainLooper = context.getMainLooper(); } public void resized(int w, int h, Rect coveredInsets, @@ -2475,6 +2535,7 @@ public final class ViewRoot extends Handler implements ViewParent, viewRoot.dispatchKey(event); } else { Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); + new EventCompletion(mMainLooper, this, event, false, null); } } @@ -2482,6 +2543,8 @@ public final class ViewRoot extends Handler implements ViewParent, final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { viewRoot.dispatchPointer(event, eventTime); + } else { + new EventCompletion(mMainLooper, this, null, true, event); } } @@ -2489,6 +2552,8 @@ public final class ViewRoot extends Handler implements ViewParent, final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { viewRoot.dispatchTrackball(event, eventTime); + } else { + new EventCompletion(mMainLooper, this, null, false, event); } } diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 5877932..f1f5f70 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -34,24 +34,12 @@ public abstract class WindowOrientationListener { private static final String TAG = "WindowOrientationListener"; private static final boolean DEBUG = false; private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private int mOrientation = ORIENTATION_UNKNOWN; private SensorManager mSensorManager; private boolean mEnabled = false; private int mRate; private Sensor mSensor; private SensorEventListener mSensorEventListener; - - /** - * Returned from onOrientationChanged when the device orientation cannot be determined - * (typically when the device is in a close to flat position). - * - * @see #onOrientationChanged - */ - public static final int ORIENTATION_UNKNOWN = -1; - /* - * Returned when the device is almost lying flat on a surface - */ - public static final int ORIENTATION_FLAT = -2; + private int mSensorRotation = -1; /** * Creates a new WindowOrientationListener. @@ -116,24 +104,47 @@ public abstract class WindowOrientationListener { private static final int _DATA_X = 0; private static final int _DATA_Y = 1; private static final int _DATA_Z = 2; + // Angle around x-axis thats considered almost perfect vertical to hold + // the device + private static final int PIVOT = 30; + // Angle around x-asis that's considered almost too vertical. Beyond + // this angle will not result in any orientation changes. f phone faces uses, + // the device is leaning backward. + private static final int PIVOT_UPPER = 65; + // Angle about x-axis that's considered negative vertical. Beyond this + // angle will not result in any orientation changes. If phone faces uses, + // the device is leaning forward. + private static final int PIVOT_LOWER = 0; + // Upper threshold limit for switching from portrait to landscape + private static final int PL_UPPER = 280; + // Lower threshold limit for switching from landscape to portrait + private static final int LP_LOWER = 320; + // Lower threshold limt for switching from portrait to landscape + private static final int PL_LOWER = 240; + // Upper threshold limit for switching from landscape to portrait + private static final int LP_UPPER = 360; + + // Internal value used for calculating linear variant + private static final float PL_LINEAR_FACTOR = + ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER)); + // Internal value used for calculating linear variant + private static final float LP_LINEAR_FACTOR = + ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER)); public void onSensorChanged(SensorEvent event) { float[] values = event.values; - int orientation = ORIENTATION_UNKNOWN; float X = values[_DATA_X]; float Y = values[_DATA_Y]; float Z = values[_DATA_Z]; float OneEightyOverPi = 57.29577957855f; float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z); float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi); - // The device is considered flat if the angle is more than 75 - // if the angle is less than 40, its considered too flat to switch - // orientation. if the angle is between 40 - 75, the orientation is unknown - if (zyangle < 40) { + int rotation = mSensorRotation; + if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) { // Check orientation only if the phone is flat enough // Don't trust the angle if the magnitude is small compared to the y value float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi; - orientation = 90 - (int)Math.round(angle); + int orientation = 90 - (int)Math.round(angle); // normalize to 0 - 359 range while (orientation >= 360) { orientation -= 360; @@ -141,13 +152,24 @@ public abstract class WindowOrientationListener { while (orientation < 0) { orientation += 360; } - } else if (zyangle >= 75){ - orientation = ORIENTATION_FLAT; + + float delta = (float)Math.abs(zyangle - PIVOT); + if (((orientation >= 0) && (orientation <= LP_UPPER)) || + (orientation >= PL_LOWER)) { + float threshold; + if (mSensorRotation == Surface.ROTATION_90) { + threshold = LP_LOWER + (LP_LINEAR_FACTOR * delta) ; + } else { + threshold = PL_UPPER - (PL_LINEAR_FACTOR * delta); + } + rotation = (orientation >= PL_LOWER && + orientation <= threshold) ? Surface.ROTATION_90 : Surface.ROTATION_0; + } + } - - if (orientation != mOrientation) { - mOrientation = orientation; - onOrientationChanged(orientation); + if (rotation != mSensorRotation) { + mSensorRotation = rotation; + onOrientationChanged(mSensorRotation); } } @@ -164,17 +186,11 @@ public abstract class WindowOrientationListener { } /** - * Called when the orientation of the device has changed. - * orientation parameter is in degrees, ranging from 0 to 359. - * orientation is 0 degrees when the device is oriented in its natural position, - * 90 degrees when its left side is at the top, 180 degrees when it is upside down, - * and 270 degrees when its right side is to the top. - * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat - * and the orientation cannot be determined. - * - * @param orientation The new orientation of the device. + * Called when the rotation view of the device has changed. + * Can be either Surface.ROTATION_90 or Surface.ROTATION_0. + * @param rotation The new orientation of the device. * * @see #ORIENTATION_UNKNOWN */ - abstract public void onOrientationChanged(int orientation); + abstract public void onOrientationChanged(int rotation); } diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index d12940d..dcf68cd 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -52,6 +52,7 @@ public final class CacheManager { private static final String NO_STORE = "no-store"; private static final String NO_CACHE = "no-cache"; + private static final String PRIVATE = "private"; private static final String MAX_AGE = "max-age"; private static long CACHE_THRESHOLD = 6 * 1024 * 1024; @@ -612,7 +613,7 @@ public final class CacheManager { // must be re-validated on every load. It does not mean that // the content can not be cached. set to expire 0 means it // can only be used in CACHE_MODE_CACHE_ONLY case - if (NO_CACHE.equals(controls[i])) { + if (NO_CACHE.equals(controls[i]) || PRIVATE.equals(controls[i])) { ret.expires = 0; } else if (controls[i].startsWith(MAX_AGE)) { int separator = controls[i].indexOf('='); diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 84aeb83..0f9f29c 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.provider.Browser; import android.util.Config; import android.util.Log; import android.view.KeyEvent; @@ -175,6 +176,11 @@ class CallbackProxy extends Handler { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(overrideUrl)); intent.addCategory(Intent.CATEGORY_BROWSABLE); + // If another application is running a WebView and launches the + // Browser through this Intent, we want to reuse the same window if + // possible. + intent.putExtra(Browser.EXTRA_APPLICATION_ID, + mContext.getPackageName()); try { mContext.startActivity(intent); override = true; diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 07c1a5d..d90a2fd 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -743,11 +743,16 @@ public final class CookieManager { * Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret * it as one cookie instead of two cookies. */ + int semicolonIndex = cookieString.indexOf(SEMICOLON, index); int equalIndex = cookieString.indexOf(EQUAL, index); if (equalIndex == -1) { // bad format, force return break; } + if (semicolonIndex > -1 && semicolonIndex < equalIndex) { + // empty cookie, like "; path=/", return + break; + } cookie = new Cookie(host, path); cookie.name = cookieString.substring(index, equalIndex); if (cookieString.charAt(equalIndex + 1) == QUOTATION) { @@ -757,7 +762,7 @@ public final class CookieManager { break; } } - int semicolonIndex = cookieString.indexOf(SEMICOLON, index); + semicolonIndex = cookieString.indexOf(SEMICOLON, index); if (semicolonIndex == -1) { semicolonIndex = length; } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index f61ce40..753267f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -180,9 +180,6 @@ public class WebView extends AbsoluteLayout */ VelocityTracker mVelocityTracker; - private static boolean mShowZoomTutorial = true; - private static final int ZOOM_TUTORIAL_DURATION = 3000; - /** * Touch mode */ @@ -223,16 +220,12 @@ public class WebView extends AbsoluteLayout */ // pre-computed square of ViewConfiguration.getScaledTouchSlop() private int mTouchSlopSquare; - // pre-computed square of the density adjusted double tap slop - private int mDoubleTapSlopSquare; // pre-computed density adjusted navigation slop private int mNavSlop; // This should be ViewConfiguration.getTapTimeout() // But system time out is 100ms, which is too short for the browser. // In the browser, if it switches out of tap too soon, jump tap won't work. private static final int TAP_TIMEOUT = 200; - // The duration in milliseconds we will wait to see if it is a double tap. - private static final int DOUBLE_TAP_TIMEOUT = 250; // This should be ViewConfiguration.getLongPressTimeout() // But system time out is 500ms, which is too short for the browser. // With a short timeout, it's difficult to treat trigger a short press. @@ -258,11 +251,6 @@ public class WebView extends AbsoluteLayout private int mContentWidth; // cache of value from WebViewCore private int mContentHeight; // cache of value from WebViewCore - static int MAX_FLOAT_CONTENT_WIDTH = 480; - // the calculated minimum content width for calculating the minimum scale. - // If it is 0, it means don't use it. - private int mMinContentWidth; - // Need to have the separate control for horizontal and vertical scrollbar // style than the View's single scrollbar style private boolean mOverlayHorizontalScrollbar = true; @@ -288,11 +276,9 @@ public class WebView extends AbsoluteLayout private static final int NEVER_REMEMBER_PASSWORD = 2; private static final int SWITCH_TO_SHORTPRESS = 3; private static final int SWITCH_TO_LONGPRESS = 4; - private static final int RELEASE_SINGLE_TAP = 5; private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; private static final int SWITCH_TO_ENTER = 7; private static final int RESUME_WEBCORE_UPDATE = 8; - private static final int DISMISS_ZOOM_TUTORIAL = 9; //! arg1=x, arg2=y static final int SCROLL_TO_MSG_ID = 10; @@ -321,7 +307,7 @@ public class WebView extends AbsoluteLayout "NEVER_REMEMBER_PASSWORD", // = 2; "SWITCH_TO_SHORTPRESS", // = 3; "SWITCH_TO_LONGPRESS", // = 4; - "RELEASE_SINGLE_TAP", // = 5; + "5", "UPDATE_TEXT_ENTRY_ADAPTER", // = 6; "SWITCH_TO_ENTER", // = 7; "RESUME_WEBCORE_UPDATE", // = 8; @@ -346,13 +332,14 @@ public class WebView extends AbsoluteLayout }; // width which view is considered to be fully zoomed out - static final int ZOOM_OUT_WIDTH = 1024; + static final int ZOOM_OUT_WIDTH = 1008; - private static final float DEFAULT_MAX_ZOOM_SCALE = 2; - private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3; + private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f; + private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f; // scale limit, which can be set through viewport meta tag in the web page private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + private boolean mMinZoomScaleFixed = false; // initial scale in percent. 0 means using default. private int mInitialScale = 0; @@ -562,8 +549,8 @@ public class WebView extends AbsoluteLayout private void initZoomController(Context context) { // Create the buttons controller - mZoomButtonsController = new ZoomButtonsController(context, this); - mZoomButtonsController.setCallback(mZoomListener); + mZoomButtonsController = new ZoomButtonsController(this); + mZoomButtonsController.setOnZoomListener(mZoomListener); // Create the accessory buttons LayoutInflater inflater = @@ -611,12 +598,6 @@ public class WebView extends AbsoluteLayout final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mTouchSlopSquare = slop * slop; mMinLockSnapReverseDistance = slop; - // use twice the line height, 32 based on our current default font, for - // the double tap slop as the ViewConfiguration's double tap slop, 100, - // is too big for the Browser - final int doubleTapslop = (int) (32 * getContext().getResources() - .getDisplayMetrics().density); - mDoubleTapSlopSquare = doubleTapslop * doubleTapslop; // use one line height, 16 based on our current default font, for how // far we allow a touch be away from the edge of a link mNavSlop = (int) (16 * getContext().getResources() @@ -1646,8 +1627,7 @@ public class WebView extends AbsoluteLayout * @return true if new values were sent */ private boolean sendViewSizeZoom() { - int viewWidth = getViewWidth(); - int newWidth = Math.round(viewWidth * mInvActualScale); + int newWidth = Math.round(getViewWidth() * mInvActualScale); int newHeight = Math.round(getViewHeight() * mInvActualScale); /* * Because the native side may have already done a layout before the @@ -1663,7 +1643,7 @@ public class WebView extends AbsoluteLayout // Avoid sending another message if the dimensions have not changed. if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, - newWidth, newHeight, new Integer(viewWidth)); + newWidth, newHeight, new Float(mActualScale)); mLastWidthSent = newWidth; mLastHeightSent = newHeight; return true; @@ -3341,19 +3321,10 @@ public class WebView extends AbsoluteLayout mZoomCenterX = getViewWidth() * .5f; mZoomCenterY = getViewHeight() * .5f; - // update mMinZoomScale - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { - boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f; - mMinZoomScale = (float) getViewWidth() / mContentWidth; - if (atMin) { - // if the WebView was at the minimum zoom scale, keep it. e,g., - // the WebView was at the minimum zoom scale at the portrait - // mode, rotate it to the landscape modifying the scale to the - // new minimum zoom scale, when rotating back, we would like to - // keep the minimum zoom scale instead of keeping the same scale - // as normally we do. - mActualScale = mMinZoomScale; - } + // update mMinZoomScale if the minimum zoom scale is not fixed + if (!mMinZoomScaleFixed) { + mMinZoomScale = (float) getViewWidth() + / Math.max(ZOOM_OUT_WIDTH, mContentWidth); } // we always force, in case our height changed, in which case we still @@ -3411,15 +3382,6 @@ public class WebView extends AbsoluteLayout return false; } - if (mShowZoomTutorial && getSettings().supportZoom() - && (mMaxZoomScale != mMinZoomScale)) { - ZoomButtonsController.showZoomTutorialOnce(mContext); - mShowZoomTutorial = false; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(DISMISS_ZOOM_TUTORIAL), - ZOOM_TUTORIAL_DURATION); - } - if (LOGV_ENABLED) { Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" + mTouchMode); @@ -3482,29 +3444,6 @@ public class WebView extends AbsoluteLayout nativeMoveSelection(viewToContent(mSelectX) , viewToContent(mSelectY), false); mTouchSelection = mExtendSelection = true; - } else if (!ZoomButtonsController.useOldZoom(mContext) - && mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { - // Found doubletap, invoke the zoom controller - int contentX = viewToContent((int) mLastTouchX - + mScrollX); - int contentY = viewToContent((int) mLastTouchY - + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); - if (mLogEvent) { - EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, - (eventTime - mLastTouchUpTime), eventTime); - } - return mZoomButtonsController.handleDoubleTapEvent(ev); - } else { - // commit the short press action - doShortPress(); - // continue, mTouchMode should be still TOUCH_INIT_MODE - } } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; @@ -3588,6 +3527,12 @@ public class WebView extends AbsoluteLayout mWebViewCore .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); } + if (getSettings().supportZoom() + && !mZoomButtonsController.isVisible() + && (canZoomScrollOut() || + mMinZoomScale < mMaxZoomScale)) { + mZoomButtonsController.setVisible(true); + } } // do pan @@ -3660,16 +3605,12 @@ public class WebView extends AbsoluteLayout mLastTouchUpTime = eventTime; switch (mTouchMode) { case TOUCH_INIT_MODE: // tap + case TOUCH_SHORTPRESS_START_MODE: + case TOUCH_SHORTPRESS_MODE: mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - if (getSettings().supportZoom()) { - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), - DOUBLE_TAP_TIMEOUT); - } else { - // do short press now - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + mTouchMode = TOUCH_DONE_MODE; + doShortPress(); break; case TOUCH_SELECT_MODE: commitCopy(); @@ -3697,28 +3638,6 @@ public class WebView extends AbsoluteLayout invalidate(); } break; - case TOUCH_SHORTPRESS_START_MODE: - case TOUCH_SHORTPRESS_MODE: { - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - if (eventTime - mLastTouchTime < TAP_TIMEOUT - && getSettings().supportZoom()) { - // Note: window manager will not release ACTION_UP - // until all the previous action events are - // returned. If GC happens, it can cause - // SWITCH_TO_SHORTPRESS message fired before - // ACTION_UP sent even time stamp of ACTION_UP is - // less than the tap time out. We need to treat this - // as tap instead of short press. - mTouchMode = TOUCH_INIT_MODE; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), - DOUBLE_TAP_TIMEOUT); - } else { - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } - break; - } case TOUCH_DRAG_MODE: // if the user waits a while w/o moving before the // up, we don't want to do a fling @@ -3759,7 +3678,6 @@ public class WebView extends AbsoluteLayout } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); mTouchMode = TOUCH_DONE_MODE; int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); @@ -4132,31 +4050,7 @@ public class WebView extends AbsoluteLayout } } - /** - * An InvisibleView is an invisible, zero-sized View for backwards - * compatibility - */ - private final class InvisibleView extends View { - - private InvisibleView(Context context) { - super(context); - setVisibility(GONE); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(0, 0); - } - - @Override - public void draw(Canvas canvas) { - } - - @Override - protected void dispatchDraw(Canvas canvas) { - } - } - + // TODO: deprecate /** * Returns a view containing zoom controls i.e. +/- buttons. The caller is * in charge of installing this view to the view hierarchy. This view will @@ -4167,7 +4061,19 @@ public class WebView extends AbsoluteLayout * an invisible dummy view for backwards compatibility. */ public View getZoomControls() { - return new InvisibleView(mContext); + return mZoomButtonsController.getDummyZoomControls(); + } + + /** + * Gets the {@link ZoomButtonsController} which can be used to add + * additional buttons to the zoom controls window. + * + * @return The instance of {@link ZoomButtonsController} used by this class, + * or null if it is unavailable. + * @hide pending API council + */ + public ZoomButtonsController getZoomButtonsController() { + return mZoomButtonsController; } /** @@ -4447,11 +4353,6 @@ public class WebView extends AbsoluteLayout updateTextEntry(); break; } - case RELEASE_SINGLE_TAP: { - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - break; - } case SWITCH_TO_ENTER: if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); mTouchMode = TOUCH_DONE_MODE; @@ -4498,10 +4399,9 @@ public class WebView extends AbsoluteLayout 0, 0); } } - mMinContentWidth = msg.arg1; - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { + if (!mMinZoomScaleFixed) { mMinZoomScale = (float) getViewWidth() - / draw.mWidthHeight.x; + / Math.max(ZOOM_OUT_WIDTH, draw.mWidthHeight.x); } // We update the layout (i.e. request a layout from the // view system) if the last view size that we sent to @@ -4561,8 +4461,10 @@ public class WebView extends AbsoluteLayout int minScale = (Integer) scaleLimit.get("minScale"); if (minScale == 0) { mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + mMinZoomScaleFixed = false; } else { mMinZoomScale = (float) (minScale / 100.0); + mMinZoomScaleFixed = true; } int maxScale = (Integer) scaleLimit.get("maxScale"); if (maxScale == 0) { @@ -4690,10 +4592,6 @@ public class WebView extends AbsoluteLayout } break; - case DISMISS_ZOOM_TUTORIAL: - mZoomButtonsController.finishZoomTutorial(); - break; - default: super.handleMessage(msg); break; diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index e10ffa1..3e4daf7 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -725,7 +725,7 @@ final class WebViewCore { case VIEW_SIZE_CHANGED: viewSizeChanged(msg.arg1, msg.arg2, - ((Integer) msg.obj).intValue()); + ((Float) msg.obj).floatValue()); break; case SET_SCROLL_OFFSET: @@ -1181,23 +1181,16 @@ final class WebViewCore { private int mCurrentViewWidth = 0; private int mCurrentViewHeight = 0; - // Define a minimum screen width so that we won't wrap the paragraph to one - // word per line during zoom-in. - private static final int MIN_SCREEN_WIDTH = 160; - // notify webkit that our virtual view size changed size (after inv-zoom) - private void viewSizeChanged(int w, int h, int viewWidth) { + private void viewSizeChanged(int w, int h, float scale) { if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged"); if (w == 0) { Log.w(LOGTAG, "skip viewSizeChanged as w is 0"); return; } - // negative scale indicate that WebCore should reuse the current scale - float scale = (float) viewWidth / w; if (mSettings.getUseWideViewPort() && (w < mViewportWidth || mViewportWidth == -1)) { int width = mViewportWidth; - int screenWidth = Math.max(w, MIN_SCREEN_WIDTH); if (mViewportWidth == -1) { if (mSettings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NORMAL) { @@ -1215,19 +1208,11 @@ final class WebViewCore { * In the worse case, the native width will be adjusted when * next zoom or screen orientation change happens. */ - int minContentWidth = nativeGetContentMinPrefWidth(); - if (minContentWidth > WebView.MAX_FLOAT_CONTENT_WIDTH) { - // keep the same width and screen width so that there is - // no reflow when zoom-out - width = minContentWidth; - screenWidth = Math.min(screenWidth, Math.abs(viewWidth)); - } else { - width = Math.max(w, minContentWidth); - } + width = Math.max(w, nativeGetContentMinPrefWidth()); } } - nativeSetSize(width, Math.round((float) width * h / w), - screenWidth, scale, w, h); + nativeSetSize(width, Math.round((float) width * h / w), w, scale, + w, h); } else { nativeSetSize(w, h, w, scale, w, h); } @@ -1289,10 +1274,7 @@ final class WebViewCore { draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight); if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); Message.obtain(mWebView.mPrivateHandler, - WebView.NEW_PICTURE_MSG_ID, - mViewportMinimumScale == 0 ? nativeGetContentMinPrefWidth() - : 0, - 0, draw).sendToTarget(); + WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget(); if (mWebkitScrollX != 0 || mWebkitScrollY != 0) { // as we have the new picture, try to sync the scroll position Message.obtain(mWebView.mPrivateHandler, @@ -1533,11 +1515,11 @@ final class WebViewCore { // white space in the GMail which uses WebView for message view. if (mWebView != null && mWebView.mHeightCanMeasure) { mWebView.mLastHeightSent = 0; - // Send a negative screen width to indicate that WebCore should - // reuse the current scale + // Send a negative scale to indicate that WebCore should reuse the + // current scale mEventHub.sendMessage(Message.obtain(null, EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent, - mWebView.mLastHeightSent, -mWebView.mLastWidthSent)); + mWebView.mLastHeightSent, -1.0f)); } mBrowserFrame.didFirstLayout(); diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java index ee8ca49..a7a144b 100644 --- a/core/java/android/webkit/gears/DesktopAndroid.java +++ b/core/java/android/webkit/gears/DesktopAndroid.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.provider.Browser; import android.util.Log; import android.webkit.WebView; @@ -78,7 +79,10 @@ public class DesktopAndroid { Intent viewWebPage = new Intent(Intent.ACTION_VIEW); viewWebPage.setData(Uri.parse(url)); - viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE); + long urlHash = url.hashCode(); + long uniqueId = (urlHash << 32) | viewWebPage.hashCode(); + viewWebPage.putExtra(Browser.EXTRA_APPLICATION_ID, + Long.toString(uniqueId)); Intent intent = new Intent(ACTION_INSTALL_SHORTCUT); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 7fc96fc..bd4bba8 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -982,6 +982,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mSelectorRect.setEmpty(); invalidate(); } + + /** + * The list is empty and we need to change the layout, so *really* clear everything out. + * @hide - for AutoCompleteTextView & SearchDialog only + */ + /* package */ void resetListAndClearViews() { + rememberSyncState(); + removeAllViewsInLayout(); + mRecycler.clear(); + mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); + requestLayout(); + } @Override protected int computeVerticalScrollExtent() { @@ -1422,7 +1434,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final View v = getChildAt(mSelectedPosition - mFirstPosition); - if (v != null) v.setPressed(true); + if (v != null) { + if (v.hasFocusable()) return; + v.setPressed(true); + } setPressed(true); final boolean longClickable = isLongClickable(); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 7b9670b..e613541 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -630,6 +630,16 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } /** + * We're changing the adapter and its views so really, really clear everything out + * @hide - for SearchDialog only + */ + public void resetListAndClearViews() { + if (mDropDownList != null) { + mDropDownList.resetListAndClearViews(); + } + } + + /** * <p>Starts filtering the content of the drop down list. The filtering * pattern is the content of the edit box. Subclasses should override this * method to filter with a different pattern, for instance a substring of diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index f646ab5..441414a 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -763,7 +763,7 @@ public class ProgressBar extends View { @Override public void invalidateDrawable(Drawable dr) { if (!mInDrawing) { - if (dr == mProgressDrawable || dr == mIndeterminateDrawable) { + if (verifyDrawable(dr)) { final Rect dirty = dr.getBounds(); final int scrollX = mScrollX + mPaddingLeft; final int scrollY = mScrollY + mPaddingTop; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 426d711..7b62b50 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6208,7 +6208,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return superResult; } - if (mMovement != null && mText instanceof Spannable && mLayout != null) { + if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { if (action == MotionEvent.ACTION_DOWN) { mScrolled = false; @@ -6219,7 +6219,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int oldSelStart = Selection.getSelectionStart(mText); int oldSelEnd = Selection.getSelectionEnd(mText); - handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); + if (mMovement != null) { + handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); + } if (mText instanceof Editable && onCheckIsTextEditor()) { if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) { diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index 6729fd1..4daa419 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -24,12 +24,15 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.provider.Settings; +import android.util.Log; +import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -37,31 +40,51 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewRoot; import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.WindowManager.LayoutParams; -// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration) - +/* + * Implementation notes: + * - The zoom controls are displayed in their own window. + * (Easier for the client and better performance) + * - This window is not touchable, and by default is not focusable. + * - To make the buttons clickable, it attaches a OnTouchListener to the owner + * view and does the hit detection locally. + * - When it is focusable, it forwards uninteresting events to the owner view's + * view hierarchy. + */ /** - * TODO: Docs - * + * The {@link ZoomButtonsController} handles showing and hiding the zoom + * controls relative to an owner view. It also gives the client access to the + * zoom controls container, allowing for additional accessory buttons to be + * shown in the zoom controls window. + * <p> + * Typical usage involves the client using the {@link GestureDetector} to + * forward events from + * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)} to + * {@link #handleDoubleTapEvent(MotionEvent)}. Also, whenever the owner cannot + * be zoomed further, the client should update + * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}. + * <p> * If you are using this with a custom View, please call * {@link #setVisible(boolean) setVisible(false)} from the * {@link View#onDetachedFromWindow}. - * + * * @hide */ -public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyListener { +public class ZoomButtonsController implements View.OnTouchListener { private static final String TAG = "ZoomButtonsController"; private static final int ZOOM_CONTROLS_TIMEOUT = (int) ViewConfiguration.getZoomControlsTimeout(); - // TODO: scaled to density private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20; + private int mTouchPaddingScaledSq; private Context mContext; private WindowManager mWindowManager; @@ -72,17 +95,17 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private View mOwnerView; /** - * The bounds of the owner view in global coordinates. This is recalculated + * The location of the owner view on the screen. This is recalculated * each time the zoom controller is shown. */ - private Rect mOwnerViewBounds = new Rect(); + private int[] mOwnerViewRawLocation = new int[2]; /** * The container that is added as a window. */ private FrameLayout mContainer; private LayoutParams mContainerLayoutParams; - private int[] mContainerLocation = new int[2]; + private int[] mContainerRawLocation = new int[2]; private ZoomControls mControls; @@ -94,7 +117,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi /** * The {@link #mTouchTargetView}'s location in window, set on touch down. */ - private int[] mTouchTargetLocationInWindow = new int[2]; + private int[] mTouchTargetWindowLocation = new int[2]; /** * If the zoom controller is dismissed but the user is still in a touch * interaction, we set this to true. This will ignore all touch events until @@ -102,15 +125,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi */ private boolean mReleaseTouchListenerOnUp; + /** + * Whether we are currently in the double-tap gesture, with the second tap + * still being performed (i.e., we're waiting for the second tap's touch up). + */ private boolean mIsSecondTapDown; + /** Whether the container has been added to the window manager. */ private boolean mIsVisible; private Rect mTempRect = new Rect(); - + private int[] mTempIntArray = new int[2]; + private OnZoomListener mCallback; /** + * In 1.0, the ZoomControls were to be added to the UI by the client of + * WebView, MapView, etc. We didn't want apps to break, so we return a dummy + * view in place now. + */ + private InvisibleView mDummyZoomControls; + + /** * When showing the zoom, we add the view as a new window. However, there is * logic that needs to know the size of the zoom which is determined after * it's laid out. Therefore, we must post this logic onto the UI thread so @@ -121,6 +157,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private IntentFilter mConfigurationChangedFilter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); + /** + * Needed to reposition the zoom controls after configuration changes. + */ private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -161,41 +200,68 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi case MSG_POST_SET_VISIBLE: if (mOwnerView.getWindowToken() == null) { - // Doh, it is still null, throw an exception - throw new IllegalArgumentException( + // Doh, it is still null, just ignore the set visible call + Log.e(TAG, "Cannot make the zoom controller visible if the owner view is " + "not attached to a window."); + } else { + setVisible(true); } - setVisible(true); break; } } }; - public ZoomButtonsController(Context context, View ownerView) { - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + /** + * Constructor for the {@link ZoomButtonsController}. + * + * @param ownerView The view that is being zoomed by the zoom controls. The + * zoom controls will be displayed aligned with this view. + */ + public ZoomButtonsController(View ownerView) { + mContext = ownerView.getContext(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mOwnerView = ownerView; + mTouchPaddingScaledSq = (int) + (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density); + mTouchPaddingScaledSq *= mTouchPaddingScaledSq; + mContainer = createContainer(); } + /** + * Whether to enable the zoom in control. + * + * @param enabled Whether to enable the zoom in control. + */ public void setZoomInEnabled(boolean enabled) { mControls.setIsZoomInEnabled(enabled); } + /** + * Whether to enable the zoom out control. + * + * @param enabled Whether to enable the zoom out control. + */ public void setZoomOutEnabled(boolean enabled) { mControls.setIsZoomOutEnabled(enabled); } + /** + * Sets the delay between zoom callbacks as the user holds a zoom button. + * + * @param speed The delay in milliseconds between zoom callbacks. + */ public void setZoomSpeed(long speed) { mControls.setZoomSpeed(speed); } private FrameLayout createContainer() { LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.BOTTOM | Gravity.CENTER; + // Controls are positioned BOTTOM | CENTER with respect to the owner view. + lp.gravity = Gravity.TOP | Gravity.LEFT; lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS; @@ -206,10 +272,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; mContainerLayoutParams = lp; - FrameLayout container = new FrameLayout(mContext); + FrameLayout container = new Container(mContext); container.setLayoutParams(lp); container.setMeasureAllChildren(true); - container.setOnKeyListener(this); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -232,30 +297,51 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi return container; } - public void setCallback(OnZoomListener callback) { - mCallback = callback; + /** + * Sets the {@link OnZoomListener} listener that receives callbacks to zoom. + * + * @param listener The listener that will be told to zoom. + */ + public void setOnZoomListener(OnZoomListener listener) { + mCallback = listener; } + /** + * Sets whether the zoom controls should be focusable. If the controls are + * focusable, then trackball and arrow key interactions are possible. + * Otherwise, only touch interactions are possible. + * + * @param focusable Whether the zoom controls should be focusable. + */ public void setFocusable(boolean focusable) { + int oldFlags = mContainerLayoutParams.flags; if (focusable) { mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE; } else { mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; } - if (mIsVisible) { + if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) { mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); } } + /** + * Whether the zoom controls are visible to the user. + * + * @return Whether the zoom controls are visible to the user. + */ public boolean isVisible() { return mIsVisible; } + /** + * Sets whether the zoom controls should be visible to the user. + * + * @param visible Whether the zoom controls should be visible to the user. + */ public void setVisible(boolean visible) { - if (!useThisZoom(mContext)) return; - if (visible) { if (mOwnerView.getWindowToken() == null) { /* @@ -329,12 +415,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } /** - * TODO: docs - * - * Notes: - * - Please ensure you set your View to INVISIBLE not GONE when hiding it. - * - * @return TODO + * Gets the container that is the parent of the zoom controls. + * <p> + * The client can add other views to this container to link them with the + * zoom controls. + * + * @return The container of the zoom controls. It will be a layout that + * respects the gravity of a child's layout parameters. */ public ViewGroup getContainer() { return mContainer; @@ -347,14 +434,12 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi /** * Should be called by the client for each event belonging to the second tap - * (the down, move, up, and cancel events). + * (the down, move, up, and/or cancel events). * * @param event The event belonging to the second tap. * @return Whether the event was consumed. */ public boolean handleDoubleTapEvent(MotionEvent event) { - if (!useThisZoom(mContext)) return false; - int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { @@ -382,9 +467,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } private void refreshPositioningVariables() { + // Position the zoom controls on the bottom of the owner view. + int ownerHeight = mOwnerView.getHeight(); + int ownerWidth = mOwnerView.getWidth(); + // The gap between the top of the owner and the top of the container + int containerOwnerYOffset = ownerHeight - mContainer.getHeight(); + // Calculate the owner view's bounds - mOwnerView.getGlobalVisibleRect(mOwnerViewBounds); - mContainer.getLocationOnScreen(mContainerLocation); + mOwnerView.getLocationOnScreen(mOwnerViewRawLocation); + mContainerRawLocation[0] = mOwnerViewRawLocation[0]; + mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset; + + int[] ownerViewWindowLoc = mTempIntArray; + mOwnerView.getLocationInWindow(ownerViewWindowLoc); + + // lp.x and lp.y should be relative to the owner's window top-left + mContainerLayoutParams.x = ownerViewWindowLoc[0]; + mContainerLayoutParams.width = ownerWidth; + mContainerLayoutParams.y = ownerViewWindowLoc[1] + containerOwnerYOffset; + if (mIsVisible) { + mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); + } + } /** @@ -396,11 +500,65 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } } - public boolean onKey(View v, int keyCode, KeyEvent event) { - dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); - return false; + /* This will only be called when the container has focus. */ + private boolean onContainerKey(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (isInterestingKey(keyCode)) { + + if (keyCode == KeyEvent.KEYCODE_BACK) { + setVisible(false); + } else { + dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); + } + + // Let the container handle the key + return false; + + } else { + + ViewRoot viewRoot = getOwnerViewRoot(); + if (viewRoot != null) { + viewRoot.dispatchKey(event); + } + + // We gave the key to the owner, don't let the container handle this key + return true; + } } + private boolean isInterestingKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_BACK: + return true; + default: + return false; + } + } + + private ViewRoot getOwnerViewRoot() { + View rootViewOfOwner = mOwnerView.getRootView(); + if (rootViewOfOwner == null) { + return null; + } + + ViewParent parentOfRootView = rootViewOfOwner.getParent(); + if (parentOfRootView instanceof ViewRoot) { + return (ViewRoot) parentOfRootView; + } else { + return null; + } + } + + /** + * @hide The ZoomButtonsController implements the OnTouchListener, but this + * does not need to be shown in its public API. + */ public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); @@ -423,14 +581,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi return true; } - // TODO: optimize this (it ends up removing message and queuing another) dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); View targetView = mTouchTargetView; switch (action) { case MotionEvent.ACTION_DOWN: - targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY()); + targetView = findViewForTouch((int) event.getRawX(), (int) event.getRawY()); setTouchTargetView(targetView); break; @@ -442,14 +599,22 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi if (targetView != null) { // The upperleft corner of the target view in raw coordinates - int targetViewRawX = mContainerLocation[0] + mTouchTargetLocationInWindow[0]; - int targetViewRawY = mContainerLocation[1] + mTouchTargetLocationInWindow[1]; + int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0]; + int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[1]; MotionEvent containerEvent = MotionEvent.obtain(event); // Convert the motion event into the target view's coordinates (from // owner view's coordinates) - containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX, - mOwnerViewBounds.top - targetViewRawY); + containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX, + mOwnerViewRawLocation[1] - targetViewRawY); + /* Disallow negative coordinates (which can occur due to + * ZOOM_CONTROLS_TOUCH_PADDING) */ + if (containerEvent.getX() < 0) { + containerEvent.offsetLocation(-containerEvent.getX(), 0); + } + if (containerEvent.getY() < 0) { + containerEvent.offsetLocation(0, -containerEvent.getY()); + } boolean retValue = targetView.dispatchTouchEvent(containerEvent); containerEvent.recycle(); return retValue || consumeEvent; @@ -462,7 +627,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private void setTouchTargetView(View view) { mTouchTargetView = view; if (view != null) { - view.getLocationInWindow(mTouchTargetLocationInWindow); + view.getLocationInWindow(mTouchTargetWindowLocation); } } @@ -473,11 +638,15 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi * @param rawY The raw Y. * @return The view that should receive the touches, or null if there is not one. */ - private View getViewForTouch(int rawX, int rawY) { + private View findViewForTouch(int rawX, int rawY) { // Reverse order so the child drawn on top gets first dibs. - int containerCoordsX = rawX - mContainerLocation[0]; - int containerCoordsY = rawY - mContainerLocation[1]; + int containerCoordsX = rawX - mContainerRawLocation[0]; + int containerCoordsY = rawY - mContainerRawLocation[1]; Rect frame = mTempRect; + + View closestChild = null; + int closestChildDistanceSq = Integer.MAX_VALUE; + for (int i = mContainer.getChildCount() - 1; i >= 0; i--) { View child = mContainer.getChildAt(i); if (child.getVisibility() != View.VISIBLE) { @@ -485,14 +654,24 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } child.getHitRect(frame); - // Expand the touch region - frame.top -= ZOOM_CONTROLS_TOUCH_PADDING; if (frame.contains(containerCoordsX, containerCoordsY)) { return child; } + + int distanceX = Math.min(Math.abs(frame.left - containerCoordsX), + Math.abs(containerCoordsX - frame.right)); + int distanceY = Math.min(Math.abs(frame.top - containerCoordsY), + Math.abs(containerCoordsY - frame.bottom)); + int distanceSq = distanceX * distanceX + distanceY * distanceY; + + if ((distanceSq < mTouchPaddingScaledSq) && + (distanceSq < closestChildDistanceSq)) { + closestChild = child; + closestChildDistanceSq = distanceSq; + } } - return null; + return closestChild; } private void onPostConfigurationChanged() { @@ -518,6 +697,10 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi * gallery */ public static void showZoomTutorialOnce(Context context) { + + // TODO: remove this code, but to hit the weekend build, just never show + if (true) return; + ContentResolver cr = context.getContentResolver(); if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { return; @@ -583,22 +766,83 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi finishZoomTutorial(mContext, true); } - // Temporary methods for different zoom types - static int getZoomType(Context context) { - return Settings.System.getInt(context.getContentResolver(), "zoom", 2); - } - - public static boolean useOldZoom(Context context) { - return getZoomType(context) == 0; - } - - public static boolean useThisZoom(Context context) { - return getZoomType(context) == 2; + /** @hide Should only be used only be WebView and MapView */ + public View getDummyZoomControls() { + if (mDummyZoomControls == null) { + mDummyZoomControls = new InvisibleView(mContext); + } + return mDummyZoomControls; } - + + /** + * Interface that will be called when the user performs an interaction that + * triggers some action, for example zooming. + */ public interface OnZoomListener { + /** + * Called when the given point should be centered. The point will be in + * owner view coordinates. + * + * @param x The x of the point. + * @param y The y of the point. + */ void onCenter(int x, int y); + + /** + * Called when the zoom controls' visibility changes. + * + * @param visible Whether the zoom controls are visible. + */ void onVisibilityChanged(boolean visible); + + /** + * Called when the owner view needs to be zoomed. + * + * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out. + */ void onZoom(boolean zoomIn); } + + private class Container extends FrameLayout { + public Container(Context context) { + super(context); + } + + /* + * Need to override this to intercept the key events. Otherwise, we + * would attach a key listener to the container but its superclass + * ViewGroup gives it to the focused View instead of calling the key + * listener, and so we wouldn't get the events. + */ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return onContainerKey(event) ? true : super.dispatchKeyEvent(event); + } + } + + /** + * An InvisibleView is an invisible, zero-sized View for backwards + * compatibility + */ + private final class InvisibleView extends View { + + private InvisibleView(Context context) { + super(context); + setVisibility(GONE); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(0, 0); + } + + @Override + public void draw(Canvas canvas) { + } + + @Override + protected void dispatchDraw(Canvas canvas) { + } + } + } diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index e978db8..84d8f0e 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -29,8 +29,12 @@ import com.android.internal.R; /** * The {@code ZoomControls} class displays a simple set of controls used for zooming and - * provides callbacks to register for events. - */ + * provides callbacks to register for events. */ +// TODO: pending API council +// * <p> +// * Instead of using this directly, consider using the {@link ZoomButtonsController} which +// * handles displaying the zoom controls. +// */ @Widget public class ZoomControls extends LinearLayout { @@ -81,9 +85,7 @@ public class ZoomControls extends LinearLayout { } public void show() { - if (ZoomButtonsController.useOldZoom(mContext)) { - fade(View.VISIBLE, 0.0f, 1.0f); - } + fade(View.VISIBLE, 0.0f, 1.0f); } public void hide() { diff --git a/core/java/com/android/internal/widget/EditStyledText.java b/core/java/com/android/internal/widget/EditStyledText.java index 48b4780..8a4675a 100644 --- a/core/java/com/android/internal/widget/EditStyledText.java +++ b/core/java/com/android/internal/widget/EditStyledText.java @@ -16,20 +16,26 @@ package com.android.internal.widget; +import android.app.AlertDialog.Builder; import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; import android.text.Editable; +import android.text.Html; import android.text.Spannable; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.EditText; /** - * EditStyledText extends EditText for managing the flow and status - * to edit the styled text. This manages the states and flows of editing, - * supports inserting image, import/export HTML. + * EditStyledText extends EditText for managing the flow and status to edit + * the styled text. This manages the states and flows of editing, supports + * inserting image, import/export HTML. */ public class EditStyledText extends EditText { @@ -61,7 +67,7 @@ public class EditStyledText extends EditText { public static final int STATE_SELECT_ON = 1; /** The state that selection is done, but not fixed. */ public static final int STATE_SELECTED = 2; - /** The state that selection is done and not fixed.*/ + /** The state that selection is done and not fixed. */ public static final int STATE_SELECT_FIX = 3; /** @@ -73,26 +79,28 @@ public class EditStyledText extends EditText { public static final int HINT_MSG_SELECT_END = 3; public static final int HINT_MSG_PUSH_COMPETE = 4; - /** - * EditStyledTextInterface provides functions for notifying messages - * to calling class. + * EditStyledTextInterface provides functions for notifying messages to + * calling class. */ - public interface EditStyledTextInterface { - public void notifyHintMsg(int msg_id); + public interface EditStyledTextNotifier { + public void notifyHintMsg(int msgId); } - private EditStyledTextInterface mESTInterface; + + private EditStyledTextNotifier mESTInterface; /** - * EditStyledTextEditorManager manages the flow and status of - * each function for editing styled text. + * EditStyledTextEditorManager manages the flow and status of each + * function for editing styled text. */ - private EditStyledTextEditorManager mManager; + private EditorManager mManager; + private StyledTextConverter mConverter; + private StyledTextToast mToast; /** - * EditStyledText extends EditText for managing flow of each editing - * action. - */ + * EditStyledText extends EditText for managing flow of each editing + * action. + */ public EditStyledText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); @@ -109,11 +117,54 @@ public class EditStyledText extends EditText { } /** - * Set View objects used in EditStyledText. - * @param helptext The view shows help messages. + * Set Notifier. + */ + public void setNotifier(EditStyledTextNotifier estInterface) { + mESTInterface = estInterface; + } + + /** + * Set Builder for AlertDialog. + * + * @param builder + * Builder for opening Alert Dialog. + */ + public void setBuilder(Builder builder) { + mToast.setBuilder(builder); + } + + /** + * Set Parameters for ColorAlertDialog. + * + * @param colortitle + * Title for Alert Dialog. + * @param colornames + * List of name of selecting color. + * @param colorints + * List of int of color. + */ + public void setColorAlertParams(CharSequence colortitle, + CharSequence[] colornames, CharSequence[] colorints) { + mToast.setColorAlertParams(colortitle, colornames, colorints); + } + + /** + * Set Parameters for SizeAlertDialog. + * + * @param sizetitle + * Title for Alert Dialog. + * @param sizenames + * List of name of selecting size. + * @param sizedisplayints + * List of int of size displayed in TextView. + * @param sizesendints + * List of int of size exported to HTML. */ - public void setParts(EditStyledTextInterface est_interface) { - mESTInterface = est_interface; + public void setSizeAlertParams(CharSequence sizetitle, + CharSequence[] sizenames, CharSequence[] sizedisplayints, + CharSequence[] sizesendints) { + mToast.setSizeAlertParams(sizetitle, sizenames, sizedisplayints, + sizesendints); } @Override @@ -129,8 +180,8 @@ public class EditStyledText extends EditText { } /** - * Start editing. This function have to be called before other - * editing actions. + * Start editing. This function have to be called before other editing + * actions. */ public void onStartEdit() { mManager.onStartEdit(); @@ -186,6 +237,26 @@ public class EditStyledText extends EditText { } /** + * InsertImage to TextView by using URI + * + * @param uri + * URI of the iamge inserted to TextView. + */ + public void onInsertImage(Uri uri) { + mManager.onInsertImage(uri); + } + + /** + * InsertImage to TextView by using resource ID + * + * @param resId + * Resource ID of the iamge inserted to TextView. + */ + public void onInsertImage(int resId) { + mManager.onInsertImage(resId); + } + + /** * Fix Selected Item. */ public void fixSelectedItem() { @@ -194,7 +265,9 @@ public class EditStyledText extends EditText { /** * Set Size of the Item. - * @param size The size of the Item. + * + * @param size + * The size of the Item. */ public void setItemSize(int size) { mManager.setItemSize(size); @@ -202,14 +275,25 @@ public class EditStyledText extends EditText { /** * Set Color of the Item. - * @param color The color of the Item. + * + * @param color + * The color of the Item. */ public void setItemColor(int color) { mManager.setItemColor(color); } + public void onShowColorAlert() { + mToast.onShowColorAlertDialog(); + } + + public void onShowSizeAlert() { + mToast.onShowSizeAlertDialog(); + } + /** * Check editing is started. + * * @return Whether editing is started or not. */ public boolean isEditting() { @@ -218,6 +302,7 @@ public class EditStyledText extends EditText { /** * Get the mode of the action. + * * @return The mode of the action. */ public int getEditMode() { @@ -226,12 +311,17 @@ public class EditStyledText extends EditText { /** * Get the state of the selection. + * * @return The state of the selection. */ public int getSelectState() { return mManager.getSelectState(); } + public String getBody() { + return mConverter.getConvertedBody(); + } + /** * Initialize members. */ @@ -240,23 +330,36 @@ public class EditStyledText extends EditText { Log.d(LOG_TAG, "--- init"); requestFocus(); } - mManager = new EditStyledTextEditorManager(this); + mManager = new EditorManager(this); + mConverter = new StyledTextConverter(this); + mToast = new StyledTextToast(this); } /** * Notify hint messages what action is expected to calling class. - * @param msg + * + * @param msgId + * Id of the hint message. */ - private void setHintMessage(int msg_id) { + private void setHintMessage(int msgId) { if (mESTInterface != null) { - mESTInterface.notifyHintMsg(msg_id); + mESTInterface.notifyHintMsg(msgId); + } + } + + @Override + public Bundle getInputExtras(boolean create) { + Bundle bundle = super.getInputExtras(create); + if (bundle != null) { + bundle.putBoolean("allowEmoji", true); } + return bundle; } /** * Object which manages the flow and status of editing actions. */ - private class EditStyledTextEditorManager { + private class EditorManager { private boolean mEditFlag = false; private int mMode = 0; private int mState = 0; @@ -266,7 +369,7 @@ public class EditStyledText extends EditText { private Editable mTextSelectBuffer; private CharSequence mTextCopyBufer; - EditStyledTextEditorManager(EditStyledText est) { + EditorManager(EditStyledText est) { mEST = est; } @@ -368,6 +471,28 @@ public class EditStyledText extends EditText { handleComplete(); } + public void onInsertImage(Uri uri) { + if (DBG) { + Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath() + + "," + uri.toString()); + } + + mEST.getText().append("a"); + mEST.getText().setSpan(new ImageSpan(mEST.getContext(), uri), + mEST.getText().length() - 1, mEST.getText().length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + public void onInsertImage(int resID) { + if (DBG) { + Log.d(LOG_TAG, "--- onInsertImage by resID"); + } + mEST.getText().append("b"); + mEST.getText().setSpan(new ImageSpan(mEST.getContext(), resID), + mEST.getText().length() - 1, mEST.getText().length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + public boolean isEditting() { return mEditFlag; } @@ -404,6 +529,12 @@ public class EditStyledText extends EditText { case MODE_COPY: handleCopy(); break; + case MODE_COLOR: + handleColor(); + break; + case MODE_SIZE: + handleSize(); + break; default: break; } @@ -455,12 +586,14 @@ public class EditStyledText extends EditText { Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState); } if (!mEditFlag) { + Log.e(LOG_TAG, "--- Editing is not started for handlesize."); return; } if (mMode == MODE_NOTHING || mMode == MODE_SELECT) { mMode = MODE_SIZE; if (mState == STATE_SELECTED) { mState = STATE_SELECT_FIX; + handleSize(); } else { handleSelect(); } @@ -468,22 +601,29 @@ public class EditStyledText extends EditText { handleCancel(); mMode = MODE_SIZE; handleSize(); - } else if (mState == STATE_SELECT_FIX) { - mEST.setHintMessage(HINT_MSG_NULL); + } else { + if (mState == STATE_SELECT_FIX) { + mEST.setHintMessage(HINT_MSG_NULL); + mEST.onShowSizeAlert(); + } else { + Log.d(LOG_TAG, "--- handlesize: do nothing"); + } } } private void handleColor() { if (DBG) { - Log.d(LOG_TAG, "--- handleColor"); + Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState); } if (!mEditFlag) { + Log.e(LOG_TAG, "--- Editing is not started for handlecolor."); return; } if (mMode == MODE_NOTHING || mMode == MODE_SELECT) { mMode = MODE_COLOR; if (mState == STATE_SELECTED) { mState = STATE_SELECT_FIX; + handleColor(); } else { handleSelect(); } @@ -491,35 +631,39 @@ public class EditStyledText extends EditText { handleCancel(); mMode = MODE_COLOR; handleSize(); - } else if (mState == STATE_SELECT_FIX) { - mEST.setHintMessage(HINT_MSG_NULL); + } else { + if (mState == STATE_SELECT_FIX) { + mEST.setHintMessage(HINT_MSG_NULL); + mEST.onShowColorAlert(); + } else { + Log.d(LOG_TAG, "--- handlecolor: do nothing"); + } } } private void handleSelect() { if (DBG) { - Log.d(LOG_TAG, "--- handleSelect" + mEditFlag + "," + mState); + Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState); } if (!mEditFlag) { return; } if (mState == STATE_SELECT_OFF) { if (isTextSelected()) { - Log.e(LOG_TAG, "Selection state is off, but selected"); + Log.e(LOG_TAG, "Selection is off, but selected"); } setSelectStartPos(); mEST.setHintMessage(HINT_MSG_SELECT_END); } else if (mState == STATE_SELECT_ON) { if (isTextSelected()) { - Log.e(LOG_TAG, "Selection state now start, but selected"); + Log.e(LOG_TAG, "Selection now start, but selected"); } setSelectEndPos(); mEST.setHintMessage(HINT_MSG_PUSH_COMPETE); doNextHandle(); } else if (mState == STATE_SELECTED) { if (!isTextSelected()) { - Log.e(LOG_TAG, - "Selection state is done, but not selected"); + Log.e(LOG_TAG, "Selection is done, but not selected"); } setSelectEndPos(); doNextHandle(); @@ -537,6 +681,9 @@ public class EditStyledText extends EditText { } private void doNextHandle() { + if (DBG) { + Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState); + } switch (mMode) { case MODE_COPY: handleCopy(); @@ -556,6 +703,9 @@ public class EditStyledText extends EditText { } private void handleResetEdit() { + if (DBG) { + Log.d(LOG_TAG, "Reset Editor"); + } handleCancel(); mEditFlag = true; mEST.setHintMessage(HINT_MSG_SELECT_START); @@ -564,7 +714,7 @@ public class EditStyledText extends EditText { // Methods of selection private void onSelect() { if (DBG) { - Log.d(LOG_TAG, "--- onSelect"); + Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd); } if (mCurStart >= 0 && mCurStart <= mEST.getText().length() && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) { @@ -650,4 +800,132 @@ public class EditStyledText extends EditText { } } + private class StyledTextConverter { + private EditStyledText mEST; + + public StyledTextConverter(EditStyledText est) { + mEST = est; + } + + public String getConvertedBody() { + String htmlBody = Html.toHtml(mEST.getText()); + return htmlBody; + } + } + + private class StyledTextToast { + Builder mBuilder; + CharSequence mColorTitle; + CharSequence mSizeTitle; + CharSequence[] mColorNames; + CharSequence[] mColorInts; + CharSequence[] mSizeNames; + CharSequence[] mSizeDisplayInts; + CharSequence[] mSizeSendInts; + EditStyledText mEST; + + public StyledTextToast(EditStyledText est) { + mEST = est; + } + + public void setBuilder(Builder builder) { + mBuilder = builder; + } + + public void setColorAlertParams(CharSequence colortitle, + CharSequence[] colornames, CharSequence[] colorints) { + mColorTitle = colortitle; + mColorNames = colornames; + mColorInts = colorints; + } + + public void setSizeAlertParams(CharSequence sizetitle, + CharSequence[] sizenames, CharSequence[] sizedisplayints, + CharSequence[] sizesendints) { + mSizeTitle = sizetitle; + mSizeNames = sizenames; + mSizeDisplayInts = sizedisplayints; + mSizeSendInts = sizesendints; + } + + public boolean checkColorAlertParams() { + if (DBG) { + Log.d(LOG_TAG, "--- checkParams"); + } + if (mBuilder == null) { + Log.e(LOG_TAG, "--- builder is null."); + return false; + } else if (mColorTitle == null || mColorNames == null + || mColorInts == null) { + Log.e(LOG_TAG, "--- color alert params are null."); + return false; + } else if (mColorNames.length != mColorInts.length) { + Log.e(LOG_TAG, "--- the length of color alert params are " + + "different."); + return false; + } + return true; + } + + public boolean checkSizeAlertParams() { + if (DBG) { + Log.d(LOG_TAG, "--- checkParams"); + } + if (mBuilder == null) { + Log.e(LOG_TAG, "--- builder is null."); + } else if (mSizeTitle == null || mSizeNames == null + || mSizeDisplayInts == null || mSizeSendInts == null) { + Log.e(LOG_TAG, "--- size alert params are null."); + } else if (mSizeNames.length != mSizeDisplayInts.length + && mSizeSendInts.length != mSizeDisplayInts.length) { + Log.e(LOG_TAG, "--- the length of size alert params are " + + "different."); + } + return true; + } + + private void onShowColorAlertDialog() { + if (DBG) { + Log.d(LOG_TAG, "--- onShowAlertDialog"); + } + if (!checkColorAlertParams()) { + return; + } + mBuilder.setTitle(mColorTitle); + mBuilder.setIcon(0); + mBuilder. + setItems(mColorNames, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.d("EETVM", "mBuilder.onclick:" + which); + int color = Integer.parseInt( + (String) mColorInts[which], 16) - 0x01000000; + mEST.setItemColor(color); + } + }); + mBuilder.show(); + } + + private void onShowSizeAlertDialog() { + if (DBG) { + Log.d(LOG_TAG, "--- onShowAlertDialog"); + } + if (!checkColorAlertParams()) { + return; + } + mBuilder.setTitle(mSizeTitle); + mBuilder.setIcon(0); + mBuilder. + setItems(mSizeNames, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.d("EETVM", "mBuilder.onclick:" + which); + int size = Integer + .parseInt((String) mSizeDisplayInts[which]); + mEST.setItemSize(size); + } + }); + mBuilder.show(); + } + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index c8b3ad4..f0b311c 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -78,6 +78,7 @@ public class LockPatternUtils { private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; + private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen"; private final ContentResolver mContentResolver; @@ -139,6 +140,16 @@ public class LockPatternUtils { } /** + * Return true if the user has ever chosen a pattern. This is true even if the pattern is + * currently cleared. + * + * @return True if the user has ever chosen a pattern. + */ + public boolean isPatternEverChosen() { + return getBoolean(PATTERN_EVER_CHOSEN); + } + + /** * Save a lock pattern. * @param pattern The new pattern to save. */ @@ -155,6 +166,7 @@ public class LockPatternUtils { raf.write(hash, 0, hash.length); } raf.close(); + setBoolean(PATTERN_EVER_CHOSEN, true); } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings provider Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); |