diff options
Diffstat (limited to 'core/java')
21 files changed, 338 insertions, 111 deletions
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java index 8eb9ba4..96c7246 100644 --- a/core/java/android/app/LauncherActivity.java +++ b/core/java/android/app/LauncherActivity.java @@ -439,14 +439,21 @@ public abstract class LauncherActivity extends ListActivity { protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) { return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0); } - + + /** + * @hide + */ + protected void onSortResultList(List<ResolveInfo> results) { + Collections.sort(results, new ResolveInfo.DisplayNameComparator(mPackageManager)); + } + /** * Perform the query to determine which results to show and return a list of them. */ public List<ListItem> makeListItems() { // Load all matching activities and sort correctly List<ResolveInfo> list = onQueryPackageManager(mIntent); - Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager)); + onSortResultList(list); ArrayList<ListItem> result = new ArrayList<ListItem>(list.size()); int listSize = list.size(); diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 888955c..2af65b9 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -448,6 +448,10 @@ public class AppWidgetManager { * and outside of the handler. * This method will only work when called from the uid that owns the AppWidget provider. * + * <p> + * This method will be ignored if a widget has not received a full update via + * {@link #updateAppWidget(int[], RemoteViews)}. + * * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. * @param views The RemoteViews object containing the incremental update / command. */ @@ -476,6 +480,10 @@ public class AppWidgetManager { * and outside of the handler. * This method will only work when called from the uid that owns the AppWidget provider. * + * <p> + * This method will be ignored if a widget has not received a full update via + * {@link #updateAppWidget(int[], RemoteViews)}. + * * @param appWidgetId The AppWidget instance for which to set the RemoteViews. * @param views The RemoteViews object containing the incremental update / command. */ diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 93c9526..1e4ad76 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -219,20 +219,13 @@ public class SyncManager { // Use this as a random offset to seed all periodic syncs private int mSyncRandomOffsetMillis; - private UserManager mUserManager; + private final UserManager mUserManager; private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours - private UserManager getUserManager() { - if (mUserManager == null) { - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - } - return mUserManager; - } - private List<UserInfo> getAllUsers() { - return getUserManager().getUsers(); + return mUserManager.getUsers(); } private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) { @@ -250,6 +243,10 @@ public class SyncManager { public void updateRunningAccounts() { mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); + if (mBootCompleted) { + doDatabaseCleanup(); + } + for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { if (!containsAccountAndUser(mRunningAccounts, currentSyncContext.mSyncOperation.account, @@ -265,6 +262,13 @@ public class SyncManager { sendCheckAlarmsMessage(); } + private void doDatabaseCleanup() { + for (UserInfo user : mUserManager.getUsers()) { + Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id); + mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); + } + } + private BroadcastReceiver mConnectivityIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { @@ -337,6 +341,7 @@ public class SyncManager { // Initialize the SyncStorageEngine first, before registering observers // and creating threads and so on; it may fail if the disk is full. mContext = context; + SyncStorageEngine.init(context); mSyncStorageEngine = SyncStorageEngine.getSingleton(); mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { @@ -402,6 +407,7 @@ public class SyncManager { mNotificationMgr = null; } mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); // This WakeLock is used to ensure that we stay awake between the time that we receive // a sync alarm notification and when we finish processing it. We need to do this @@ -896,12 +902,10 @@ public class SyncManager { updateRunningAccounts(); - final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); - mSyncStorageEngine.doDatabaseCleanup(accounts, userId); - mSyncQueue.addPendingOperations(userId); // Schedule sync for any accounts under started user + final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); for (Account account : accounts) { scheduleSync(account, userId, null, null, 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */); @@ -1624,6 +1628,8 @@ public class SyncManager { public void onBootCompleted() { mBootCompleted = true; + doDatabaseCleanup(); + if (mReadyToRunLatch != null) { mReadyToRunLatch.countDown(); } diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index d33382b..9dbfd50 100644 --- a/core/java/android/os/UEventObserver.java +++ b/core/java/android/os/UEventObserver.java @@ -16,6 +16,8 @@ package android.os; +import android.util.Log; + import java.util.ArrayList; import java.util.HashMap; @@ -37,14 +39,20 @@ import java.util.HashMap; * @hide */ public abstract class UEventObserver { + private static final String TAG = "UEventObserver"; + private static final boolean DEBUG = false; + private static UEventThread sThread; - private static native void native_setup(); - private static native int next_event(byte[] buffer); + private static native void nativeSetup(); + private static native String nativeWaitForNextEvent(); + private static native void nativeAddMatch(String match); + private static native void nativeRemoveMatch(String match); public UEventObserver() { } + @Override protected void finalize() throws Throwable { try { stopObserving(); @@ -78,10 +86,18 @@ public abstract class UEventObserver { * This method can be called multiple times to register multiple matches. * Only one call to stopObserving is required even with multiple registered * matches. - * @param match A substring of the UEvent to match. Use "" to match all - * UEvent's + * + * @param match A substring of the UEvent to match. Try to be as specific + * as possible to avoid incurring unintended additional cost from processing + * irrelevant messages. Netlink messages can be moderately high bandwidth and + * are expensive to parse. For example, some devices may send one netlink message + * for each vsync period. */ public final void startObserving(String match) { + if (match == null || match.isEmpty()) { + throw new IllegalArgumentException("match substring must be non-empty"); + } + final UEventThread t = getThread(); t.addObserver(match, this); } @@ -117,7 +133,7 @@ public abstract class UEventObserver { while (offset < length) { int equals = message.indexOf('=', offset); - int at = message.indexOf(0, offset); + int at = message.indexOf('\0', offset); if (at < 0) break; if (equals > offset && equals < at) { @@ -158,15 +174,17 @@ public abstract class UEventObserver { super("UEventObserver"); } + @Override public void run() { - native_setup(); + nativeSetup(); - byte[] buffer = new byte[1024]; - int len; while (true) { - len = next_event(buffer); - if (len > 0) { - sendEvent(new String(buffer, 0, len)); + String message = nativeWaitForNextEvent(); + if (message != null) { + if (DEBUG) { + Log.d(TAG, message); + } + sendEvent(message); } } } @@ -176,7 +194,7 @@ public abstract class UEventObserver { final int N = mKeysAndObservers.size(); for (int i = 0; i < N; i += 2) { final String key = (String)mKeysAndObservers.get(i); - if (message.indexOf(key) != -1) { + if (message.contains(key)) { final UEventObserver observer = (UEventObserver)mKeysAndObservers.get(i + 1); mTempObserversToSignal.add(observer); @@ -199,6 +217,7 @@ public abstract class UEventObserver { synchronized (mKeysAndObservers) { mKeysAndObservers.add(match); mKeysAndObservers.add(observer); + nativeAddMatch(match); } } @@ -208,7 +227,8 @@ public abstract class UEventObserver { for (int i = 0; i < mKeysAndObservers.size(); ) { if (mKeysAndObservers.get(i + 1) == observer) { mKeysAndObservers.remove(i + 1); - mKeysAndObservers.remove(i); + final String match = (String)mKeysAndObservers.remove(i); + nativeRemoveMatch(match); } else { i += 2; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 00ea873..8897039 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -386,10 +386,9 @@ public final class Settings { /** * Activity Action: Show settings to allow configuration of application - * development-related settings. - * <p> - * In some cases, a matching Activity may not exist, so ensure you safeguard - * against this. + * development-related settings. As of + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} this action is + * a required part of the platform. * <p> * Input: Nothing. * <p> diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index f865455..c72b714 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -37,6 +37,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; @@ -510,8 +511,12 @@ public class DreamService extends Service implements Window.Callback { @Override public void onDestroy() { if (mDebug) Slog.v(TAG, "onDestroy()"); - super.onDestroy(); // hook for subclasses + + // Just in case destroy came in before detach, let's take care of that now + detach(); + + super.onDestroy(); } // end public api @@ -521,13 +526,13 @@ public class DreamService extends Service implements Window.Callback { } /** - * Called when the Dream is about to be unbound and destroyed. + * Called by DreamController.stopDream() when the Dream is about to be unbound and destroyed. * * Must run on mHandler. */ private final void detach() { if (mWindow == null) { - Slog.e(TAG, "detach() called when not attached"); + // already detached! return; } @@ -540,7 +545,11 @@ public class DreamService extends Service implements Window.Callback { if (mDebug) Slog.v(TAG, "detach(): Removing window from window manager"); try { - mWindowManager.removeView(mWindow.getDecorView()); + // force our window to be removed synchronously + mWindowManager.removeViewImmediate(mWindow.getDecorView()); + // the following will print a log message if it finds any other leaked windows + WindowManagerGlobal.getInstance().closeAll(mWindowToken, + this.getClass().getName(), "Dream"); } catch (Throwable t) { Slog.w(TAG, "Crashed removing window view", t); } diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java new file mode 100644 index 0000000..70142ce --- /dev/null +++ b/core/java/android/service/dreams/Sandman.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dreams; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Slog; + +/** + * Internal helper for launching dreams to ensure consistency between the + * <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity. + * + * @hide + */ +public final class Sandman { + private static final String TAG = "Sandman"; + + private static final int DEFAULT_SCREENSAVER_ENABLED = 1; + private static final int DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK = 1; + + // The component name of a special dock app that merely launches a dream. + // We don't want to launch this app when docked because it causes an unnecessary + // activity transition. We just want to start the dream. + private static final ComponentName SOMNAMBULATOR_COMPONENT = + new ComponentName("com.android.systemui", "com.android.systemui.Somnambulator"); + + + // The sandman is eternal. No one instantiates him. + private Sandman() { + } + + /** + * Returns true if the specified dock app intent should be started. + * False if we should dream instead, if appropriate. + */ + public static boolean shouldStartDockApp(Context context, Intent intent) { + ComponentName name = intent.resolveActivity(context.getPackageManager()); + return name != null && !name.equals(SOMNAMBULATOR_COMPONENT); + } + + /** + * Starts a dream manually. + */ + public static void startDreamByUserRequest(Context context) { + startDream(context, false); + } + + /** + * Starts a dream when docked if the system has been configured to do so, + * otherwise does nothing. + */ + public static void startDreamWhenDockedIfAppropriate(Context context) { + if (!isScreenSaverEnabled(context) + || !isScreenSaverActivatedOnDock(context)) { + Slog.i(TAG, "Dreams currently disabled for docks."); + return; + } + + startDream(context, true); + } + + private static void startDream(Context context, boolean docked) { + try { + IDreamManager dreamManagerService = IDreamManager.Stub.asInterface( + ServiceManager.getService(DreamService.DREAM_SERVICE)); + if (dreamManagerService != null && !dreamManagerService.isDreaming()) { + if (docked) { + Slog.i(TAG, "Activating dream while docked."); + + // Wake up. + // The power manager will wake up the system automatically when it starts + // receiving power from a dock but there is a race between that happening + // and the UI mode manager starting a dream. We want the system to already + // be awake by the time this happens. Otherwise the dream may not start. + PowerManager powerManager = + (PowerManager)context.getSystemService(Context.POWER_SERVICE); + powerManager.wakeUp(SystemClock.uptimeMillis()); + } else { + Slog.i(TAG, "Activating dream by user request."); + } + + // Dream. + dreamManagerService.dream(); + } + } catch (RemoteException ex) { + Slog.e(TAG, "Could not start dream when docked.", ex); + } + } + + private static boolean isScreenSaverEnabled(Context context) { + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.SCREENSAVER_ENABLED, DEFAULT_SCREENSAVER_ENABLED, + UserHandle.USER_CURRENT) != 0; + } + + private static boolean isScreenSaverActivatedOnDock(Context context) { + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK, UserHandle.USER_CURRENT) != 0; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 22243b1..608bdd7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5214,11 +5214,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @RemotableViewMethod public void setContentDescription(CharSequence contentDescription) { + if (mContentDescription == null) { + if (contentDescription == null) { + return; + } + } else if (mContentDescription.equals(contentDescription)) { + return; + } mContentDescription = contentDescription; final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } + notifyAccessibilityStateChanged(); } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6436059..0661992 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4817,6 +4817,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + final int layoutDirection = getLayoutDirection(); + lp.resolveLayoutDirection(layoutDirection); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index 81b36db..e0e19b9 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -217,6 +217,12 @@ public final class TextServicesManager { public SpellCheckerSubtype getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype) { try { + if (sService == null) { + // TODO: This is a workaround. Needs to investigate why sService could be null + // here. + Log.e(TAG, "sService is null."); + return null; + } // Passing null as a locale until we support multiple enabled spell checker subtypes. return sService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); } catch (RemoteException e) { diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java index d6576e1..357a16e 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -96,11 +96,26 @@ class AccessibilityInjector { // Template for JavaScript that performs AndroidVox actions. private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE = - "cvox.AndroidVox.performAction('%1s')"; + "(function() {" + + " if ((typeof(cvox) != 'undefined')"+ + " && (typeof(cvox.ChromeVox) != 'undefined')" + + " && (typeof(cvox.AndroidVox) != 'undefined')" + + " && cvox.ChromeVox.isActive) {" + + " return cvox.AndroidVox.performAction('%1s');" + + " } else {" + + " return false;" + + " }" + + "})()"; // JS code used to shut down an active AndroidVox instance. private static final String TOGGLE_CVOX_TEMPLATE = - "javascript:(function() { cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b); })();"; + "javascript:(function() {" + + " if ((typeof(cvox) != 'undefined')"+ + " && (typeof(cvox.ChromeVox) != 'undefined')" + + " && (typeof(cvox.ChromeVox.host) != 'undefined')) {" + + " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b);" + + " }" + + "})();"; /** * Creates an instance of the AccessibilityInjector based on @@ -776,20 +791,26 @@ class AccessibilityInjector { while (true) { try { if (mResultId == resultId) { + if (DEBUG) + Log.w(TAG, "Received CVOX result"); return true; } if (mResultId > resultId) { + if (DEBUG) + Log.w(TAG, "Obsolete CVOX result"); return false; } final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; waitTimeMillis = RESULT_TIMEOUT - elapsedTimeMillis; if (waitTimeMillis <= 0) { + if (DEBUG) + Log.w(TAG, "Timed out while waiting for CVOX result"); return false; } mResultLock.wait(waitTimeMillis); } catch (InterruptedException ie) { if (DEBUG) - Log.w(TAG, "Timed out while waiting for CVOX result"); + Log.w(TAG, "Interrupted while waiting for CVOX result"); /* ignore */ } } @@ -805,6 +826,8 @@ class AccessibilityInjector { @JavascriptInterface @SuppressWarnings("unused") public void onResult(String id, String result) { + if (DEBUG) + Log.w(TAG, "Saw CVOX result of '" + result + "'"); final long resultId; try { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4202a7f..6df7820 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1480,7 +1480,6 @@ public class WebView extends AbsoluteLayout * * @param listener an implementation of WebView.PictureListener * @deprecated This method is now obsolete. - * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ @Deprecated public void setPictureListener(PictureListener listener) { diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index d68511c..7d0d0ba 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -5185,7 +5185,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (cm.hasPrimaryClip()) { Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x), contentToViewY(mSelectCursorBase.y)); - Point cursorTop = calculateCaretTop(); + Point cursorTop = calculateBaseCaretTop(); cursorTop.set(contentToViewX(cursorTop.x), contentToViewY(cursorTop.y)); @@ -5229,17 +5229,22 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return scale; } + private Point calculateBaseCaretTop() { + return calculateCaretTop(mSelectCursorBase, mSelectCursorBaseTextQuad); + } + + private Point calculateDraggingCaretTop() { + return calculateCaretTop(mSelectDraggingCursor, mSelectDraggingTextQuad); + } + /** * Assuming arbitrary shape of a quadralateral forming text bounds, this * calculates the top of a caret. */ - private Point calculateCaretTop() { - float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y, - mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3); - int x = Math.round(scaleCoordinate(scale, - mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x)); - int y = Math.round(scaleCoordinate(scale, - mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y)); + private static Point calculateCaretTop(Point base, QuadF quad) { + float scale = scaleAlongSegment(base.x, base.y, quad.p4, quad.p3); + int x = Math.round(scaleCoordinate(scale, quad.p1.x, quad.p2.x)); + int y = Math.round(scaleCoordinate(scale, quad.p1.y, quad.p2.y)); return new Point(x, y); } @@ -5269,12 +5274,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return true; } - private void updateWebkitSelection() { + private void updateWebkitSelection(boolean isSnapped) { int handleId = (mSelectDraggingCursor == mSelectCursorBase) ? HANDLE_ID_BASE : HANDLE_ID_EXTENT; + int x = mSelectDraggingCursor.x; + int y = mSelectDraggingCursor.y; + if (isSnapped) { + // "center" the cursor in the snapping quad + Point top = calculateDraggingCaretTop(); + x = Math.round((top.x + x) / 2); + y = Math.round((top.y + y) / 2); + } mWebViewCore.removeMessages(EventHub.SELECT_TEXT); mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, - mSelectDraggingCursor.x, mSelectDraggingCursor.y, (Integer)handleId); + x, y, (Integer)handleId); } private void resetCaretTimer() { @@ -5616,7 +5629,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc Math.max(0, mEditTextContentBounds.top - buffer), mEditTextContentBounds.right + buffer, mEditTextContentBounds.bottom + buffer); - Point caretTop = calculateCaretTop(); + Point caretTop = calculateBaseCaretTop(); if (visibleRect.width() < mEditTextContentBounds.width()) { // The whole edit won't fit in the width, so use the caret rect if (mSelectCursorBase.x < caretTop.x) { @@ -5947,10 +5960,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } else { endScrollEdit(); } + boolean snapped = false; if (inCursorText || (mIsEditingText && !inEditBounds)) { snapDraggingCursor(); + snapped = true; } - updateWebkitSelection(); + updateWebkitSelection(snapped); if (!inCursorText && mIsEditingText && inEditBounds) { // Visually snap even if we have moved the handle. snapDraggingCursor(); @@ -6277,7 +6292,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc int oldX = mSelectDraggingCursor.x; int oldY = mSelectDraggingCursor.y; mSelectDraggingCursor.set(selectionX, selectionY); - updateWebkitSelection(); + updateWebkitSelection(false); scrollEditText(scrollX, scrollY); mSelectDraggingCursor.set(oldX, oldY); } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 00cd604..45f30df 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -304,11 +304,16 @@ public class FrameLayout extends ViewGroup { int maxWidth = 0; int childState = 0; + final int layoutDirection = getLayoutDirection(); + for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + // measureChildWithMargins() has triggered layout params resolution, so no need + // to do it now final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 4ca405b..ace26f3 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -414,12 +414,15 @@ public class RelativeLayout extends ViewGroup { final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY; final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY; + final int layoutDirection = getLayoutDirection(); + View[] views = mSortedHorizontalChildren; int count = views.length; for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() != GONE) { LayoutParams params = (LayoutParams) child.getLayoutParams(); + params.resolveLayoutDirection(layoutDirection); applyHorizontalSizeRules(params, myWidth); measureChildHorizontal(child, params, myWidth, myHeight); @@ -483,8 +486,6 @@ public class RelativeLayout extends ViewGroup { } } - final int layoutDirection = getLayoutDirection(); - if (isWrapContentWidth) { // Width already has left padding in it since it was calculated by looking at // the right of each child view diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index bc41931..1def89f 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -329,11 +329,13 @@ public class ScrollView extends FrameLayout { return; } + final int layoutDirection = getLayoutDirection(); if (getChildCount() > 0) { final View child = getChildAt(0); int height = getMeasuredHeight(); if (child.getMeasuredHeight() < height) { final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.resolveLayoutDirection(layoutDirection); int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); diff --git a/core/java/android/widget/SimpleExpandableListAdapter.java b/core/java/android/widget/SimpleExpandableListAdapter.java index f514374..015c169 100644 --- a/core/java/android/widget/SimpleExpandableListAdapter.java +++ b/core/java/android/widget/SimpleExpandableListAdapter.java @@ -38,8 +38,6 @@ import java.util.Map; */ public class SimpleExpandableListAdapter extends BaseExpandableListAdapter { private List<? extends Map<String, ?>> mGroupData; - // Keeps track of if a group is currently expanded or not - private boolean[] mIsGroupExpanded; private int mExpandedGroupLayout; private int mCollapsedGroupLayout; private String[] mGroupFrom; @@ -198,8 +196,6 @@ public class SimpleExpandableListAdapter extends BaseExpandableListAdapter { int childLayout, int lastChildLayout, String[] childFrom, int[] childTo) { mGroupData = groupData; - // Initially all groups are not expanded - mIsGroupExpanded = new boolean[groupData.size()]; mExpandedGroupLayout = expandedGroupLayout; mCollapsedGroupLayout = collapsedGroupLayout; mGroupFrom = groupFrom; @@ -302,52 +298,4 @@ public class SimpleExpandableListAdapter extends BaseExpandableListAdapter { return true; } - /** - * {@inheritDoc} - * @return 1 for the last child in a group, 0 for the other children. - */ - @Override - public int getChildType(int groupPosition, int childPosition) { - final int childrenInGroup = getChildrenCount(groupPosition); - return childPosition == childrenInGroup - 1 ? 1 : 0; - } - - /** - * {@inheritDoc} - * @return 2, one type for the last child in a group, one for the other children. - */ - @Override - public int getChildTypeCount() { - return 2; - } - - /** - * {@inheritDoc} - * @return 1 for an expanded group view, 0 for a collapsed one. - */ - @Override - public int getGroupType(int groupPosition) { - return mIsGroupExpanded[groupPosition] ? 1 : 0; - } - - /** - * {@inheritDoc} - * @return 2, one for a collapsed group view, one for an expanded one. - */ - @Override - public int getGroupTypeCount() { - return 2; - } - - /** {@inheritDoc} */ - @Override - public void onGroupCollapsed(int groupPosition) { - mIsGroupExpanded[groupPosition] = false; - } - - /** {@inheritDoc} */ - @Override - public void onGroupExpanded(int groupPosition) { - mIsGroupExpanded[groupPosition] = true; - } } diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index db3853f..3f8f9dae 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -192,7 +192,9 @@ public class TableRow extends LinearLayout { int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) { if (mConstrainedColumnWidths != null) { + final int layoutDirection = getLayoutDirection(); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.resolveLayoutDirection(layoutDirection); int measureMode = MeasureSpec.EXACTLY; int columnWidth = 0; @@ -226,7 +228,6 @@ public class TableRow extends LinearLayout { final int childWidth = child.getMeasuredWidth(); lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth; - final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: @@ -292,11 +293,13 @@ public class TableRow extends LinearLayout { } final int[] columnWidths = mColumnWidths; + final int layoutDirection = getLayoutDirection(); for (int i = 0; i < numColumns; i++) { final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); + layoutParams.resolveLayoutDirection(layoutDirection); if (layoutParams.span == 1) { int spec; switch (layoutParams.width) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 410a0ca..958b669 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -33,6 +33,7 @@ import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -132,6 +133,7 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Locale; +import java.util.concurrent.locks.ReentrantLock; /** * Displays text to the user and optionally allows them to edit it. A TextView @@ -378,6 +380,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private InputFilter[] mFilters = NO_FILTERS; + private volatile Locale mCurrentTextServicesLocaleCache; + private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock(); + // It is possible to have a selection even when mEditor is null (programmatically set, like when // a link is pressed). These highlight-related fields do not go in mEditor. int mHighlightColor = 0x6633B5E5; @@ -7675,13 +7680,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * This is a temporary method. Future versions may support multi-locale text. + * Caveat: This method may not return the latest text services locale, but this should be + * acceptable and it's more important to make this method asynchronous. * * @return The locale that should be used for a word iterator and a spell checker * in this TextView, based on the current spell checker settings, * the current IME's locale, or the system default locale. * @hide */ + // TODO: Support multi-locale + // TODO: Update the text services locale immediately after the keyboard locale is switched + // by catching intent of keyboard switch event public Locale getTextServicesLocale() { + if (mCurrentTextServicesLocaleCache == null) { + // If there is no cached text services locale, just return the default locale. + mCurrentTextServicesLocaleCache = Locale.getDefault(); + } + // Start fetching the text services locale asynchronously. + updateTextServicesLocaleAsync(); + return mCurrentTextServicesLocaleCache; + } + + private void updateTextServicesLocaleAsync() { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + if (mCurrentTextServicesLocaleLock.tryLock()) { + try { + updateTextServicesLocaleLocked(); + } finally { + mCurrentTextServicesLocaleLock.unlock(); + } + } + } + }); + } + + private void updateTextServicesLocaleLocked() { Locale locale = Locale.getDefault(); final TextServicesManager textServicesManager = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); @@ -7689,7 +7724,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (subtype != null) { locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); } - return locale; + mCurrentTextServicesLocaleCache = locale; } void onLocaleChanged() { diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 8bc1081..43c63b6 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -343,9 +343,11 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi final int height = maxHeight - verticalPadding; final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); + final int layoutDirection = getLayoutDirection(); if (mClose != null) { availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + lp.resolveLayoutDirection(layoutDirection); availableWidth -= lp.leftMargin + lp.rightMargin; } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 5a7f10c..d8b3d2f 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -946,6 +946,9 @@ public class ActionBarView extends AbsActionBarView { final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ? (ActionBar.LayoutParams) lp : null; + final int layoutDirection = getLayoutDirection(); + lp.resolveLayoutDirection(layoutDirection); + int horizontalMargin = 0; int verticalMargin = 0; if (ablp != null) { @@ -1096,9 +1099,9 @@ public class ActionBarView extends AbsActionBarView { customView = mCustomNavView; } if (customView != null) { - final int resolvedLayoutDirection = getLayoutDirection(); ViewGroup.LayoutParams lp = customView.getLayoutParams(); - lp.resolveLayoutDirection(resolvedLayoutDirection); + final int layoutDirection = getLayoutDirection(); + lp.resolveLayoutDirection(layoutDirection); final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ? (ActionBar.LayoutParams) lp : null; final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY; @@ -1139,7 +1142,7 @@ public class ActionBarView extends AbsActionBarView { } int xpos = 0; - switch (Gravity.getAbsoluteGravity(hgravity, resolvedLayoutDirection)) { + switch (Gravity.getAbsoluteGravity(hgravity, layoutDirection)) { case Gravity.CENTER_HORIZONTAL: xpos = ((mRight - mLeft) - navWidth) / 2; break; @@ -1336,11 +1339,15 @@ public class ActionBarView extends AbsActionBarView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0); + // measureChildWithMargins() has triggered layout params resolution, so no need + // to do it now final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams(); mUpWidth = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin; int width = mUpView.getVisibility() == GONE ? 0 : mUpWidth; int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin; measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0); + // measureChildWithMargins() has triggered layout params resolution, so no need + // to do it now final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams(); width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin; height = Math.max(height, |