diff options
Diffstat (limited to 'core')
31 files changed, 1626 insertions, 462 deletions
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 326f27c..f3a442a 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -910,7 +910,7 @@ public class ValueAnimator extends Animator { animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running - setCurrentPlayTime(getCurrentPlayTime()); + setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 29d96fe..781eea5 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2540,11 +2540,10 @@ public class Activity extends ContextThemeWrapper if (item.getItemId() == android.R.id.home && mActionBar != null && (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { if (mParent == null) { - onNavigateUp(); + return onNavigateUp(); } else { - mParent.onNavigateUpFromChild(this); + return mParent.onNavigateUpFromChild(this); } - return true; } return false; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index b730581..3d0b7d8 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -147,12 +147,17 @@ public class ActivityOptions { * activity is scaled from a small originating area of the screen to * its final full representation. * + * <p>If the Intent this is being used with has not set its + * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, + * those bounds will be filled in for you based on the initial + * bounds passed in here. + * * @param source The View that the new activity is animating from. This * defines the coordinate space for <var>startX</var> and <var>startY</var>. * @param startX The x starting location of the new activity, relative to <var>source</var>. * @param startY The y starting location of the activity, relative to <var>source</var>. * @param startWidth The initial width of the new activity. - * @param startWidth The initial height of the new activity. + * @param startHeight The initial height of the new activity. * @return Returns a new ActivityOptions object that you can use to * supply these options as the options Bundle when starting an activity. */ @@ -175,6 +180,11 @@ public class ActivityOptions { * is scaled from a given position to the new activity window that is * being started. * + * <p>If the Intent this is being used with has not set its + * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, + * those bounds will be filled in for you based on the initial + * thumbnail location and size provided here. + * * @param source The View that this thumbnail is animating from. This * defines the coordinate space for <var>startX</var> and <var>startY</var>. * @param thumbnail The bitmap that will be shown as the initial thumbnail diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b55ee26..314f5c2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -136,7 +136,7 @@ public final class ActivityThread { /** @hide */ public static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; - private static final boolean DEBUG_BACKUP = true; + private static final boolean DEBUG_BACKUP = false; private static final boolean DEBUG_CONFIGURATION = false; private static final boolean DEBUG_SERVICE = false; private static final boolean DEBUG_MEMORY_TRIM = false; @@ -1172,10 +1172,10 @@ public final class ActivityThread { case DUMP_PROVIDER: return "DUMP_PROVIDER"; } } - return "(unknown)"; + return Integer.toString(code); } public void handleMessage(Message msg) { - if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what); + if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); @@ -1378,7 +1378,7 @@ public final class ActivityThread { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; } - if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what); + if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } private void maybeSnapshot() { diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 05acd63..ac9ee26 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -37,6 +37,7 @@ public final class Trace { public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5; public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6; public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7; + public static final long TRACE_TAG_AUDIO = 1L << 8; private static final long sEnabledTags = nativeGetEnabledTags(); diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java index f543de9..2a994b2 100644 --- a/core/java/android/server/BluetoothAdapterStateMachine.java +++ b/core/java/android/server/BluetoothAdapterStateMachine.java @@ -39,7 +39,7 @@ import java.io.PrintWriter; * (BluetootOn)<----------------------<- * | ^ -------------------->- | * | | | | - * TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON + * USER_TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON * AIRPLANE_MODE_ON | | | | * V | | | * (Switching) (PerProcessState) @@ -121,8 +121,10 @@ final class BluetoothAdapterStateMachine extends StateMachine { private static final int DEVICES_DISCONNECT_TIMEOUT = 103; // Prepare Bluetooth timeout happens private static final int PREPARE_BLUETOOTH_TIMEOUT = 104; - // Bluetooth Powerdown timeout happens - private static final int POWER_DOWN_TIMEOUT = 105; + // Bluetooth turn off wait timeout happens + private static final int TURN_OFF_TIMEOUT = 105; + // Bluetooth device power off wait timeout happens + private static final int POWER_DOWN_TIMEOUT = 106; private Context mContext; private BluetoothService mBluetoothService; @@ -137,13 +139,17 @@ final class BluetoothAdapterStateMachine extends StateMachine { // this is the BluetoothAdapter state that reported externally private int mPublicState; + // When turning off, broadcast STATE_OFF in the last HotOff state + // This is because we do HotOff -> PowerOff -> HotOff for USER_TURN_OFF + private boolean mDelayBroadcastStateOff; // timeout value waiting for all the devices to be disconnected private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000; private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 10000; - private static final int POWER_DOWN_TIMEOUT_TIME = 5000; + private static final int TURN_OFF_TIMEOUT_TIME = 5000; + private static final int POWER_DOWN_TIMEOUT_TIME = 20; BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, BluetoothAdapter bluetoothAdapter) { @@ -168,6 +174,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { setInitialState(mPowerOff); mPublicState = BluetoothAdapter.STATE_OFF; + mDelayBroadcastStateOff = false; } /** @@ -315,6 +322,10 @@ final class BluetoothAdapterStateMachine extends StateMachine { case SERVICE_RECORD_LOADED: removeMessages(PREPARE_BLUETOOTH_TIMEOUT); transitionTo(mHotOff); + if (mDelayBroadcastStateOff) { + broadcastState(BluetoothAdapter.STATE_OFF); + mDelayBroadcastStateOff = false; + } break; case PREPARE_BLUETOOTH_TIMEOUT: Log.e(TAG, "Bluetooth adapter SDP failed to load"); @@ -373,8 +384,17 @@ final class BluetoothAdapterStateMachine extends StateMachine { case AIRPLANE_MODE_ON: case TURN_COLD: shutoffBluetooth(); + // we cannot go to power off state yet, we need wait for the Bluetooth + // device power off. Unfortunately the stack does not give a event back + // so we wait a little bit here + sendMessageDelayed(POWER_DOWN_TIMEOUT, + POWER_DOWN_TIMEOUT_TIME); + break; + case POWER_DOWN_TIMEOUT: transitionTo(mPowerOff); - broadcastState(BluetoothAdapter.STATE_OFF); + if (!mDelayBroadcastStateOff) { + broadcastState(BluetoothAdapter.STATE_OFF); + } break; case AIRPLANE_MODE_OFF: if (getBluetoothPersistedSetting()) { @@ -402,6 +422,9 @@ final class BluetoothAdapterStateMachine extends StateMachine { recoverStateMachine(TURN_HOT, null); } break; + case TURN_HOT: + deferMessage(message); + break; default: return NOT_HANDLED; } @@ -436,15 +459,17 @@ final class BluetoothAdapterStateMachine extends StateMachine { } break; case POWER_STATE_CHANGED: - removeMessages(POWER_DOWN_TIMEOUT); + removeMessages(TURN_OFF_TIMEOUT); if (!((Boolean) message.obj)) { if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) { transitionTo(mHotOff); - finishSwitchingOff(); + mBluetoothService.finishDisable(); + mBluetoothService.cleanupAfterFinishDisable(); deferMessage(obtainMessage(TURN_COLD)); if (mContext.getResources().getBoolean (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { deferMessage(obtainMessage(TURN_HOT)); + mDelayBroadcastStateOff = true; } } } else { @@ -461,7 +486,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { case ALL_DEVICES_DISCONNECTED: removeMessages(DEVICES_DISCONNECT_TIMEOUT); mBluetoothService.switchConnectable(false); - sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME); + sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); break; case DEVICES_DISCONNECT_TIMEOUT: sendMessage(ALL_DEVICES_DISCONNECTED); @@ -473,7 +498,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { deferMessage(obtainMessage(TURN_HOT)); } break; - case POWER_DOWN_TIMEOUT: + case TURN_OFF_TIMEOUT: transitionTo(mHotOff); finishSwitchingOff(); // reset the hardware for error recovery @@ -536,7 +561,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { DEVICES_DISCONNECT_TIMEOUT_TIME); } else { mBluetoothService.switchConnectable(false); - sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME); + sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); } // we turn all the way to PowerOff with AIRPLANE_MODE_ON @@ -610,13 +635,12 @@ final class BluetoothAdapterStateMachine extends StateMachine { } break; case POWER_STATE_CHANGED: - removeMessages(POWER_DOWN_TIMEOUT); + removeMessages(TURN_OFF_TIMEOUT); if (!((Boolean) message.obj)) { transitionTo(mHotOff); - deferMessage(obtainMessage(TURN_COLD)); - if (mContext.getResources().getBoolean + if (!mContext.getResources().getBoolean (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_HOT)); + deferMessage(obtainMessage(TURN_COLD)); } } else { if (!isTurningOn) { @@ -629,7 +653,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { } } break; - case POWER_DOWN_TIMEOUT: + case TURN_OFF_TIMEOUT: transitionTo(mHotOff); Log.e(TAG, "Power-down timed out, resetting..."); deferMessage(obtainMessage(TURN_COLD)); @@ -676,12 +700,12 @@ final class BluetoothAdapterStateMachine extends StateMachine { perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj); if (mBluetoothService.isApplicationStateChangeTrackerEmpty()) { mBluetoothService.switchConnectable(false); - sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME); + sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); } break; case AIRPLANE_MODE_ON: mBluetoothService.switchConnectable(false); - sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME); + sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME); allProcessesCallback(false); // we turn all the way to PowerOff with AIRPLANE_MODE_ON deferMessage(obtainMessage(AIRPLANE_MODE_ON)); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index a420734..3cf207f 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -526,6 +526,12 @@ public class BluetoothService extends IBluetooth.Stub { return false; } switchConnectable(false); + + // Bluetooth stack needs a small delay here before adding + // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs + try { + Thread.sleep(20); + } catch (InterruptedException e) {} updateSdpRecords(); return true; } @@ -593,6 +599,12 @@ public class BluetoothService extends IBluetooth.Stub { // Add SDP records for profiles maintained by Android userspace addReservedSdpRecords(uuids); + // Bluetooth stack need some a small delay here before adding more + // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs + try { + Thread.sleep(20); + } catch (InterruptedException e) {} + if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { // Enable profiles maintained by Bluez userspace. setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE, diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java new file mode 100644 index 0000000..386c866 --- /dev/null +++ b/core/java/android/view/AccessibilityIterators.java @@ -0,0 +1,352 @@ +/* + * 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.view; + +import android.content.ComponentCallbacks; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; + +import java.text.BreakIterator; +import java.util.Locale; + +/** + * This class contains the implementation of text segment iterators + * for accessibility support. + * + * Note: Such iterators are needed in the view package since we want + * to be able to iterator over content description of any view. + * + * @hide + */ +public final class AccessibilityIterators { + + /** + * @hide + */ + public static interface TextSegmentIterator { + public int[] following(int current); + public int[] preceding(int current); + } + + /** + * @hide + */ + public static abstract class AbstractTextSegmentIterator implements TextSegmentIterator { + protected static final int DONE = -1; + + protected String mText; + + private final int[] mSegment = new int[2]; + + public void initialize(String text) { + mText = text; + } + + protected int[] getRange(int start, int end) { + if (start < 0 || end < 0 || start == end) { + return null; + } + mSegment[0] = start; + mSegment[1] = end; + return mSegment; + } + } + + static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator + implements ComponentCallbacks { + private static CharacterTextSegmentIterator sInstance; + + private final Context mAppContext; + + protected BreakIterator mImpl; + + public static CharacterTextSegmentIterator getInstance(Context context) { + if (sInstance == null) { + sInstance = new CharacterTextSegmentIterator(context); + } + return sInstance; + } + + private CharacterTextSegmentIterator(Context context) { + mAppContext = context.getApplicationContext(); + Locale locale = mAppContext.getResources().getConfiguration().locale; + onLocaleChanged(locale); + ViewRootImpl.addConfigCallback(this); + } + + @Override + public void initialize(String text) { + super.initialize(text); + mImpl.setText(text); + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= textLegth) { + return null; + } + int start = -1; + if (offset < 0) { + offset = 0; + if (mImpl.isBoundary(offset)) { + start = offset; + } + } + if (start < 0) { + start = mImpl.following(offset); + } + if (start < 0) { + return null; + } + final int end = mImpl.following(start); + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + offset = mText.length(); + if (mImpl.isBoundary(offset)) { + end = offset; + } + } + if (end < 0) { + end = mImpl.preceding(offset); + } + if (end < 0) { + return null; + } + final int start = mImpl.preceding(end); + return getRange(start, end); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + Configuration oldConfig = mAppContext.getResources().getConfiguration(); + final int changed = oldConfig.diff(newConfig); + if ((changed & ActivityInfo.CONFIG_LOCALE) != 0) { + Locale locale = newConfig.locale; + onLocaleChanged(locale); + } + } + + @Override + public void onLowMemory() { + /* ignore */ + } + + protected void onLocaleChanged(Locale locale) { + mImpl = BreakIterator.getCharacterInstance(locale); + } + } + + static class WordTextSegmentIterator extends CharacterTextSegmentIterator { + private static WordTextSegmentIterator sInstance; + + public static WordTextSegmentIterator getInstance(Context context) { + if (sInstance == null) { + sInstance = new WordTextSegmentIterator(context); + } + return sInstance; + } + + private WordTextSegmentIterator(Context context) { + super(context); + } + + @Override + protected void onLocaleChanged(Locale locale) { + mImpl = BreakIterator.getWordInstance(locale); + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + int start = -1; + if (offset < 0) { + offset = 0; + if (mImpl.isBoundary(offset) && isLetterOrDigit(offset)) { + start = offset; + } + } + if (start < 0) { + while ((offset = mImpl.following(offset)) != DONE) { + if (isLetterOrDigit(offset)) { + start = offset; + break; + } + } + } + if (start < 0) { + return null; + } + final int end = mImpl.following(start); + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + offset = mText.length(); + if (mImpl.isBoundary(offset) && offset > 0 && isLetterOrDigit(offset - 1)) { + end = offset; + } + } + if (end < 0) { + while ((offset = mImpl.preceding(offset)) != DONE) { + if (offset > 0 && isLetterOrDigit(offset - 1)) { + end = offset; + break; + } + } + } + if (end < 0) { + return null; + } + final int start = mImpl.preceding(end); + return getRange(start, end); + } + + private boolean isLetterOrDigit(int index) { + if (index >= 0 && index < mText.length()) { + final int codePoint = mText.codePointAt(index); + return Character.isLetterOrDigit(codePoint); + } + return false; + } + } + + static class ParagraphTextSegmentIterator extends AbstractTextSegmentIterator { + private static ParagraphTextSegmentIterator sInstance; + + public static ParagraphTextSegmentIterator getInstance() { + if (sInstance == null) { + sInstance = new ParagraphTextSegmentIterator(); + } + return sInstance; + } + + @Override + public int[] following(int offset) { + final int textLength = mText.length(); + if (textLength <= 0) { + return null; + } + if (offset >= textLength) { + return null; + } + int start = -1; + if (offset < 0) { + start = 0; + } else { + for (int i = offset + 1; i < textLength; i++) { + if (mText.charAt(i) == '\n') { + start = i; + break; + } + } + } + while (start < textLength && mText.charAt(start) == '\n') { + start++; + } + if (start < 0) { + return null; + } + int end = start; + for (int i = end + 1; i < textLength; i++) { + end = i; + if (mText.charAt(i) == '\n') { + break; + } + } + while (end < textLength && mText.charAt(end) == '\n') { + end++; + } + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLength = mText.length(); + if (textLength <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int end = -1; + if (offset > mText.length()) { + end = mText.length(); + } else { + if (offset > 0 && mText.charAt(offset - 1) == '\n') { + offset--; + } + for (int i = offset - 1; i >= 0; i--) { + if (i > 0 && mText.charAt(i - 1) == '\n') { + end = i; + break; + } + } + } + if (end <= 0) { + return null; + } + int start = end; + while (start > 0 && mText.charAt(start - 1) == '\n') { + start--; + } + if (start == 0 && mText.charAt(start) == '\n') { + return null; + } + for (int i = start - 1; i >= 0; i--) { + start = i; + if (start > 0 && mText.charAt(i - 1) == '\n') { + break; + } + } + start = Math.max(0, start); + return getRange(start, end); + } + } +} diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index b319cd5..825f351 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -92,6 +92,7 @@ public final class Choreographer { private boolean mFrameScheduled; private boolean mCallbacksRunning; private long mLastFrameTimeNanos; + private long mFrameIntervalNanos; /** * Callback type: Input callback. Runs first. @@ -116,6 +117,8 @@ public final class Choreographer { mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mLastFrameTimeNanos = Long.MIN_VALUE; + mFrameIntervalNanos = (long)(1000000000 / + new Display(Display.DEFAULT_DISPLAY, null).getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { @@ -343,17 +346,37 @@ public final class Choreographer { } void doFrame(long timestampNanos, int frame) { + final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; // no work to do } - mFrameScheduled = false; - mLastFrameTimeNanos = timestampNanos; - } - final long startNanos; - if (DEBUG) { startNanos = System.nanoTime(); + final long jitterNanos = startNanos - timestampNanos; + if (jitterNanos >= mFrameIntervalNanos) { + final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; + if (DEBUG) { + Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + + "which is more than the frame interval of " + + (mFrameIntervalNanos * 0.000001f) + " ms! " + + "Setting frame time to " + (lastFrameOffset * 0.000001f) + + " ms in the past."); + } + timestampNanos = startNanos - lastFrameOffset; + } + + if (timestampNanos < mLastFrameTimeNanos) { + if (DEBUG) { + Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + + "previously skipped frame. Waiting for next vsync"); + } + scheduleVsyncLocked(); + return; + } + + mFrameScheduled = false; + mLastFrameTimeNanos = timestampNanos; } doCallbacks(Choreographer.CALLBACK_INPUT); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5299d58..2972774 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -47,7 +47,6 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.LocaleUtil; @@ -60,6 +59,10 @@ import android.util.Property; import android.util.SparseArray; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; +import android.view.AccessibilityIterators.TextSegmentIterator; +import android.view.AccessibilityIterators.CharacterTextSegmentIterator; +import android.view.AccessibilityIterators.WordTextSegmentIterator; +import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -1524,7 +1527,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED - | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY; /** * Temporary Rect currently for use in setBackground(). This will probably @@ -1590,6 +1594,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mAccessibilityViewId = NO_ID; /** + * @hide + */ + private int mAccessibilityCursorPosition = -1; + + /** * The view's tag. * {@hide} * @@ -4703,8 +4712,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + if (!isAccessibilityFocused()) { + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } else { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } if (isClickable()) { info.addAction(AccessibilityNodeInfo.ACTION_CLICK); @@ -4714,11 +4726,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); } - if (getContentDescription() != null) { + if (mContentDescription != null && mContentDescription.length() > 0) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD); + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH); } } @@ -5949,7 +5962,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal outViews.add(this); } } else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0 - && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mContentDescription)) { + && (searched != null && searched.length() > 0) + && (mContentDescription != null && mContentDescription.length() > 0)) { String searchedLowerCase = searched.toString().toLowerCase(); String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase(); if (contentDescriptionLowerCase.contains(searchedLowerCase)) { @@ -6050,6 +6064,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); notifyAccessibilityStateChanged(); + + // Clear the text navigation state. + setAccessibilityCursorPosition(-1); + // Try to move accessibility focus to the input focus. View rootView = getRootView(); if (rootView != null) { @@ -6447,9 +6465,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * possible accessibility actions look at {@link AccessibilityNodeInfo}. * * @param action The action to perform. + * @param arguments Optional action arguments. * @return Whether the action was performed. */ - public boolean performAccessibilityAction(int action, Bundle args) { + public boolean performAccessibilityAction(int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { @@ -6498,10 +6517,150 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return true; } } break; + case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: { + if (arguments != null) { + final int granularity = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); + return nextAtGranularity(granularity); + } + } break; + case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { + if (arguments != null) { + final int granularity = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT); + return previousAtGranularity(granularity); + } + } break; } return false; } + private boolean nextAtGranularity(int granularity) { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + return false; + } + TextSegmentIterator iterator = getIteratorForGranularity(granularity); + if (iterator == null) { + return false; + } + final int current = getAccessibilityCursorPosition(); + final int[] range = iterator.following(current); + if (range == null) { + setAccessibilityCursorPosition(-1); + return false; + } + final int start = range[0]; + final int end = range[1]; + setAccessibilityCursorPosition(start); + sendViewTextTraversedAtGranularityEvent( + AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, + granularity, start, end); + return true; + } + + private boolean previousAtGranularity(int granularity) { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + return false; + } + TextSegmentIterator iterator = getIteratorForGranularity(granularity); + if (iterator == null) { + return false; + } + final int selectionStart = getAccessibilityCursorPosition(); + final int current = selectionStart >= 0 ? selectionStart : text.length() + 1; + final int[] range = iterator.preceding(current); + if (range == null) { + setAccessibilityCursorPosition(-1); + return false; + } + final int start = range[0]; + final int end = range[1]; + setAccessibilityCursorPosition(end); + sendViewTextTraversedAtGranularityEvent( + AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, + granularity, start, end); + return true; + } + + /** + * Gets the text reported for accessibility purposes. + * + * @return The accessibility text. + * + * @hide + */ + public CharSequence getIterableTextForAccessibility() { + return mContentDescription; + } + + /** + * @hide + */ + public int getAccessibilityCursorPosition() { + return mAccessibilityCursorPosition; + } + + /** + * @hide + */ + public void setAccessibilityCursorPosition(int position) { + mAccessibilityCursorPosition = position; + } + + private void sendViewTextTraversedAtGranularityEvent(int action, int granularity, + int fromIndex, int toIndex) { + if (mParent == null) { + return; + } + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY); + onInitializeAccessibilityEvent(event); + onPopulateAccessibilityEvent(event); + event.setFromIndex(fromIndex); + event.setToIndex(toIndex); + event.setAction(action); + event.setMovementGranularity(granularity); + mParent.requestSendAccessibilityEvent(this, event); + } + + /** + * @hide + */ + public TextSegmentIterator getIteratorForGranularity(int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + CharacterTextSegmentIterator iterator = + CharacterTextSegmentIterator.getInstance(mContext); + iterator.initialize(text.toString()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + WordTextSegmentIterator iterator = + WordTextSegmentIterator.getInstance(mContext); + iterator.initialize(text.toString()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: { + CharSequence text = getIterableTextForAccessibility(); + if (text != null && text.length() > 0) { + ParagraphTextSegmentIterator iterator = + ParagraphTextSegmentIterator.getInstance(); + iterator.initialize(text.toString()); + return iterator; + } + } break; + } + return null; + } + /** * @hide */ diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 1c5d436..6a8a60a 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import android.graphics.Region; import java.util.ArrayList; -import java.util.concurrent.CopyOnWriteArrayList; /** * A view tree observer is used to register listeners that can be notified of global @@ -32,12 +31,12 @@ import java.util.concurrent.CopyOnWriteArrayList; * for more information. */ public final class ViewTreeObserver { - private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; - private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; - private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; - private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; - private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; - private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private CopyOnWriteArray<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; + private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; + private CopyOnWriteArray<OnTouchModeChangeListener> mOnTouchModeChangeListeners; + private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; + private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; + private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -147,7 +146,7 @@ public final class ViewTreeObserver { * windows behind it should be placed. */ public final Rect contentInsets = new Rect(); - + /** * Offsets from the frame of the window at which windows behind it * are visible. @@ -166,13 +165,13 @@ public final class ViewTreeObserver { * can be touched. */ public static final int TOUCHABLE_INSETS_FRAME = 0; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the content insets can be touched. */ public static final int TOUCHABLE_INSETS_CONTENT = 1; - + /** * Option for {@link #setTouchableInsets(int)}: the area inside of * the visible insets can be touched. @@ -195,7 +194,7 @@ public final class ViewTreeObserver { } int mTouchableInsets; - + void reset() { contentInsets.setEmpty(); visibleInsets.setEmpty(); @@ -231,7 +230,7 @@ public final class ViewTreeObserver { mTouchableInsets = other.mTouchableInsets; } } - + /** * Interface definition for a callback to be invoked when layout has * completed and the client can compute its interior insets. @@ -328,7 +327,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalFocusListeners == null) { - mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); + mOnGlobalFocusListeners = new CopyOnWriteArray<OnGlobalFocusChangeListener>(); } mOnGlobalFocusListeners.add(listener); @@ -363,7 +362,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalLayoutListeners == null) { - mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>(); + mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>(); } mOnGlobalLayoutListeners.add(listener); @@ -413,7 +412,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnPreDrawListeners == null) { - mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); + mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); } mOnPreDrawListeners.add(listener); @@ -485,7 +484,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnScrollChangedListeners == null) { - mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>(); + mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>(); } mOnScrollChangedListeners.add(listener); @@ -519,7 +518,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnTouchModeChangeListeners == null) { - mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); + mOnTouchModeChangeListeners = new CopyOnWriteArray<OnTouchModeChangeListener>(); } mOnTouchModeChangeListeners.add(listener); @@ -558,7 +557,7 @@ public final class ViewTreeObserver { if (mOnComputeInternalInsetsListeners == null) { mOnComputeInternalInsetsListeners = - new CopyOnWriteArrayList<OnComputeInternalInsetsListener>(); + new CopyOnWriteArray<OnComputeInternalInsetsListener>(); } mOnComputeInternalInsetsListeners.add(listener); @@ -622,10 +621,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; + final CopyOnWriteArray<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; if (listeners != null && listeners.size() > 0) { - for (OnGlobalFocusChangeListener listener : listeners) { - listener.onGlobalFocusChanged(oldFocus, newFocus); + CopyOnWriteArray.Access<OnGlobalFocusChangeListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onGlobalFocusChanged(oldFocus, newFocus); + } + } finally { + listeners.end(); } } } @@ -640,10 +645,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; + final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; if (listeners != null && listeners.size() > 0) { - for (OnGlobalLayoutListener listener : listeners) { - listener.onGlobalLayout(); + CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onGlobalLayout(); + } + } finally { + listeners.end(); } } } @@ -658,17 +669,17 @@ public final class ViewTreeObserver { */ @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { - // NOTE: we *must* clone the listener list to perform the dispatching. - // The clone is a safe guard against listeners that - // could mutate the list by calling the various add/remove methods. This prevents - // the array from being modified while we process it. boolean cancelDraw = false; - if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) { - final ArrayList<OnPreDrawListener> listeners = - (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone(); - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; ++i) { - cancelDraw |= !(listeners.get(i).onPreDraw()); + final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; + if (listeners != null && listeners.size() > 0) { + CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + cancelDraw |= !(access.get(i).onPreDraw()); + } + } finally { + listeners.end(); } } return cancelDraw; @@ -693,11 +704,17 @@ public final class ViewTreeObserver { * @param inTouchMode True if the touch mode is now enabled, false otherwise. */ final void dispatchOnTouchModeChanged(boolean inTouchMode) { - final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = + final CopyOnWriteArray<OnTouchModeChangeListener> listeners = mOnTouchModeChangeListeners; if (listeners != null && listeners.size() > 0) { - for (OnTouchModeChangeListener listener : listeners) { - listener.onTouchModeChanged(inTouchMode); + CopyOnWriteArray.Access<OnTouchModeChangeListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onTouchModeChanged(inTouchMode); + } + } finally { + listeners.end(); } } } @@ -710,10 +727,16 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; + final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners; if (listeners != null && listeners.size() > 0) { - for (OnScrollChangedListener listener : listeners) { - listener.onScrollChanged(); + CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onScrollChanged(); + } + } finally { + listeners.end(); } } } @@ -722,11 +745,11 @@ public final class ViewTreeObserver { * Returns whether there are listeners for computing internal insets. */ final boolean hasComputeInternalInsetsListeners() { - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; return (listeners != null && listeners.size() > 0); } - + /** * Calls all listeners to compute the current insets. */ @@ -735,12 +758,105 @@ public final class ViewTreeObserver { // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. - final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; if (listeners != null && listeners.size() > 0) { - for (OnComputeInternalInsetsListener listener : listeners) { - listener.onComputeInternalInsets(inoutInfo); + CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start(); + try { + int count = access.size(); + for (int i = 0; i < count; i++) { + access.get(i).onComputeInternalInsets(inoutInfo); + } + } finally { + listeners.end(); + } + } + } + + /** + * Copy on write array. This array is not thread safe, and only one loop can + * iterate over this array at any given time. This class avoids allocations + * until a concurrent modification happens. + * + * Usage: + * + * CopyOnWriteArray.Access<MyData> access = array.start(); + * try { + * for (int i = 0; i < access.size(); i++) { + * MyData d = access.get(i); + * } + * } finally { + * access.end(); + * } + */ + static class CopyOnWriteArray<T> { + private ArrayList<T> mData = new ArrayList<T>(); + private ArrayList<T> mDataCopy; + + private final Access<T> mAccess = new Access<T>(); + + private boolean mStart; + + static class Access<T> { + private ArrayList<T> mData; + private int mSize; + + T get(int index) { + return mData.get(index); } + + int size() { + return mSize; + } + } + + CopyOnWriteArray() { + } + + private ArrayList<T> getArray() { + if (mStart) { + if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData); + return mDataCopy; + } + return mData; + } + + Access<T> start() { + if (mStart) throw new IllegalStateException("Iteration already started"); + mStart = true; + mDataCopy = null; + mAccess.mData = mData; + mAccess.mSize = mData.size(); + return mAccess; + } + + void end() { + if (!mStart) throw new IllegalStateException("Iteration not started"); + mStart = false; + if (mDataCopy != null) { + mData = mDataCopy; + } + mDataCopy = null; + } + + int size() { + return getArray().size(); + } + + void add(T item) { + getArray().add(item); + } + + void addAll(CopyOnWriteArray<T> array) { + getArray().addAll(array.mData); + } + + void remove(T item) { + getArray().remove(item); + } + + void clear() { + getArray().clear(); } } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index f70ffa9..1a2a194 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -236,12 +236,19 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the current text at the movement granularity.</li> + * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text + * was traversed.</li> + * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #getFromIndex()} - The start of the next/previous text at the specified granularity + * - inclusive.</li> + * <li>{@link #getToIndex()} - The end of the next/previous text at the specified granularity + * - exclusive.</li> * <li>{@link #isPassword()} - Whether the source is password.</li> * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * <li>{@link #getContentDescription()} - The content description of the source.</li> * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text * was traversed.</li> + * <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li> * </ul> * </p> * <p> @@ -635,6 +642,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private CharSequence mPackageName; private long mEventTime; int mMovementGranularity; + int mAction; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); @@ -653,6 +661,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.init(event); mEventType = event.mEventType; mMovementGranularity = event.mMovementGranularity; + mAction = event.mAction; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -791,6 +800,27 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Sets the performed action that triggered this event. + * + * @param action The action. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setAction(int action) { + enforceNotSealed(); + mAction = action; + } + + /** + * Gets the performed action that triggered this event. + * + * @return The action. + */ + public int getAction() { + return mAction; + } + + /** * Returns a cached instance if such is available or a new one is * instantiated with its type property set. * @@ -879,6 +909,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.clear(); mEventType = 0; mMovementGranularity = 0; + mAction = 0; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -896,6 +927,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mSealed = (parcel.readInt() == 1); mEventType = parcel.readInt(); mMovementGranularity = parcel.readInt(); + mAction = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -947,6 +979,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); parcel.writeInt(mMovementGranularity); + parcel.writeInt(mAction); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1004,6 +1037,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; EventTime: ").append(mEventTime); builder.append("; PackageName: ").append(mPackageName); builder.append("; MovementGranularity: ").append(mMovementGranularity); + builder.append("; Action: ").append(mAction); builder.append(super.toString()); if (DEBUG) { builder.append("\n"); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index c0696a9..fef24e2 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -102,12 +102,12 @@ public class AccessibilityNodeInfo implements Parcelable { public static final int ACTION_CLEAR_SELECTION = 0x00000008; /** - * Action that long clicks on the node info. + * Action that clicks on the node info. */ public static final int ACTION_CLICK = 0x00000010; /** - * Action that clicks on the node. + * Action that long clicks on the node. */ public static final int ACTION_LONG_CLICK = 0x00000020; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 449870e..a3b613b 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3985,7 +3985,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } class PositionScroller implements Runnable { - private static final int SCROLL_DURATION = 400; + private static final int SCROLL_DURATION = 200; private static final int MOVE_DOWN_POS = 1; private static final int MOVE_UP_POS = 2; @@ -4006,21 +4006,35 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); } - void start(int position) { + void start(final int position) { stop(); + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + if (mDataChanged) { + // But we might have something in a minute. + post(new Runnable() { + @Override public void run() { + start(position); + } + }); + } + return; + } + final int firstPos = mFirstPosition; - final int lastPos = firstPos + getChildCount() - 1; + final int lastPos = firstPos + childCount - 1; int viewTravelCount; - if (position <= firstPos) { + if (position < firstPos) { viewTravelCount = firstPos - position + 1; mMode = MOVE_UP_POS; - } else if (position >= lastPos) { + } else if (position > lastPos) { viewTravelCount = position - lastPos + 1; mMode = MOVE_DOWN_POS; } else { - // Already on screen, nothing to do + scrollToVisible(position, INVALID_POSITION, SCROLL_DURATION); return; } @@ -4036,7 +4050,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te postOnAnimation(this); } - void start(int position, int boundPosition) { + void start(final int position, final int boundPosition) { stop(); if (boundPosition == INVALID_POSITION) { @@ -4044,11 +4058,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return; } + final int childCount = getChildCount(); + if (childCount == 0) { + // Can't scroll without children. + if (mDataChanged) { + // But we might have something in a minute. + post(new Runnable() { + @Override public void run() { + start(position, boundPosition); + } + }); + } + return; + } + final int firstPos = mFirstPosition; - final int lastPos = firstPos + getChildCount() - 1; + final int lastPos = firstPos + childCount - 1; int viewTravelCount; - if (position <= firstPos) { + if (position < firstPos) { final int boundPosFromLast = lastPos - boundPosition; if (boundPosFromLast < 1) { // Moving would shift our bound position off the screen. Abort. @@ -4064,7 +4092,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te viewTravelCount = posTravel; mMode = MOVE_UP_POS; } - } else if (position >= lastPos) { + } else if (position > lastPos) { final int boundPosFromFirst = boundPosition - firstPos; if (boundPosFromFirst < 1) { // Moving would shift our bound position off the screen. Abort. @@ -4081,7 +4109,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mMode = MOVE_DOWN_POS; } } else { - // Already on screen, nothing to do + scrollToVisible(position, boundPosition, SCROLL_DURATION); return; } @@ -4137,6 +4165,60 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te postOnAnimation(this); } + /** + * Scroll such that targetPos is in the visible padded region without scrolling + * boundPos out of view. Assumes targetPos is onscreen. + */ + void scrollToVisible(int targetPos, int boundPos, int duration) { + final int firstPos = mFirstPosition; + final int childCount = getChildCount(); + final int lastPos = firstPos + childCount - 1; + final int paddedTop = mListPadding.top; + final int paddedBottom = getHeight() - mListPadding.bottom; + + if (targetPos < firstPos || targetPos > lastPos) { + Log.w(TAG, "scrollToVisible called with targetPos " + targetPos + + " not visible [" + firstPos + ", " + lastPos + "]"); + } + if (boundPos < firstPos || boundPos > lastPos) { + // boundPos doesn't matter, it's already offscreen. + boundPos = INVALID_POSITION; + } + + final View targetChild = getChildAt(targetPos - firstPos); + final int targetTop = targetChild.getTop(); + final int targetBottom = targetChild.getBottom(); + int scrollBy = 0; + + if (targetBottom > paddedBottom) { + scrollBy = targetBottom - paddedBottom; + } + if (targetTop < paddedTop) { + scrollBy = targetTop - paddedTop; + } + + if (scrollBy == 0) { + return; + } + + if (boundPos >= 0) { + final View boundChild = getChildAt(boundPos - firstPos); + final int boundTop = boundChild.getTop(); + final int boundBottom = boundChild.getBottom(); + final int absScroll = Math.abs(scrollBy); + + if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) { + // Don't scroll the bound view off the bottom of the screen. + scrollBy = Math.max(0, boundBottom - paddedBottom); + } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) { + // Don't scroll the bound view off the top of the screen. + scrollBy = Math.min(0, boundTop - paddedTop); + } + } + + smoothScrollBy(scrollBy, duration); + } + void stop() { removeCallbacks(this); } @@ -6217,6 +6299,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te removeDetachedView(scrapPile.remove(size--), false); } } + + if (mTransientStateViews != null) { + for (int i = 0; i < mTransientStateViews.size(); i++) { + final View v = mTransientStateViews.valueAt(i); + if (!v.hasTransientState()) { + mTransientStateViews.removeAt(i); + i--; + } + } + } } /** diff --git a/core/java/android/widget/AccessibilityIterators.java b/core/java/android/widget/AccessibilityIterators.java new file mode 100644 index 0000000..e800e8d --- /dev/null +++ b/core/java/android/widget/AccessibilityIterators.java @@ -0,0 +1,219 @@ +/* + * 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.widget; + +import android.graphics.Rect; +import android.text.Layout; +import android.text.Spannable; +import android.view.AccessibilityIterators.AbstractTextSegmentIterator; + +/** + * This class contains the implementation of text segment iterators + * for accessibility support. + */ +final class AccessibilityIterators { + + static class LineTextSegmentIterator extends AbstractTextSegmentIterator { + private static LineTextSegmentIterator sLineInstance; + + protected static final int DIRECTION_START = -1; + protected static final int DIRECTION_END = 1; + + protected Layout mLayout; + + public static LineTextSegmentIterator getInstance() { + if (sLineInstance == null) { + sLineInstance = new LineTextSegmentIterator(); + } + return sLineInstance; + } + + public void initialize(Spannable text, Layout layout) { + mText = text.toString(); + mLayout = layout; + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + int nextLine = -1; + if (offset < 0) { + nextLine = mLayout.getLineForOffset(0); + } else { + final int currentLine = mLayout.getLineForOffset(offset); + if (currentLine < mLayout.getLineCount() - 1) { + nextLine = currentLine + 1; + } + } + if (nextLine < 0) { + return null; + } + final int start = getLineEdgeIndex(nextLine, DIRECTION_START); + final int end = getLineEdgeIndex(nextLine, DIRECTION_END) + 1; + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + int previousLine = -1; + if (offset > mText.length()) { + previousLine = mLayout.getLineForOffset(mText.length()); + } else { + final int currentLine = mLayout.getLineForOffset(offset - 1); + if (currentLine > 0) { + previousLine = currentLine - 1; + } + } + if (previousLine < 0) { + return null; + } + final int start = getLineEdgeIndex(previousLine, DIRECTION_START); + final int end = getLineEdgeIndex(previousLine, DIRECTION_END) + 1; + return getRange(start, end); + } + + protected int getLineEdgeIndex(int lineNumber, int direction) { + final int paragraphDirection = mLayout.getParagraphDirection(lineNumber); + if (direction * paragraphDirection < 0) { + return mLayout.getLineStart(lineNumber); + } else { + return mLayout.getLineEnd(lineNumber) - 1; + } + } + } + + static class PageTextSegmentIterator extends LineTextSegmentIterator { + private static PageTextSegmentIterator sPageInstance; + + private TextView mView; + + private final Rect mTempRect = new Rect(); + + public static PageTextSegmentIterator getInstance() { + if (sPageInstance == null) { + sPageInstance = new PageTextSegmentIterator(); + } + return sPageInstance; + } + + public void initialize(TextView view) { + super.initialize((Spannable) view.getIterableTextForAccessibility(), view.getLayout()); + mView = view; + } + + @Override + public int[] following(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset >= mText.length()) { + return null; + } + if (!mView.getGlobalVisibleRect(mTempRect)) { + return null; + } + + final int currentLine = mLayout.getLineForOffset(offset); + final int currentLineTop = mLayout.getLineTop(currentLine); + final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop() + - mView.getTotalPaddingBottom(); + + final int nextPageStartLine; + final int nextPageEndLine; + if (offset < 0) { + nextPageStartLine = currentLine; + final int nextPageEndY = currentLineTop + pageHeight; + nextPageEndLine = mLayout.getLineForVertical(nextPageEndY); + } else { + final int nextPageStartY = currentLineTop + pageHeight; + nextPageStartLine = mLayout.getLineForVertical(nextPageStartY) + 1; + if (mLayout.getLineTop(nextPageStartLine) <= nextPageStartY) { + return null; + } + final int nextPageEndY = nextPageStartY + pageHeight; + nextPageEndLine = mLayout.getLineForVertical(nextPageEndY); + } + + final int start = getLineEdgeIndex(nextPageStartLine, DIRECTION_START); + final int end = getLineEdgeIndex(nextPageEndLine, DIRECTION_END) + 1; + + return getRange(start, end); + } + + @Override + public int[] preceding(int offset) { + final int textLegth = mText.length(); + if (textLegth <= 0) { + return null; + } + if (offset <= 0) { + return null; + } + if (!mView.getGlobalVisibleRect(mTempRect)) { + return null; + } + + final int currentLine = mLayout.getLineForOffset(offset); + final int currentLineTop = mLayout.getLineTop(currentLine); + final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop() + - mView.getTotalPaddingBottom(); + + final int previousPageStartLine; + final int previousPageEndLine; + if (offset > mText.length()) { + final int prevousPageStartY = mLayout.getHeight() - pageHeight; + if (prevousPageStartY < 0) { + return null; + } + previousPageStartLine = mLayout.getLineForVertical(prevousPageStartY); + previousPageEndLine = mLayout.getLineCount() - 1; + } else { + final int prevousPageStartY; + if (offset == mText.length()) { + prevousPageStartY = mLayout.getHeight() - 2 * pageHeight; + } else { + prevousPageStartY = currentLineTop - 2 * pageHeight; + } + if (prevousPageStartY < 0) { + return null; + } + previousPageStartLine = mLayout.getLineForVertical(prevousPageStartY); + final int previousPageEndY = prevousPageStartY + pageHeight; + previousPageEndLine = mLayout.getLineForVertical(previousPageEndY) - 1; + } + + final int start = getLineEdgeIndex(previousPageStartLine, DIRECTION_START); + final int end = getLineEdgeIndex(previousPageEndLine, DIRECTION_END) + 1; + + return getRange(start, end); + } + } +} diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java index fba8d3a..c6104bc 100644 --- a/core/java/android/widget/ActivityChooserModel.java +++ b/core/java/android/widget/ActivityChooserModel.java @@ -23,7 +23,6 @@ import android.content.pm.ResolveInfo; import android.database.DataSetObservable; import android.database.DataSetObserver; import android.os.AsyncTask; -import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.util.Xml; @@ -42,10 +41,8 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** * <p> @@ -239,7 +236,7 @@ public class ActivityChooserModel extends DataSetObservable { /** * List of activities that can handle the current intent. */ - private final List<ActivityResolveInfo> mActivites = new ArrayList<ActivityResolveInfo>(); + private final List<ActivityResolveInfo> mActivities = new ArrayList<ActivityResolveInfo>(); /** * List with historical choice records. @@ -278,18 +275,18 @@ public class ActivityChooserModel extends DataSetObservable { /** * Flag whether choice history can be read. In general many clients can - * share the same data model and {@link #readHistoricalData()} may be called + * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called * by arbitrary of them any number of times. Therefore, this class guarantees * that the very first read succeeds and subsequent reads can be performed - * only after a call to {@link #persistHistoricalData()} followed by change + * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change * of the share records. */ private boolean mCanReadHistoricalData = true; /** * Flag whether the choice history was read. This is used to enforce that - * before calling {@link #persistHistoricalData()} a call to - * {@link #persistHistoricalData()} has been made. This aims to avoid a + * before calling {@link #persistHistoricalDataIfNeeded()} a call to + * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a * scenario in which a choice history file exits, it is not read yet and * it is overwritten. Note that always all historical records are read in * full and the file is rewritten. This is necessary since we need to @@ -299,16 +296,16 @@ public class ActivityChooserModel extends DataSetObservable { /** * Flag whether the choice records have changed. In general many clients can - * share the same data model and {@link #persistHistoricalData()} may be called + * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called * by arbitrary of them any number of times. Therefore, this class guarantees * that choice history will be persisted only if it has changed. */ private boolean mHistoricalRecordsChanged = true; /** - * Hander for scheduling work on client tread. + * Flag whether to reload the activities for the current intent. */ - private final Handler mHandler = new Handler(); + private boolean mReloadActivities = false; /** * Policy for controlling how the model handles chosen activities. @@ -346,7 +343,6 @@ public class ActivityChooserModel extends DataSetObservable { dataModel = new ActivityChooserModel(context, historyFileName); sDataModelRegistry.put(historyFileName, dataModel); } - dataModel.readHistoricalData(); return dataModel; } } @@ -383,7 +379,8 @@ public class ActivityChooserModel extends DataSetObservable { return; } mIntent = intent; - loadActivitiesLocked(); + mReloadActivities = true; + ensureConsistentState(); } } @@ -407,7 +404,8 @@ public class ActivityChooserModel extends DataSetObservable { */ public int getActivityCount() { synchronized (mInstanceLock) { - return mActivites.size(); + ensureConsistentState(); + return mActivities.size(); } } @@ -421,7 +419,8 @@ public class ActivityChooserModel extends DataSetObservable { */ public ResolveInfo getActivity(int index) { synchronized (mInstanceLock) { - return mActivites.get(index).resolveInfo; + ensureConsistentState(); + return mActivities.get(index).resolveInfo; } } @@ -433,15 +432,18 @@ public class ActivityChooserModel extends DataSetObservable { * @return The index if found, -1 otherwise. */ public int getActivityIndex(ResolveInfo activity) { - List<ActivityResolveInfo> activities = mActivites; - final int activityCount = activities.size(); - for (int i = 0; i < activityCount; i++) { - ActivityResolveInfo currentActivity = activities.get(i); - if (currentActivity.resolveInfo == activity) { - return i; + synchronized (mInstanceLock) { + ensureConsistentState(); + List<ActivityResolveInfo> activities = mActivities; + final int activityCount = activities.size(); + for (int i = 0; i < activityCount; i++) { + ActivityResolveInfo currentActivity = activities.get(i); + if (currentActivity.resolveInfo == activity) { + return i; + } } + return INVALID_INDEX; } - return INVALID_INDEX; } /** @@ -462,30 +464,34 @@ public class ActivityChooserModel extends DataSetObservable { * @see OnChooseActivityListener */ public Intent chooseActivity(int index) { - ActivityResolveInfo chosenActivity = mActivites.get(index); + synchronized (mInstanceLock) { + ensureConsistentState(); - ComponentName chosenName = new ComponentName( - chosenActivity.resolveInfo.activityInfo.packageName, - chosenActivity.resolveInfo.activityInfo.name); + ActivityResolveInfo chosenActivity = mActivities.get(index); - Intent choiceIntent = new Intent(mIntent); - choiceIntent.setComponent(chosenName); + ComponentName chosenName = new ComponentName( + chosenActivity.resolveInfo.activityInfo.packageName, + chosenActivity.resolveInfo.activityInfo.name); - if (mActivityChoserModelPolicy != null) { - // Do not allow the policy to change the intent. - Intent choiceIntentCopy = new Intent(choiceIntent); - final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, - choiceIntentCopy); - if (handled) { - return null; + Intent choiceIntent = new Intent(mIntent); + choiceIntent.setComponent(chosenName); + + if (mActivityChoserModelPolicy != null) { + // Do not allow the policy to change the intent. + Intent choiceIntentCopy = new Intent(choiceIntent); + final boolean handled = mActivityChoserModelPolicy.onChooseActivity(this, + choiceIntentCopy); + if (handled) { + return null; + } } - } - HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, - System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); - addHisoricalRecord(historicalRecord); + HistoricalRecord historicalRecord = new HistoricalRecord(chosenName, + System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT); + addHisoricalRecord(historicalRecord); - return choiceIntent; + return choiceIntent; + } } /** @@ -494,7 +500,9 @@ public class ActivityChooserModel extends DataSetObservable { * @param listener The listener. */ public void setOnChooseActivityListener(OnChooseActivityListener listener) { - mActivityChoserModelPolicy = listener; + synchronized (mInstanceLock) { + mActivityChoserModelPolicy = listener; + } } /** @@ -508,8 +516,9 @@ public class ActivityChooserModel extends DataSetObservable { */ public ResolveInfo getDefaultActivity() { synchronized (mInstanceLock) { - if (!mActivites.isEmpty()) { - return mActivites.get(0).resolveInfo; + ensureConsistentState(); + if (!mActivities.isEmpty()) { + return mActivities.get(0).resolveInfo; } } return null; @@ -526,72 +535,50 @@ public class ActivityChooserModel extends DataSetObservable { * @param index The index of the activity to set as default. */ public void setDefaultActivity(int index) { - ActivityResolveInfo newDefaultActivity = mActivites.get(index); - ActivityResolveInfo oldDefaultActivity = mActivites.get(0); - - final float weight; - if (oldDefaultActivity != null) { - // Add a record with weight enough to boost the chosen at the top. - weight = oldDefaultActivity.weight - newDefaultActivity.weight - + DEFAULT_ACTIVITY_INFLATION; - } else { - weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; - } - - ComponentName defaultName = new ComponentName( - newDefaultActivity.resolveInfo.activityInfo.packageName, - newDefaultActivity.resolveInfo.activityInfo.name); - HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, - System.currentTimeMillis(), weight); - addHisoricalRecord(historicalRecord); - } - - /** - * Reads the history data from the backing file if the latter - * was provided. Calling this method more than once before a call - * to {@link #persistHistoricalData()} has been made has no effect. - * <p> - * <strong>Note:</strong> Historical data is read asynchronously and - * as soon as the reading is completed any registered - * {@link DataSetObserver}s will be notified. Also no historical - * data is read until this method is invoked. - * <p> - */ - private void readHistoricalData() { synchronized (mInstanceLock) { - if (!mCanReadHistoricalData || !mHistoricalRecordsChanged) { - return; - } - mCanReadHistoricalData = false; - mReadShareHistoryCalled = true; - if (!TextUtils.isEmpty(mHistoryFileName)) { - AsyncTask.SERIAL_EXECUTOR.execute(new HistoryLoader()); + ensureConsistentState(); + + ActivityResolveInfo newDefaultActivity = mActivities.get(index); + ActivityResolveInfo oldDefaultActivity = mActivities.get(0); + + final float weight; + if (oldDefaultActivity != null) { + // Add a record with weight enough to boost the chosen at the top. + weight = oldDefaultActivity.weight - newDefaultActivity.weight + + DEFAULT_ACTIVITY_INFLATION; + } else { + weight = DEFAULT_HISTORICAL_RECORD_WEIGHT; } + + ComponentName defaultName = new ComponentName( + newDefaultActivity.resolveInfo.activityInfo.packageName, + newDefaultActivity.resolveInfo.activityInfo.name); + HistoricalRecord historicalRecord = new HistoricalRecord(defaultName, + System.currentTimeMillis(), weight); + addHisoricalRecord(historicalRecord); } } /** * Persists the history data to the backing file if the latter - * was provided. Calling this method before a call to {@link #readHistoricalData()} + * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()} * throws an exception. Calling this method more than one without choosing an * activity has not effect. * * @throws IllegalStateException If this method is called before a call to - * {@link #readHistoricalData()}. + * {@link #readHistoricalDataIfNeeded()}. */ - private void persistHistoricalData() { - synchronized (mInstanceLock) { - if (!mReadShareHistoryCalled) { - throw new IllegalStateException("No preceding call to #readHistoricalData"); - } - if (!mHistoricalRecordsChanged) { - return; - } - mHistoricalRecordsChanged = false; - mCanReadHistoricalData = true; - if (!TextUtils.isEmpty(mHistoryFileName)) { - AsyncTask.SERIAL_EXECUTOR.execute(new HistoryPersister()); - } + private void persistHistoricalDataIfNeeded() { + if (!mReadShareHistoryCalled) { + throw new IllegalStateException("No preceding call to #readHistoricalData"); + } + if (!mHistoricalRecordsChanged) { + return; + } + mHistoricalRecordsChanged = false; + if (!TextUtils.isEmpty(mHistoryFileName)) { + new PersistHistoryAsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName); } } @@ -608,21 +595,7 @@ public class ActivityChooserModel extends DataSetObservable { return; } mActivitySorter = activitySorter; - sortActivities(); - } - } - - /** - * Sorts the activities based on history and an intent. If - * a sorter is not specified this a default implementation is used. - * - * @see #setActivitySorter(ActivitySorter) - */ - private void sortActivities() { - synchronized (mInstanceLock) { - if (mActivitySorter != null && !mActivites.isEmpty()) { - mActivitySorter.sort(mIntent, mActivites, - Collections.unmodifiableList(mHistoricalRecords)); + if (sortActivitiesIfNeeded()) { notifyChanged(); } } @@ -647,8 +620,10 @@ public class ActivityChooserModel extends DataSetObservable { return; } mHistoryMaxSize = historyMaxSize; - pruneExcessiveHistoricalRecordsLocked(); - sortActivities(); + pruneExcessiveHistoricalRecordsIfNeeded(); + if (sortActivitiesIfNeeded()) { + notifyChanged(); + } } } @@ -670,6 +645,7 @@ public class ActivityChooserModel extends DataSetObservable { */ public int getHistorySize() { synchronized (mInstanceLock) { + ensureConsistentState(); return mHistoricalRecords.size(); } } @@ -681,82 +657,110 @@ public class ActivityChooserModel extends DataSetObservable { } /** - * Adds a historical record. - * - * @param historicalRecord The record to add. - * @return True if the record was added. + * Ensures the model is in a consistent state which is the + * activities for the current intent have been loaded, the + * most recent history has been read, and the activities + * are sorted. */ - private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { - synchronized (mInstanceLock) { - final boolean added = mHistoricalRecords.add(historicalRecord); - if (added) { - mHistoricalRecordsChanged = true; - pruneExcessiveHistoricalRecordsLocked(); - persistHistoricalData(); - sortActivities(); - } - return added; + private void ensureConsistentState() { + boolean stateChanged = loadActivitiesIfNeeded(); + stateChanged |= readHistoricalDataIfNeeded(); + pruneExcessiveHistoricalRecordsIfNeeded(); + if (stateChanged) { + sortActivitiesIfNeeded(); + notifyChanged(); } } /** - * Prunes older excessive records to guarantee {@link #mHistoryMaxSize}. + * Sorts the activities if necessary which is if there is a + * sorter, there are some activities to sort, and there is some + * historical data. + * + * @return Whether sorting was performed. */ - private void pruneExcessiveHistoricalRecordsLocked() { - List<HistoricalRecord> choiceRecords = mHistoricalRecords; - final int pruneCount = choiceRecords.size() - mHistoryMaxSize; - if (pruneCount <= 0) { - return; - } - mHistoricalRecordsChanged = true; - for (int i = 0; i < pruneCount; i++) { - HistoricalRecord prunedRecord = choiceRecords.remove(0); - if (DEBUG) { - Log.i(LOG_TAG, "Pruned: " + prunedRecord); - } + private boolean sortActivitiesIfNeeded() { + if (mActivitySorter != null && mIntent != null + && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) { + mActivitySorter.sort(mIntent, mActivities, + Collections.unmodifiableList(mHistoricalRecords)); + return true; } + return false; } /** - * Loads the activities. + * Loads the activities for the current intent if needed which is + * if they are not already loaded for the current intent. + * + * @return Whether loading was performed. */ - private void loadActivitiesLocked() { - mActivites.clear(); - if (mIntent != null) { - List<ResolveInfo> resolveInfos = - mContext.getPackageManager().queryIntentActivities(mIntent, 0); + private boolean loadActivitiesIfNeeded() { + if (mReloadActivities && mIntent != null) { + mReloadActivities = false; + mActivities.clear(); + List<ResolveInfo> resolveInfos = mContext.getPackageManager() + .queryIntentActivities(mIntent, 0); final int resolveInfoCount = resolveInfos.size(); for (int i = 0; i < resolveInfoCount; i++) { ResolveInfo resolveInfo = resolveInfos.get(i); - mActivites.add(new ActivityResolveInfo(resolveInfo)); + mActivities.add(new ActivityResolveInfo(resolveInfo)); } - sortActivities(); - } else { - notifyChanged(); + return true; } + return false; } /** - * Prunes historical records for a package that goes away. + * Reads the historical data if necessary which is it has + * changed, there is a history file, and there is not persist + * in progress. * - * @param packageName The name of the package that goes away. + * @return Whether reading was performed. */ - private void pruneHistoricalRecordsForPackageLocked(String packageName) { - boolean recordsRemoved = false; - - List<HistoricalRecord> historicalRecords = mHistoricalRecords; - for (int i = 0; i < historicalRecords.size(); i++) { - HistoricalRecord historicalRecord = historicalRecords.get(i); - String recordPackageName = historicalRecord.activity.getPackageName(); - if (recordPackageName.equals(packageName)) { - historicalRecords.remove(historicalRecord); - recordsRemoved = true; - } + private boolean readHistoricalDataIfNeeded() { + if (mCanReadHistoricalData && mHistoricalRecordsChanged && + !TextUtils.isEmpty(mHistoryFileName)) { + mCanReadHistoricalData = false; + mReadShareHistoryCalled = true; + readHistoricalDataImpl(); + return true; } + return false; + } - if (recordsRemoved) { + /** + * Adds a historical record. + * + * @param historicalRecord The record to add. + * @return True if the record was added. + */ + private boolean addHisoricalRecord(HistoricalRecord historicalRecord) { + final boolean added = mHistoricalRecords.add(historicalRecord); + if (added) { mHistoricalRecordsChanged = true; - sortActivities(); + pruneExcessiveHistoricalRecordsIfNeeded(); + persistHistoricalDataIfNeeded(); + sortActivitiesIfNeeded(); + notifyChanged(); + } + return added; + } + + /** + * Prunes older excessive records to guarantee maxHistorySize. + */ + private void pruneExcessiveHistoricalRecordsIfNeeded() { + final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize; + if (pruneCount <= 0) { + return; + } + mHistoricalRecordsChanged = true; + for (int i = 0; i < pruneCount; i++) { + HistoricalRecord prunedRecord = mHistoricalRecords.remove(0); + if (DEBUG) { + Log.i(LOG_TAG, "Pruned: " + prunedRecord); + } } } @@ -974,112 +978,72 @@ public class ActivityChooserModel extends DataSetObservable { /** * Command for reading the historical records from a file off the UI thread. */ - private final class HistoryLoader implements Runnable { - - public void run() { - FileInputStream fis = null; - try { - fis = mContext.openFileInput(mHistoryFileName); - } catch (FileNotFoundException fnfe) { - if (DEBUG) { - Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); - } - return; + private void readHistoricalDataImpl() { + FileInputStream fis = null; + try { + fis = mContext.openFileInput(mHistoryFileName); + } catch (FileNotFoundException fnfe) { + if (DEBUG) { + Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName); } - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - - int type = XmlPullParser.START_DOCUMENT; - while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { - type = parser.next(); - } - - if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { - throw new XmlPullParserException("Share records file does not start with " - + TAG_HISTORICAL_RECORDS + " tag."); - } - - List<HistoricalRecord> readRecords = new ArrayList<HistoricalRecord>(); + return; + } + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); - while (true) { - type = parser.next(); - if (type == XmlPullParser.END_DOCUMENT) { - break; - } - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String nodeName = parser.getName(); - if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { - throw new XmlPullParserException("Share records file not well-formed."); - } + int type = XmlPullParser.START_DOCUMENT; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); + } - String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); - final long time = - Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); - final float weight = - Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) { + throw new XmlPullParserException("Share records file does not start with " + + TAG_HISTORICAL_RECORDS + " tag."); + } - HistoricalRecord readRecord = new HistoricalRecord(activity, time, - weight); - readRecords.add(readRecord); + List<HistoricalRecord> historicalRecords = mHistoricalRecords; + historicalRecords.clear(); - if (DEBUG) { - Log.i(LOG_TAG, "Read " + readRecord.toString()); - } + while (true) { + type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT) { + break; } - - if (DEBUG) { - Log.i(LOG_TAG, "Read " + readRecords.size() + " historical records."); + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String nodeName = parser.getName(); + if (!TAG_HISTORICAL_RECORD.equals(nodeName)) { + throw new XmlPullParserException("Share records file not well-formed."); } - synchronized (mInstanceLock) { - Set<HistoricalRecord> uniqueShareRecords = - new LinkedHashSet<HistoricalRecord>(readRecords); - - // Make sure no duplicates. Example: Read a file with - // one record, add one record, persist the two records, - // add a record, read the persisted records - the - // read two records should not be added again. - List<HistoricalRecord> historicalRecords = mHistoricalRecords; - final int historicalRecordsCount = historicalRecords.size(); - for (int i = historicalRecordsCount - 1; i >= 0; i--) { - HistoricalRecord historicalRecord = historicalRecords.get(i); - uniqueShareRecords.add(historicalRecord); - } - - if (historicalRecords.size() == uniqueShareRecords.size()) { - return; - } + String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY); + final long time = + Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME)); + final float weight = + Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT)); + HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight); + historicalRecords.add(readRecord); - // Make sure the oldest records go to the end. - historicalRecords.clear(); - historicalRecords.addAll(uniqueShareRecords); - - mHistoricalRecordsChanged = true; - - // Do this on the client thread since the client may be on the UI - // thread, wait for data changes which happen during sorting, and - // perform UI modification based on the data change. - mHandler.post(new Runnable() { - public void run() { - pruneExcessiveHistoricalRecordsLocked(); - sortActivities(); - } - }); + if (DEBUG) { + Log.i(LOG_TAG, "Read " + readRecord.toString()); } - } catch (XmlPullParserException xppe) { - Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); - } catch (IOException ioe) { - Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException ioe) { - /* ignore */ - } + } + + if (DEBUG) { + Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records."); + } + } catch (XmlPullParserException xppe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, xppe); + } catch (IOException ioe) { + Log.e(LOG_TAG, "Error reading historical recrod file: " + mHistoryFileName, ioe); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ioe) { + /* ignore */ } } } @@ -1088,21 +1052,21 @@ public class ActivityChooserModel extends DataSetObservable { /** * Command for persisting the historical records to a file off the UI thread. */ - private final class HistoryPersister implements Runnable { + private final class PersistHistoryAsyncTask extends AsyncTask<Object, Void, Void> { - public void run() { - FileOutputStream fos = null; - List<HistoricalRecord> records = null; + @Override + @SuppressWarnings("unchecked") + public Void doInBackground(Object... args) { + List<HistoricalRecord> historicalRecords = (List<HistoricalRecord>) args[0]; + String hostoryFileName = (String) args[1]; - synchronized (mInstanceLock) { - records = new ArrayList<HistoricalRecord>(mHistoricalRecords); - } + FileOutputStream fos = null; try { - fos = mContext.openFileOutput(mHistoryFileName, Context.MODE_PRIVATE); + fos = mContext.openFileOutput(hostoryFileName, Context.MODE_PRIVATE); } catch (FileNotFoundException fnfe) { - Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, fnfe); - return; + Log.e(LOG_TAG, "Error writing historical recrod file: " + hostoryFileName, fnfe); + return null; } XmlSerializer serializer = Xml.newSerializer(); @@ -1112,11 +1076,12 @@ public class ActivityChooserModel extends DataSetObservable { serializer.startDocument("UTF-8", true); serializer.startTag(null, TAG_HISTORICAL_RECORDS); - final int recordCount = records.size(); + final int recordCount = historicalRecords.size(); for (int i = 0; i < recordCount; i++) { - HistoricalRecord record = records.remove(0); + HistoricalRecord record = historicalRecords.remove(0); serializer.startTag(null, TAG_HISTORICAL_RECORD); - serializer.attribute(null, ATTRIBUTE_ACTIVITY, record.activity.flattenToString()); + serializer.attribute(null, ATTRIBUTE_ACTIVITY, + record.activity.flattenToString()); serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time)); serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight)); serializer.endTag(null, TAG_HISTORICAL_RECORD); @@ -1138,6 +1103,7 @@ public class ActivityChooserModel extends DataSetObservable { } catch (IOException ioe) { Log.e(LOG_TAG, "Error writing historical recrod file: " + mHistoryFileName, ioe); } finally { + mCanReadHistoricalData = true; if (fos != null) { try { fos.close(); @@ -1146,6 +1112,7 @@ public class ActivityChooserModel extends DataSetObservable { } } } + return null; } } @@ -1155,33 +1122,8 @@ public class ActivityChooserModel extends DataSetObservable { private final class DataModelPackageMonitor extends PackageMonitor { @Override - public void onPackageAdded(String packageName, int uid) { - synchronized (mInstanceLock) { - loadActivitiesLocked(); - } - } - - @Override - public void onPackageAppeared(String packageName, int reason) { - synchronized (mInstanceLock) { - loadActivitiesLocked(); - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - synchronized (mInstanceLock) { - pruneHistoricalRecordsForPackageLocked(packageName); - loadActivitiesLocked(); - } - } - - @Override - public void onPackageDisappeared(String packageName, int reason) { - synchronized (mInstanceLock) { - pruneHistoricalRecordsForPackageLocked(packageName); - loadActivitiesLocked(); - } + public void onSomePackagesChanged() { + mReloadActivities = true; } } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8c81343..f2334ae 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -26,7 +26,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -91,6 +90,7 @@ import android.util.AttributeSet; import android.util.FloatMath; import android.util.Log; import android.util.TypedValue; +import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.ActionMode; import android.view.DragEvent; import android.view.Gravity; @@ -7712,6 +7712,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!isPassword) { info.setText(getTextForAccessibility()); } + + if (TextUtils.isEmpty(getContentDescription()) + && !TextUtils.isEmpty(mText)) { + info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); + info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); + info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH + | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); + } } @Override @@ -7726,12 +7737,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Gets the text reported for accessibility purposes. It is the - * text if not empty or the hint. + * Gets the text reported for accessibility purposes. * * @return The accessibility text. + * + * @hide */ - private CharSequence getTextForAccessibility() { + public CharSequence getTextForAccessibility() { CharSequence text = getText(); if (TextUtils.isEmpty(text)) { text = getHint(); @@ -8287,6 +8299,79 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * @hide + */ + @Override + public CharSequence getIterableTextForAccessibility() { + if (getContentDescription() == null) { + if (!(mText instanceof Spannable)) { + setText(mText, BufferType.SPANNABLE); + } + return mText; + } + return super.getIterableTextForAccessibility(); + } + + /** + * @hide + */ + @Override + public TextSegmentIterator getIteratorForGranularity(int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { + Spannable text = (Spannable) getIterableTextForAccessibility(); + if (!TextUtils.isEmpty(text) && getLayout() != null) { + AccessibilityIterators.LineTextSegmentIterator iterator = + AccessibilityIterators.LineTextSegmentIterator.getInstance(); + iterator.initialize(text, getLayout()); + return iterator; + } + } break; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { + Spannable text = (Spannable) getIterableTextForAccessibility(); + if (!TextUtils.isEmpty(text) && getLayout() != null) { + AccessibilityIterators.PageTextSegmentIterator iterator = + AccessibilityIterators.PageTextSegmentIterator.getInstance(); + iterator.initialize(this); + return iterator; + } + } break; + } + return super.getIteratorForGranularity(granularity); + } + + /** + * @hide + */ + @Override + public int getAccessibilityCursorPosition() { + if (TextUtils.isEmpty(getContentDescription())) { + return getSelectionEnd(); + } else { + return super.getAccessibilityCursorPosition(); + } + } + + /** + * @hide + */ + @Override + public void setAccessibilityCursorPosition(int index) { + if (getAccessibilityCursorPosition() == index) { + return; + } + if (TextUtils.isEmpty(getContentDescription())) { + if (index >= 0) { + Selection.setSelection((Spannable) mText, index); + } else { + Selection.removeSelection((Spannable) mText); + } + } else { + super.setAccessibilityCursorPosition(index); + } + } + + /** * User interface state that is stored by TextView for implementing * {@link View#onSaveInstanceState}. */ diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index 624dea8..a74ecd3 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -32,6 +32,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -99,8 +100,11 @@ public class MultiWaveView extends View { private float mTapRadius; private float mWaveCenterX; private float mWaveCenterY; - private float mVerticalOffset; + private int mMaxTargetHeight; + private int mMaxTargetWidth; private float mHorizontalOffset; + private float mVerticalOffset; + private float mOuterRadius = 0.0f; private float mHitRadius = 0.0f; private float mSnapMargin = 0.0f; @@ -142,6 +146,9 @@ public class MultiWaveView extends View { private int mTargetDescriptionsResourceId; private int mDirectionDescriptionsResourceId; private boolean mAlwaysTrackFinger; + private int mHorizontalInset; + private int mVerticalInset; + private int mGravity = Gravity.TOP; public MultiWaveView(Context context) { this(context, null); @@ -153,10 +160,9 @@ public class MultiWaveView extends View { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView); mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius); - mHorizontalOffset = a.getDimension(R.styleable.MultiWaveView_horizontalOffset, - mHorizontalOffset); - mVerticalOffset = a.getDimension(R.styleable.MultiWaveView_verticalOffset, - mVerticalOffset); +// mHorizontalOffset = a.getDimension(R.styleable.MultiWaveView_horizontalOffset, +// mHorizontalOffset); +// mVerticalOffset = a.getDimension(R.styleable.MultiWaveView_verticalOffset, mVerticalOffset); mHitRadius = a.getDimension(R.styleable.MultiWaveView_hitRadius, mHitRadius); mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin); mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration, @@ -169,6 +175,7 @@ public class MultiWaveView extends View { mOuterRing = new TargetDrawable(res, a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId); mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false); + mGravity = a.getInt(R.styleable.MultiWaveView_gravity, Gravity.TOP); // Read chevron animation drawables final int chevrons[] = { R.styleable.MultiWaveView_leftChevronDrawable, @@ -231,16 +238,16 @@ public class MultiWaveView extends View { @Override protected int getSuggestedMinimumWidth() { - // View should be large enough to contain the background + target drawable on either edge - return mOuterRing.getWidth() - + (mTargetDrawables.size() > 0 ? (mTargetDrawables.get(0).getWidth()/2) : 0); + // View should be large enough to contain the background + handle and + // target drawable on either edge. + return mOuterRing.getWidth() + mMaxTargetWidth; } @Override protected int getSuggestedMinimumHeight() { - // View should be large enough to contain the unlock ring + target drawable on either edge - return mOuterRing.getHeight() - + (mTargetDrawables.size() > 0 ? (mTargetDrawables.get(0).getHeight()/2) : 0); + // View should be large enough to contain the unlock ring + target and + // target drawable on either edge + return mOuterRing.getHeight() + mMaxTargetHeight; } private int resolveMeasured(int measureSpec, int desired) @@ -265,9 +272,10 @@ public class MultiWaveView extends View { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int minimumWidth = getSuggestedMinimumWidth(); final int minimumHeight = getSuggestedMinimumHeight(); - int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth); - int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight); - setMeasuredDimension(viewWidth, viewHeight); + int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth); + int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight); + setupGravity((computedWidth - minimumWidth), (computedHeight - minimumHeight)); + setMeasuredDimension(computedWidth, computedHeight); } private void switchToState(int state, float x, float y) { @@ -521,14 +529,25 @@ public class MultiWaveView extends View { TypedArray array = res.obtainTypedArray(resourceId); int count = array.length(); ArrayList<TargetDrawable> targetDrawables = new ArrayList<TargetDrawable>(count); + int maxWidth = mHandleDrawable.getWidth(); + int maxHeight = mHandleDrawable.getHeight(); for (int i = 0; i < count; i++) { TypedValue value = array.peekValue(i); - targetDrawables.add(new TargetDrawable(res, value != null ? value.resourceId : 0)); + TargetDrawable target= new TargetDrawable(res, value != null ? value.resourceId : 0); + targetDrawables.add(target); + maxWidth = Math.max(maxWidth, target.getWidth()); + maxHeight = Math.max(maxHeight, target.getHeight()); } - array.recycle(); mTargetResourceId = resourceId; mTargetDrawables = targetDrawables; - updateTargetPositions(); + if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) { + mMaxTargetWidth = maxWidth; + mMaxTargetHeight = maxHeight; + requestLayout(); // required to resize layout and call updateTargetPositions() + } else { + updateTargetPositions(); + } + array.recycle(); } /** @@ -638,23 +657,27 @@ public class MultiWaveView extends View { boolean handled = false; switch (action) { case MotionEvent.ACTION_DOWN: + if (DEBUG) Log.v(TAG, "*** DOWN ***"); handleDown(event); handled = true; break; case MotionEvent.ACTION_MOVE: + if (DEBUG) Log.v(TAG, "*** MOVE ***"); handleMove(event); handled = true; break; case MotionEvent.ACTION_UP: + if (DEBUG) Log.v(TAG, "*** UP ***"); handleMove(event); handleUp(event); handled = true; break; case MotionEvent.ACTION_CANCEL: - handleMove(event); + if (DEBUG) Log.v(TAG, "*** CANCEL ***"); + // handleMove(event); handleCancel(event); handled = true; break; @@ -795,6 +818,11 @@ public class MultiWaveView extends View { } mGrabbedState = newState; if (mOnTriggerListener != null) { + if (newState == OnTriggerListener.NO_HANDLE) { + mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE); + } else { + mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE); + } mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState); } } @@ -832,13 +860,45 @@ public class MultiWaveView extends View { moveHandleTo(centerX, centerY, false); } + private void setupGravity(int dx, int dy) { + final int layoutDirection = getResolvedLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.LEFT: + mHorizontalInset = 0; + break; + case Gravity.RIGHT: + mHorizontalInset = dx; + break; + case Gravity.CENTER_HORIZONTAL: + default: + mHorizontalInset = dx / 2; + break; + } + switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.TOP: + mVerticalInset = 0; + break; + case Gravity.BOTTOM: + mVerticalInset = dy; + break; + case Gravity.CENTER_VERTICAL: + default: + mVerticalInset = dy / 2; + break; + } + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); final int width = right - left; final int height = bottom - top; - float newWaveCenterX = mHorizontalOffset + Math.max(width, mOuterRing.getWidth() ) / 2; - float newWaveCenterY = mVerticalOffset + Math.max(height, mOuterRing.getHeight()) / 2; + float newWaveCenterX = mHorizontalOffset + mHorizontalInset + + Math.max(width, mMaxTargetWidth + mOuterRing.getWidth()) / 2; + float newWaveCenterY = mVerticalOffset + mVerticalInset + + Math.max(height, + mMaxTargetHeight + mOuterRing.getHeight()) / 2; if (newWaveCenterX != mWaveCenterX || newWaveCenterY != mWaveCenterY) { if (mWaveCenterX == 0 && mWaveCenterY == 0) { performInitialLayout(newWaveCenterX, newWaveCenterY); @@ -848,9 +908,8 @@ public class MultiWaveView extends View { mOuterRing.setX(mWaveCenterX); mOuterRing.setY(Math.max(mWaveCenterY, mWaveCenterY)); - - updateTargetPositions(); } + updateTargetPositions(); if (DEBUG) dump(); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 523b2d5..cd0959b 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -218,6 +218,7 @@ LOCAL_SHARED_LIBRARIES := \ libusbhost \ libharfbuzz \ libz \ + libsuspend \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 2c38893..16f377d 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -34,8 +34,9 @@ namespace android { #define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf" #define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf" #define TYPEFACE_BENGALI "/system/fonts/Lohit-Bengali.ttf" -#define TYPEFACE_DEVANAGARI "/system/fonts/Lohit-Devanagari.ttf" -#define TYPEFACE_TAMIL "/system/fonts/Lohit-Tamil.ttf" +#define TYPEFACE_DEVANAGARI_REGULAR "/system/fonts/DroidSansDevanagari-Regular.ttf" +#define TYPEFACE_TAMIL_REGULAR "/system/fonts/DroidSansTamil-Regular.ttf" +#define TYPEFACE_TAMIL_BOLD "/system/fonts/DroidSansTamil-Bold.ttf" #define TYPEFACE_THAI "/system/fonts/DroidSansThai.ttf" ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); @@ -337,8 +338,9 @@ TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) { mHebrewBoldTypeface = NULL; mBengaliTypeface = NULL; mThaiTypeface = NULL; - mDevanagariTypeface = NULL; - mTamilTypeface = NULL; + mDevanagariRegularTypeface = NULL; + mTamilRegularTypeface = NULL; + mTamilBoldTypeface = NULL; mFontRec.klass = &harfbuzzSkiaClass; mFontRec.userData = 0; @@ -364,8 +366,9 @@ TextLayoutShaper::~TextLayoutShaper() { SkSafeUnref(mHebrewBoldTypeface); SkSafeUnref(mBengaliTypeface); SkSafeUnref(mThaiTypeface); - SkSafeUnref(mDevanagariTypeface); - SkSafeUnref(mTamilTypeface); + SkSafeUnref(mDevanagariRegularTypeface); + SkSafeUnref(mTamilRegularTypeface); + SkSafeUnref(mTamilBoldTypeface); deleteShaperItemGlyphArrays(); } @@ -801,17 +804,38 @@ SkTypeface* TextLayoutShaper::typefaceForUnichar(const SkPaint* paint, SkTypefac break; case HB_Script_Devanagari: - typeface = getCachedTypeface(&mDevanagariTypeface, TYPEFACE_DEVANAGARI); + typeface = getCachedTypeface(&mDevanagariRegularTypeface, TYPEFACE_DEVANAGARI_REGULAR); #if DEBUG_GLYPHS - ALOGD("Using Devanagari Typeface"); + ALOGD("Using Devanagari Regular Typeface"); #endif break; case HB_Script_Tamil: - typeface = getCachedTypeface(&mTamilTypeface, TYPEFACE_TAMIL); + if (typeface) { + switch (typeface->style()) { + case SkTypeface::kBold: + case SkTypeface::kBoldItalic: + typeface = getCachedTypeface(&mTamilBoldTypeface, TYPEFACE_TAMIL_BOLD); #if DEBUG_GLYPHS - ALOGD("Using Tamil Typeface"); + ALOGD("Using Tamil Bold Typeface"); #endif + break; + + case SkTypeface::kNormal: + case SkTypeface::kItalic: + default: + typeface = getCachedTypeface(&mTamilRegularTypeface, TYPEFACE_TAMIL_REGULAR); +#if DEBUG_GLYPHS + ALOGD("Using Tamil Regular Typeface"); +#endif + break; + } + } else { + typeface = getCachedTypeface(&mTamilRegularTypeface, TYPEFACE_TAMIL_REGULAR); +#if DEBUG_GLYPHS + ALOGD("Using Tamil Regular Typeface"); +#endif + } break; default: diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index 7d7caac..0be61c6 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -194,8 +194,9 @@ private: SkTypeface* mHebrewBoldTypeface; SkTypeface* mBengaliTypeface; SkTypeface* mThaiTypeface; - SkTypeface* mDevanagariTypeface; - SkTypeface* mTamilTypeface; + SkTypeface* mDevanagariRegularTypeface; + SkTypeface* mTamilRegularTypeface; + SkTypeface* mTamilBoldTypeface; /** * Cache of Harfbuzz faces diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp index 48845f6..a201d8b 100644 --- a/core/jni/android_os_Power.cpp +++ b/core/jni/android_os_Power.cpp @@ -24,6 +24,7 @@ #include <hardware/power.h> #include <hardware_legacy/power.h> #include <cutils/android_reboot.h> +#include <suspend/autosuspend.h> static struct power_module *sPowerModule; @@ -70,8 +71,14 @@ setLastUserActivityTimeout(JNIEnv *env, jobject clazz, jlong timeMS) static int setScreenState(JNIEnv *env, jobject clazz, jboolean on) { - if (sPowerModule) - sPowerModule->setInteractive(sPowerModule, on); + if (on) { + autosuspend_disable(); + sPowerModule->setInteractive(sPowerModule, true); + } else { + sPowerModule->setInteractive(sPowerModule, false); + autosuspend_enable(); + } + return 0; } diff --git a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml index d8bea56..1be4462 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml @@ -57,12 +57,13 @@ android:background="@android:drawable/edit_text"> <!-- displays dots as user enters pin --> - <TextView android:id="@+id/pinDisplay" + <EditText android:id="@+id/pinDisplay" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:maxLines="1" android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textColor="@android:color/primary_text_holo_light" android:textStyle="bold" android:inputType="textPassword" /> diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml index 73dadb4..66cf98d 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml @@ -85,10 +85,10 @@ <com.android.internal.widget.multiwaveview.MultiWaveView android:id="@+id/unlock_widget" android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentBottom="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="center" + android:gravity="center" android:targetDrawables="@array/lockscreen_targets_with_camera" android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" @@ -99,8 +99,6 @@ android:snapMargin="@dimen/multiwaveview_snap_margin" android:hitRadius="@dimen/multiwaveview_hit_radius" android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right" - android:horizontalOffset="0dip" - android:verticalOffset="60dip" android:feedbackCount="3" android:vibrationDuration="20" /> diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml index 10b1ace..65b442b 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml @@ -84,10 +84,11 @@ <com.android.internal.widget.multiwaveview.MultiWaveView android:id="@+id/unlock_widget" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_rowSpan="7" android:layout_gravity="center_vertical|center_horizontal" + android:gravity="center" android:targetDrawables="@array/lockscreen_targets_with_camera" android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" @@ -100,8 +101,6 @@ android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right" android:feedbackCount="3" android:vibrationDuration="20" - android:horizontalOffset="0dip" - android:verticalOffset="0dip" /> <!-- emergency call button shown when sim is PUKd and tab_selector is hidden --> diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml index 6e8a645..9ca351c 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml @@ -57,12 +57,13 @@ android:background="@android:drawable/edit_text"> <!-- displays dots as user enters pin --> - <TextView android:id="@+id/pinDisplay" + <EditText android:id="@+id/pinDisplay" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:maxLines="1" android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textColor="@android:color/primary_text_holo_light" android:textStyle="bold" android:inputType="textPassword" /> diff --git a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml index 722dc26..56e6426 100644 --- a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml +++ b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml @@ -122,14 +122,16 @@ android:background="@android:drawable/edit_text"> <!-- displays dots as user enters new pin --> - <TextView android:id="@+id/pinDisplay" + <EditText android:id="@+id/pinDisplay" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:maxLines="1" android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textColor="@android:color/primary_text_holo_light" android:textStyle="bold" android:inputType="textPassword" + android:hint="@android:string/keyguard_password_enter_pin_prompt" /> <ImageButton android:id="@+id/pinDel" diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index 0ec8f75..3fd3023 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -129,6 +129,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentBottom="true" + android:gravity="top" android:targetDrawables="@array/lockscreen_targets_with_camera" android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" @@ -139,8 +140,6 @@ android:snapMargin="@dimen/multiwaveview_snap_margin" android:hitRadius="@dimen/multiwaveview_hit_radius" android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right" - android:horizontalOffset="0dip" - android:verticalOffset="60dip" android:feedbackCount="3" android:vibrationDuration="20" /> diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index 294f91e..cd03c10 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -131,9 +131,10 @@ <!-- Column 2 --> <com.android.internal.widget.multiwaveview.MultiWaveView android:id="@+id/unlock_widget" - android:layout_width="200dip" + android:layout_width="302dip" android:layout_height="match_parent" android:layout_rowSpan="7" + android:gravity="center" android:targetDrawables="@array/lockscreen_targets_with_camera" android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" @@ -146,8 +147,6 @@ android:topChevronDrawable="@drawable/ic_lockscreen_chevron_up" android:feedbackCount="3" android:vibrationDuration="20" - android:horizontalOffset="0dip" - android:verticalOffset="0dip" /> <!-- Music transport control --> diff --git a/core/res/res/values-sw600dp-land/arrays.xml b/core/res/res/values-sw600dp-land/arrays.xml index 6304bc0..6a09cf8 100644 --- a/core/res/res/values-sw600dp-land/arrays.xml +++ b/core/res/res/values-sw600dp-land/arrays.xml @@ -57,14 +57,14 @@ <array name="lockscreen_targets_with_camera"> <item>@drawable/ic_lockscreen_unlock</item> - <item>@null</item> + <item>@drawable/ic_lockscreen_search</item> <item>@drawable/ic_lockscreen_camera</item> <item>@null</item> </array> <array name="lockscreen_target_descriptions_with_camera"> <item>@string/description_target_unlock</item> - <item>@null</item> + <item>@string/description_target_search</item> <item>@string/description_target_camera</item> <item>@null</item> </array> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 9fa666e..484de0d 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3107,7 +3107,7 @@ <attr name="singleLine" format="boolean" /> <!-- Specifies whether the widget is enabled. The interpretation of the enabled state varies by subclass. For example, a non-enabled EditText prevents the user from editing the contained text, and - a non-enabled Button prevents the user from tapping the button. + a non-enabled Button prevents the user from tapping the button. The appearance of enabled and non-enabled widgets may differ, if the drawables referenced from evaluating state_enabled differ. --> <attr name="enabled" format="boolean" /> @@ -5378,12 +5378,17 @@ <!-- Number of waves/chevrons to show in animation. --> <attr name="feedbackCount" format="integer" /> - <!-- Used to shift center of pattern vertically. --> + <!-- {@deprecated Not used by the framework. Use android:gravity instead} + Used to shift center of pattern vertically. --> <attr name="verticalOffset" format="dimension" /> - <!-- Used to shift center of pattern horizontally. --> + <!-- {@deprecated Not used by the framework. Use android:gravity instead} + Used to shift center of pattern horizontally. --> <attr name="horizontalOffset" format="dimension" /> + <!-- How the items in this layout should be positioned --> + <attr name="gravity" /> + <!-- Used when the handle shouldn't wait to be hit before following the finger --> <attr name="alwaysTrackFinger" format="boolean" /> </declare-styleable> |