diff options
Diffstat (limited to 'core/java/android')
21 files changed, 550 insertions, 161 deletions
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java index df64035..b810b89 100644 --- a/core/java/android/app/FragmentBreadCrumbs.java +++ b/core/java/android/app/FragmentBreadCrumbs.java @@ -19,7 +19,9 @@ package android.app; import android.animation.LayoutTransition; import android.app.FragmentManager.BackStackEntry; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -51,7 +53,11 @@ public class FragmentBreadCrumbs extends ViewGroup private OnClickListener mParentClickListener; private OnBreadCrumbClickListener mOnBreadCrumbClickListener; - + + private int mGravity; + + private static final int DEFAULT_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL; + /** * Interface to intercept clicks on the bread crumbs. */ @@ -80,6 +86,14 @@ public class FragmentBreadCrumbs extends ViewGroup public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.FragmentBreadCrumbs, defStyle, 0); + + mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity, + DEFAULT_GRAVITY); + + a.recycle(); } /** @@ -159,16 +173,50 @@ public class FragmentBreadCrumbs extends ViewGroup @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - // Eventually we should implement our own layout of the views, - // rather than relying on a linear layout. + // Eventually we should implement our own layout of the views, rather than relying on + // a single linear layout. final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); + if (childCount == 0) { + return; + } + + final View child = getChildAt(0); - int childRight = mPaddingLeft + child.getMeasuredWidth() - mPaddingRight; - int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom; - child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); + final int childTop = mPaddingTop; + final int childBottom = mPaddingTop + child.getMeasuredHeight() - mPaddingBottom; + + int childLeft; + int childRight; + + final int layoutDirection = getLayoutDirection(); + final int horizontalGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; + switch (Gravity.getAbsoluteGravity(horizontalGravity, layoutDirection)) { + case Gravity.RIGHT: + childRight = mRight - mLeft - mPaddingRight; + childLeft = childRight - child.getMeasuredWidth(); + break; + + case Gravity.CENTER_HORIZONTAL: + childLeft = mPaddingLeft + (mRight - mLeft - child.getMeasuredWidth()) / 2; + childRight = childLeft + child.getMeasuredWidth(); + break; + + case Gravity.LEFT: + default: + childLeft = mPaddingLeft; + childRight = childLeft + child.getMeasuredWidth(); + break; } + + if (childLeft < mPaddingLeft) { + childLeft = mPaddingLeft; + } + + if (childRight > mRight - mLeft - mPaddingRight) { + childRight = mRight - mLeft - mPaddingRight; + } + + child.layout(childLeft, childTop, childRight, childBottom); } @Override 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 e0e2995..1e4ad76 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -243,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, @@ -258,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) { @@ -891,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 */); @@ -1619,6 +1628,8 @@ public class SyncManager { public void onBootCompleted() { mBootCompleted = true; + doDatabaseCleanup(); + if (mReadyToRunLatch != null) { mReadyToRunLatch.countDown(); } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 0941d71..2bec1c1 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -16,6 +16,7 @@ package android.os; +import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -91,7 +92,7 @@ public class FileUtils { } return result; } - + /** * Copy data from a source stream to destFile. * Return true if succeed, return false if failed. @@ -143,12 +144,16 @@ public class FileUtils { */ public static String readTextFile(File file, int max, String ellipsis) throws IOException { InputStream input = new FileInputStream(file); + // wrapping a BufferedInputStream around it because when reading /proc with unbuffered + // input stream, bytes read not equal to buffer size is not necessarily the correct + // indication for EOF; but it is true for BufferedInputStream due to its implementation. + BufferedInputStream bis = new BufferedInputStream(input); try { long size = file.length(); if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes if (size > 0 && (max == 0 || size < max)) max = (int) size; byte[] data = new byte[max + 1]; - int length = input.read(data); + int length = bis.read(data); if (length <= 0) return ""; if (length <= max) return new String(data, 0, length); if (ellipsis == null) return new String(data, 0, max); @@ -161,7 +166,7 @@ public class FileUtils { if (last != null) rolled = true; byte[] tmp = last; last = data; data = tmp; if (data == null) data = new byte[-max]; - len = input.read(data); + len = bis.read(data); } while (len == data.length); if (last == null && len <= 0) return ""; @@ -178,12 +183,13 @@ public class FileUtils { int len; byte[] data = new byte[1024]; do { - len = input.read(data); + len = bis.read(data); if (len > 0) contents.write(data, 0, len); } while (len == data.length); return contents.toString(); } } finally { + bis.close(); input.close(); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 2739cac..898c766 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -320,6 +320,8 @@ public class UserManager { * @return a value greater than or equal to 1 */ public static int getMaxSupportedUsers() { + // Don't allow multiple users on certain builds + if (android.os.Build.ID.startsWith("JVP")) return 1; return SystemProperties.getInt("fw.max_users", Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)); } 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/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..946965b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3666,15 +3666,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if // defined. - if (startPaddingDefined) { - mUserPaddingLeftInitial = startPadding; - } else if (leftPaddingDefined) { + if (leftPaddingDefined) { mUserPaddingLeftInitial = leftPadding; } - if (endPaddingDefined) { - mUserPaddingRightInitial = endPadding; - } - else if (rightPaddingDefined) { + if (rightPaddingDefined) { mUserPaddingRightInitial = rightPadding; } } @@ -5214,11 +5209,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(); } /** @@ -11551,8 +11554,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Resolve all RTL related properties. + * + * @hide */ - void resolveRtlPropertiesIfNeeded() { + public void resolveRtlPropertiesIfNeeded() { if (!needRtlPropertiesResolution()) return; // Order is important here: LayoutDirection MUST be resolved first @@ -11576,8 +11581,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onRtlPropertiesChanged(getLayoutDirection()); } - // Reset resolution of all RTL related properties. - void resetRtlProperties() { + /** + * Reset resolution of all RTL related properties. + * + * @hide + */ + public void resetRtlProperties() { resetResolvedLayoutDirection(); resetResolvedTextDirection(); resetResolvedTextAlignment(); @@ -14187,7 +14196,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void resolveDrawables() { + protected void resolveDrawables() { if (mBackground != null) { mBackground.setLayoutDirection(getLayoutDirection()); } @@ -14210,7 +14219,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void onResolveDrawables(int layoutDirection) { } - private void resetResolvedDrawables() { + /** + * @hide + */ + protected void resetResolvedDrawables() { mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED; } @@ -14796,14 +14808,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (isRtlCompatibilityMode()) { mPaddingLeft = mUserPaddingLeftInitial; mPaddingRight = mUserPaddingRightInitial; + return; + } + if (isLayoutRtl()) { + mPaddingLeft = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingLeftInitial; + mPaddingRight = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingRightInitial; } else { - if (isLayoutRtl()) { - mPaddingLeft = mUserPaddingRightInitial; - mPaddingRight = mUserPaddingLeftInitial; - } else { - mPaddingLeft = mUserPaddingLeftInitial; - mPaddingRight = mUserPaddingRightInitial; - } + mPaddingLeft = (mUserPaddingStart >= 0) ? mUserPaddingStart : mUserPaddingLeftInitial; + mPaddingRight = (mUserPaddingEnd >= 0) ? mUserPaddingEnd : mUserPaddingRightInitial; } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6436059..9ce7df9 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 @@ -5261,6 +5263,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override + public void resolveRtlPropertiesIfNeeded() { + super.resolveRtlPropertiesIfNeeded(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resolveRtlPropertiesIfNeeded(); + } + } + } + + /** + * @hide + */ + @Override public boolean resolveLayoutDirection() { final boolean result = super.resolveLayoutDirection(); if (result) { @@ -5315,6 +5332,51 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override + public void resolvePadding() { + super.resolvePadding(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resolvePadding(); + } + } + } + + /** + * @hide + */ + @Override + protected void resolveDrawables() { + super.resolveDrawables(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resolveDrawables(); + } + } + } + + /** + * @hide + */ + @Override + public void resetRtlProperties() { + super.resetRtlProperties(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resetRtlProperties(); + } + } + } + + /** + * @hide + */ + @Override public void resetResolvedLayoutDirection() { super.resetResolvedLayoutDirection(); @@ -5360,6 +5422,38 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * @hide + */ + @Override + public void resetResolvedPadding() { + super.resetResolvedPadding(); + + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resetResolvedPadding(); + } + } + } + + /** + * @hide + */ + @Override + protected void resetResolvedDrawables() { + super.resetResolvedDrawables(); + + int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.isLayoutDirectionInherited()) { + child.resetResolvedDrawables(); + } + } + } + + /** * Return true if the pressed state should be delayed for children or descendants of this * ViewGroup. Generally, this should be done for containers that can scroll, such as a List. * This prevents the pressed state from appearing when the user is actually trying to scroll 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..fe5cad4 100644 --- a/core/java/android/webkit/AccessibilityInjector.java +++ b/core/java/android/webkit/AccessibilityInjector.java @@ -96,11 +96,32 @@ 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')" + + " && (cvox != null)" + + " && (typeof(cvox.ChromeVox) != 'undefined')" + + " && (cvox.ChromeVox != null)" + + " && (typeof(cvox.AndroidVox) != 'undefined')" + + " && (cvox.AndroidVox != null)" + + " && 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')" + + " && (cvox != null)" + + " && (typeof(cvox.ChromeVox) != 'undefined')" + + " && (cvox.ChromeVox != null)" + + " && (typeof(cvox.ChromeVox.host) != 'undefined')" + + " && (cvox.ChromeVox.host != null)) {" + + " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b);" + + " }" + + "})();"; /** * Creates an instance of the AccessibilityInjector based on @@ -117,33 +138,60 @@ class AccessibilityInjector { } /** + * If JavaScript is enabled, pauses or resumes AndroidVox. + * + * @param enabled Whether feedback should be enabled. + */ + public void toggleAccessibilityFeedback(boolean enabled) { + if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { + return; + } + + toggleAndroidVox(enabled); + + if (!enabled && (mTextToSpeech != null)) { + mTextToSpeech.stop(); + } + } + + /** * Attempts to load scripting interfaces for accessibility. * <p> - * This should be called when the window is attached. - * </p> + * This should only be called before a page loads. */ - public void addAccessibilityApisIfNecessary() { + private void addAccessibilityApisIfNecessary() { if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { return; } addTtsApis(); addCallbackApis(); - toggleAndroidVox(true); } /** * Attempts to unload scripting interfaces for accessibility. * <p> - * This should be called when the window is detached. - * </p> + * This should only be called before a page loads. */ - public void removeAccessibilityApisIfNecessary() { - toggleAndroidVox(false); + private void removeAccessibilityApisIfNecessary() { removeTtsApis(); removeCallbackApis(); } + /** + * Destroys this accessibility injector. + */ + public void destroy() { + if (mTextToSpeech != null) { + mTextToSpeech.shutdown(); + mTextToSpeech = null; + } + + if (mCallback != null) { + mCallback = null; + } + } + private void toggleAndroidVox(boolean state) { if (!mAccessibilityScriptInjected) { return; @@ -502,7 +550,12 @@ class AccessibilityInjector { * settings. */ private boolean isJavaScriptEnabled() { - return mWebView.getSettings().getJavaScriptEnabled(); + final WebSettings settings = mWebView.getSettings(); + if (settings == null) { + return false; + } + + return settings.getJavaScriptEnabled(); } /** @@ -717,7 +770,7 @@ class AccessibilityInjector { private final String mInterfaceName; private boolean mResult = false; - private long mResultId = -1; + private int mResultId = -1; private CallbackHandler(String interfaceName) { mInterfaceName = interfaceName; @@ -769,28 +822,46 @@ class AccessibilityInjector { * @return Whether the result was received. */ private boolean waitForResultTimedLocked(int resultId) { - if (DEBUG) - Log.d(TAG, "Waiting for CVOX result..."); - long waitTimeMillis = RESULT_TIMEOUT; final long startTimeMillis = SystemClock.uptimeMillis(); + + if (DEBUG) + Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "..."); + while (true) { + // Fail if we received a callback from the future. + if (mResultId > resultId) { + if (DEBUG) + Log.w(TAG, "Aborted CVOX result"); + return false; + } + + final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis); + + // Succeed if we received the callback we were expecting. + if (DEBUG) + Log.w(TAG, "Check " + mResultId + " versus expected " + resultId); + if (mResultId == resultId) { + if (DEBUG) + Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms"); + return true; + } + + final long waitTimeMillis = (RESULT_TIMEOUT - elapsedTimeMillis); + + // Fail if we've already exceeded the timeout. + if (waitTimeMillis <= 0) { + if (DEBUG) + Log.w(TAG, "Timed out while waiting for CVOX result"); + return false; + } + try { - if (mResultId == resultId) { - return true; - } - if (mResultId > resultId) { - return false; - } - final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; - waitTimeMillis = RESULT_TIMEOUT - elapsedTimeMillis; - if (waitTimeMillis <= 0) { - return false; - } + if (DEBUG) + Log.w(TAG, "Start waiting..."); mResultLock.wait(waitTimeMillis); } catch (InterruptedException ie) { if (DEBUG) - Log.w(TAG, "Timed out while waiting for CVOX result"); - /* ignore */ + Log.w(TAG, "Interrupted while waiting for CVOX result"); } } } @@ -805,10 +876,12 @@ class AccessibilityInjector { @JavascriptInterface @SuppressWarnings("unused") public void onResult(String id, String result) { - final long resultId; + if (DEBUG) + Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id); + final int resultId; try { - resultId = Long.parseLong(id); + resultId = Integer.parseInt(id); } catch (NumberFormatException e) { return; } @@ -817,6 +890,9 @@ class AccessibilityInjector { if (resultId > mResultId) { mResult = Boolean.parseBoolean(result); mResultId = resultId; + } else { + if (DEBUG) + Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId); } mResultLock.notifyAll(); } 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..0f8966e 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -2132,6 +2132,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void destroyJava() { mCallbackProxy.blockMessages(); + if (mAccessibilityInjector != null) { + mAccessibilityInjector.destroy(); + mAccessibilityInjector = null; + } if (mWebViewCore != null) { // Tell WebViewCore to destroy itself synchronized (this) { @@ -3967,8 +3971,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // null, and that will be the case mWebView.setCertificate(null); - // reset the flag since we set to true in if need after - // loading is see onPageFinished(Url) if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().onPageStarted(url); } @@ -5185,7 +5187,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 +5231,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 +5276,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() { @@ -5384,7 +5399,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mWebView.hasWindowFocus()) setActive(true); if (isAccessibilityInjectionEnabled()) { - getAccessibilityInjector().addAccessibilityApisIfNecessary(); + getAccessibilityInjector().toggleAccessibilityFeedback(true); } updateHwAccelerated(); @@ -5397,11 +5412,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mWebView.hasWindowFocus()) setActive(false); if (isAccessibilityInjectionEnabled()) { - getAccessibilityInjector().removeAccessibilityApisIfNecessary(); - } else { - // Ensure the injector is cleared if we're detaching from the window - // and accessibility is disabled. - mAccessibilityInjector = null; + getAccessibilityInjector().toggleAccessibilityFeedback(false); } updateHwAccelerated(); @@ -5616,7 +5627,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 +5958,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 +6290,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/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index e74e37c..de8b80d 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -188,10 +188,11 @@ public class CheckedTextView extends TextView implements Checkable { resetPaddingToInitialValues(); int newPadding = (mCheckMarkDrawable != null) ? mCheckMarkWidth + mBasePadding : mBasePadding; - mNeedRequestlayout |= (mPaddingRight != newPadding); if (isLayoutRtl()) { + mNeedRequestlayout |= (mPaddingLeft != newPadding); mPaddingLeft = newPadding; } else { + mNeedRequestlayout |= (mPaddingRight != newPadding); mPaddingRight = newPadding; } if (mNeedRequestlayout) { @@ -200,18 +201,6 @@ public class CheckedTextView extends TextView implements Checkable { } } - @Override - public void setPadding(int left, int top, int right, int bottom) { - super.setPadding(left, top, right, bottom); - setBasePadding(isLayoutRtl()); - } - - @Override - public void setPaddingRelative(int start, int top, int end, int bottom) { - super.setPaddingRelative(start, top, end, bottom); - setBasePadding(isLayoutRtl()); - } - private void setBasePadding(boolean isLayoutRtl) { if (isLayoutRtl) { mBasePadding = mPaddingLeft; 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..b3c679c 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() { @@ -8279,6 +8314,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** + * @hide + */ protected void resetResolvedDrawables() { mResolvedDrawables = false; } |
