diff options
30 files changed, 810 insertions, 358 deletions
diff --git a/api/current.txt b/api/current.txt index 0467b69..51a1e79 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24479,13 +24479,14 @@ package android.provider { field public static final java.lang.String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; field public static final java.lang.String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; field public static final java.lang.String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS"; - field public static final java.lang.String ACTION_VOICE_CANCEL_ALARM = "android.intent.action.VOICE_CANCEL_ALARM"; - field public static final java.lang.String ACTION_VOICE_DELETE_ALARM = "android.intent.action.VOICE_DELETE_ALARM"; - field public static final java.lang.String ALARM_SEARCH_MODE_ALL = "all"; - field public static final java.lang.String ALARM_SEARCH_MODE_NEXT = "next"; - field public static final java.lang.String ALARM_SEARCH_MODE_NONE = "none"; - field public static final java.lang.String ALARM_SEARCH_MODE_TIME = "time"; - field public static final java.lang.String EXTRA_ALARM_SEARCH_MODE = "android.intent.extra.alarm.ALARM_SEARCH_MODE"; + field public static final java.lang.String ACTION_DISMISS_ALARM = "android.intent.action.DISMISS_ALARM"; + field public static final java.lang.String ACTION_SNOOZE_ALARM = "android.intent.action.SNOOZE_ALARM"; + field public static final java.lang.String ALARM_SEARCH_MODE_ALL = "android.all"; + field public static final java.lang.String ALARM_SEARCH_MODE_LABEL = "android.label"; + field public static final java.lang.String ALARM_SEARCH_MODE_NEXT = "android.next"; + field public static final java.lang.String ALARM_SEARCH_MODE_TIME = "android.time"; + field public static final java.lang.String EXTRA_ALARM_SEARCH_MODE = "android.intent.extra.alarm.SEARCH_MODE"; + field public static final java.lang.String EXTRA_ALARM_SNOOZE_DURATION = "android.intent.extra.alarm.SNOOZE_DURATION"; field public static final java.lang.String EXTRA_DAYS = "android.intent.extra.alarm.DAYS"; field public static final java.lang.String EXTRA_HOUR = "android.intent.extra.alarm.HOUR"; field public static final java.lang.String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM"; @@ -41541,6 +41542,7 @@ package android.widget { method public int getCompoundPaddingTop(); method public final int getCurrentHintTextColor(); method public final int getCurrentTextColor(); + method public android.view.ActionMode.Callback getCustomInsertionActionModeCallback(); method public android.view.ActionMode.Callback getCustomSelectionActionModeCallback(); method protected boolean getDefaultEditable(); method protected android.text.method.MovementMethod getDefaultMovementMethod(); @@ -41643,6 +41645,7 @@ package android.widget { method public void setCompoundDrawablesWithIntrinsicBounds(int, int, int, int); method public void setCompoundDrawablesWithIntrinsicBounds(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); method public void setCursorVisible(boolean); + method public void setCustomInsertionActionModeCallback(android.view.ActionMode.Callback); method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback); method public final void setEditableFactory(android.text.Editable.Factory); method public void setElegantTextHeight(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index d11fe85..7ce111c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -26409,13 +26409,14 @@ package android.provider { field public static final java.lang.String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; field public static final java.lang.String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; field public static final java.lang.String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS"; - field public static final java.lang.String ACTION_VOICE_CANCEL_ALARM = "android.intent.action.VOICE_CANCEL_ALARM"; - field public static final java.lang.String ACTION_VOICE_DELETE_ALARM = "android.intent.action.VOICE_DELETE_ALARM"; - field public static final java.lang.String ALARM_SEARCH_MODE_ALL = "all"; - field public static final java.lang.String ALARM_SEARCH_MODE_NEXT = "next"; - field public static final java.lang.String ALARM_SEARCH_MODE_NONE = "none"; - field public static final java.lang.String ALARM_SEARCH_MODE_TIME = "time"; - field public static final java.lang.String EXTRA_ALARM_SEARCH_MODE = "android.intent.extra.alarm.ALARM_SEARCH_MODE"; + field public static final java.lang.String ACTION_DISMISS_ALARM = "android.intent.action.DISMISS_ALARM"; + field public static final java.lang.String ACTION_SNOOZE_ALARM = "android.intent.action.SNOOZE_ALARM"; + field public static final java.lang.String ALARM_SEARCH_MODE_ALL = "android.all"; + field public static final java.lang.String ALARM_SEARCH_MODE_LABEL = "android.label"; + field public static final java.lang.String ALARM_SEARCH_MODE_NEXT = "android.next"; + field public static final java.lang.String ALARM_SEARCH_MODE_TIME = "android.time"; + field public static final java.lang.String EXTRA_ALARM_SEARCH_MODE = "android.intent.extra.alarm.SEARCH_MODE"; + field public static final java.lang.String EXTRA_ALARM_SNOOZE_DURATION = "android.intent.extra.alarm.SNOOZE_DURATION"; field public static final java.lang.String EXTRA_DAYS = "android.intent.extra.alarm.DAYS"; field public static final java.lang.String EXTRA_HOUR = "android.intent.extra.alarm.HOUR"; field public static final java.lang.String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM"; @@ -44120,6 +44121,7 @@ package android.widget { method public int getCompoundPaddingTop(); method public final int getCurrentHintTextColor(); method public final int getCurrentTextColor(); + method public android.view.ActionMode.Callback getCustomInsertionActionModeCallback(); method public android.view.ActionMode.Callback getCustomSelectionActionModeCallback(); method protected boolean getDefaultEditable(); method protected android.text.method.MovementMethod getDefaultMovementMethod(); @@ -44222,6 +44224,7 @@ package android.widget { method public void setCompoundDrawablesWithIntrinsicBounds(int, int, int, int); method public void setCompoundDrawablesWithIntrinsicBounds(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); method public void setCursorVisible(boolean); + method public void setCustomInsertionActionModeCallback(android.view.ActionMode.Callback); method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback); method public final void setEditableFactory(android.text.Editable.Factory); method public void setElegantTextHeight(boolean); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3224d41..10d76f7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1640,6 +1640,10 @@ public final class ActivityThread { return sCurrentActivityThread; } + public static boolean isSystem() { + return (sCurrentActivityThread != null) ? sCurrentActivityThread.mSystemThread : false; + } + public static String currentOpPackageName() { ActivityThread am = currentActivityThread(); return (am != null && am.getApplication() != null) diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 2cfc1fa4..031854a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -25,11 +25,13 @@ import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.IntentSender; import android.os.Bundle; +import android.os.Looper; import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.os.UserHandle; import android.util.AndroidException; @@ -206,10 +208,20 @@ public final class PendingIntent implements Parcelable { private int mResultCode; private String mResultData; private Bundle mResultExtras; + private static Handler sDefaultSystemHandler; FinishedDispatcher(PendingIntent pi, OnFinished who, Handler handler) { mPendingIntent = pi; mWho = who; - mHandler = handler; + if (handler == null && ActivityThread.isSystem()) { + // We assign a default handler for the system process to avoid deadlocks when + // processing receivers in various components that hold global service locks. + if (sDefaultSystemHandler == null) { + sDefaultSystemHandler = new Handler(Looper.getMainLooper()); + } + mHandler = sDefaultSystemHandler; + } else { + mHandler = handler; + } } public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean serialized, boolean sticky, int sendingUser) { diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java index 48b604c..f965f54 100644 --- a/core/java/android/inputmethodservice/ExtractEditText.java +++ b/core/java/android/inputmethodservice/ExtractEditText.java @@ -106,7 +106,7 @@ public class ExtractEditText extends EditText { if (mIME != null && mIME.onExtractTextContextMenuItem(id)) { // Mode was started on Extracted, needs to be stopped here. // Cut and paste will change the text, which stops selection mode. - if (id == android.R.id.copy) stopSelectionActionMode(); + if (id == android.R.id.copy) stopTextActionMode(); return true; } return super.onTextContextMenuItem(id); diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java index 25a35e1..be37293 100644 --- a/core/java/android/provider/AlarmClock.java +++ b/core/java/android/provider/AlarmClock.java @@ -50,7 +50,7 @@ public final class AlarmClock { * {@link android.app.Activity#isVoiceInteraction}, and if true, the implementation should * report a deeplink of the created/enabled alarm using * {@link android.app.VoiceInteractor.CompleteVoiceRequest}. This allows follow-on voice actions - * such as {@link #ACTION_VOICE_CANCEL_ALARM} to cancel the alarm that was just enabled. + * such as {@link #ACTION_DISMISS_ALARM} to dismiss the alarm that was just enabled. * </p> * <h3>Request parameters</h3> * <ul> @@ -69,46 +69,61 @@ public final class AlarmClock { public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM"; /** - * Voice Activity Action: Cancel an alarm. - * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be - * started in Voice Interaction mode. + * Activity Action: Dismiss an alarm. * <p> - * Cancels the specified alarm by voice. To cancel means to disable, but not delete, the alarm. - * See {@link #ACTION_VOICE_DELETE_ALARM} to delete an alarm by voice. - * </p><p> - * The alarm to cancel can be specified or searched for in one of the following ways: + * The alarm to dismiss can be specified or searched for in one of the following ways: * <ol> - * <li>The Intent's data URI specifies a deeplink to the alarm. - * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is - * required to determine how to search for the alarm. + * <li>The Intent's data URI, which represents a deeplink to the alarm. + * <li>The extra {@link #EXTRA_ALARM_SEARCH_MODE} to determine how to search for the alarm. + * </ol> + * </p><p> + * If neither of the above are given then: + * <ul> + * <li>If exactly one active alarm exists, it is dismissed. + * <li>If more than one active alarm exists, the user is prompted to choose the alarm to dismiss. + * </ul> + * </p><p> + * If the extra {@link #EXTRA_ALARM_SEARCH_MODE} is used, and the search results contain two or + * more matching alarms, then the implementation should show an UI with the results and allow + * the user to select the alarm to dismiss. If the implementation supports + * {@link android.content.Intent.CATEGORY_VOICE} and the activity is started in Voice + * Interaction mode (i.e. check {@link android.app.Activity#isVoiceInteraction}), then the + * implementation should additionally use {@link android.app.VoiceInteractor.PickOptionRequest} + * to start a voice interaction follow-on flow to help the user disambiguate the alarm by voice. + * </p><p> + * If the specified alarm is a single occurrence alarm, then dismissing it effectively disables + * the alarm; it will never ring again unless explicitly re-enabled. + * </p><p> + * If the specified alarm is a repeating alarm, then dismissing it only prevents the upcoming + * instance from ringing. The alarm remains enabled so that it will still ring on the date and + * time of the next instance (i.e. the instance after the upcoming one). + * </p> * - * @see #ACTION_VOICE_DELETE_ALARM * @see #EXTRA_ALARM_SEARCH_MODE */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_VOICE_CANCEL_ALARM = - "android.intent.action.VOICE_CANCEL_ALARM"; + public static final String ACTION_DISMISS_ALARM = + "android.intent.action.DISMISS_ALARM"; /** - * Voice Activity Action: Delete an alarm. - * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be - * started in Voice Interaction mode. + * Activity Action: Snooze a currently ringing alarm. * <p> - * Deletes the specified alarm by voice. - * See {@link #ACTION_VOICE_CANCEL_ALARM} to cancel (disable) an alarm by voice. + * Snoozes the currently ringing alarm. The extra {@link #EXTRA_ALARM_SNOOZE_DURATION} can be + * optionally set to specify the snooze duration; if unset, the implementation should use a + * reasonable default, for example 10 minutes. The alarm should ring again after the snooze + * duration. * </p><p> - * The alarm to delete can be specified or searched for in one of the following ways: - * <ol> - * <li>The Intent's data URI specifies a deeplink to the alarm. - * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is - * required to determine how to search for the alarm. + * Note: setting the extra {@link #EXTRA_ALARM_SNOOZE_DURATION} does not change the default + * snooze duration; it's only applied to the currently ringing alarm. + * </p><p> + * If there is no currently ringing alarm, then this is a no-op. + * </p> * - * @see #ACTION_VOICE_CANCEL_ALARM - * @see #EXTRA_ALARM_SEARCH_MODE + * @see #EXTRA_ALARM_SNOOZE_DURATION */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_VOICE_DELETE_ALARM = - "android.intent.action.VOICE_DELETE_ALARM"; + public static final String ACTION_SNOOZE_ALARM = + "android.intent.action.SNOOZE_ALARM"; /** * Activity Action: Set a timer. @@ -149,10 +164,9 @@ public final class AlarmClock { /** * Bundle extra: Specify the type of search mode to look up an alarm. * <p> - * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM} to identify - * the alarm(s) to cancel or delete, respectively. + * For example, used by {@link #ACTION_DISMISS_ALARM} to identify the alarm to dismiss. * </p><p> - * This extra is only required when the alarm is not already identified by a deeplink as + * This extra is only used when the alarm is not already identified by a deeplink as * specified in the Intent's data URI. * </p><p> * The value of this extra is a {@link String}, restricted to the following set of supported @@ -164,22 +178,19 @@ public final class AlarmClock { * <li><i>Next alarm</i> - {@link #ALARM_SEARCH_MODE_NEXT}: Selects the alarm that will * ring next, or the alarm that is currently ringing, if any. * <li><i>All alarms</i> - {@link #ALARM_SEARCH_MODE_ALL}: Selects all alarms. - * <li><i>None</i> - {@link #ALARM_SEARCH_MODE_NONE}: No search mode specified. The - * implementation should ask the user to select a search mode using - * {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to - * identify the alarm. + * <li><i>Label</i> - {@link #ALARM_SEARCH_MODE_LABEL}: Search by alarm label. Should return + * alarms that contain the word or phrase in given label. * </ul> - * </ol> + * </p> * * @see #ALARM_SEARCH_MODE_TIME * @see #ALARM_SEARCH_MODE_NEXT * @see #ALARM_SEARCH_MODE_ALL - * @see #ALARM_SEARCH_MODE_NONE - * @see #ACTION_VOICE_CANCEL_ALARM - * @see #ACTION_VOICE_DELETE_ALARM + * @see #ALARM_SEARCH_MODE_LABEL + * @see #ACTION_DISMISS_ALARM */ public static final String EXTRA_ALARM_SEARCH_MODE = - "android.intent.extra.alarm.ALARM_SEARCH_MODE"; + "android.intent.extra.alarm.SEARCH_MODE"; /** * Search for the alarm that is most closely matched by the search parameters @@ -193,35 +204,33 @@ public final class AlarmClock { * * @see #EXTRA_ALARM_SEARCH_MODE */ - public static final String ALARM_SEARCH_MODE_TIME = "time"; + public static final String ALARM_SEARCH_MODE_TIME = "android.time"; /** * Selects the alarm that will ring next, or the alarm that is currently ringing, if any. * * @see #EXTRA_ALARM_SEARCH_MODE */ - public static final String ALARM_SEARCH_MODE_NEXT = "next"; + public static final String ALARM_SEARCH_MODE_NEXT = "android.next"; /** * Selects all alarms. * * @see #EXTRA_ALARM_SEARCH_MODE */ - public static final String ALARM_SEARCH_MODE_ALL = "all"; + public static final String ALARM_SEARCH_MODE_ALL = "android.all"; /** - * No search mode specified. The implementation should ask the user to select a search mode - * using {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to - * identify the alarm. + * Search by alarm label. Should return alarms that contain the word or phrase in given label. * * @see #EXTRA_ALARM_SEARCH_MODE */ - public static final String ALARM_SEARCH_MODE_NONE = "none"; + public static final String ALARM_SEARCH_MODE_LABEL = "android.label"; /** * Bundle extra: The AM/PM of the alarm. * <p> - * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM}. + * Used by {@link #ACTION_DISMISS_ALARM}. * </p><p> * This extra is optional and only used when {@link #EXTRA_ALARM_SEARCH_MODE} is set to * {@link #ALARM_SEARCH_MODE_TIME}. In this search mode, the {@link #EXTRA_IS_PM} is @@ -233,13 +242,25 @@ public final class AlarmClock { * The value is a {@link Boolean}, where false=AM and true=PM. * </p> * - * @see #ACTION_VOICE_CANCEL_ALARM - * @see #ACTION_VOICE_DELETE_ALARM + * @see #ACTION_DISMISS_ALARM * @see #EXTRA_HOUR * @see #EXTRA_MINUTES */ public static final String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM"; + + /** + * Bundle extra: The snooze duration of the alarm in minutes. + * <p> + * Used by {@link #ACTION_SNOOZE_ALARM}. This extra is optional and the value is an + * {@link Integer} that specifies the duration in minutes for which to snooze the alarm. + * </p> + * + * @see #ACTION_SNOOZE_ALARM + */ + public static final String EXTRA_ALARM_SNOOZE_DURATION = + "android.intent.extra.alarm.SNOOZE_DURATION"; + /** * Bundle extra: Weekdays for repeating alarm. * <p> diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 053b35c..1c8a79b 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1169,7 +1169,10 @@ public final class InputMethodManager { // do its stuff. // Life is good: let's hook everything up! EditorInfo tba = new EditorInfo(); - tba.packageName = view.getContext().getPackageName(); + // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the + // system can verify the consistency between the uid of this process and package name passed + // from here. See comment of Context#getOpPackageName() for details. + tba.packageName = view.getContext().getOpPackageName(); tba.fieldId = view.getId(); InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 56f9b5c..4c98abf 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -134,7 +134,8 @@ public class Editor { // Cursor Controllers. InsertionPointCursorController mInsertionPointCursorController; SelectionModifierCursorController mSelectionModifierCursorController; - ActionMode mSelectionActionMode; + // Action mode used when text is selected or when actions on an insertion cursor are triggered. + ActionMode mTextActionMode; boolean mInsertionControllerEnabled; boolean mSelectionControllerEnabled; @@ -205,13 +206,14 @@ public class Editor { float mLastDownPositionX, mLastDownPositionY; Callback mCustomSelectionActionModeCallback; + Callback mCustomInsertionActionModeCallback; // Set when this TextView gained focus with some text selected. Will start selection mode. boolean mCreatedWithASelection; boolean mDoubleTap = false; - private Runnable mSelectionModeWithoutSelectionRunnable; + private Runnable mInsertionActionModeRunnable; // The span controller helps monitoring the changes to which the Editor needs to react: // - EasyEditSpans, for which we have some UI to display on attach and on hide @@ -236,8 +238,8 @@ public class Editor { private final Runnable mHideFloatingToolbar = new Runnable() { @Override public void run() { - if (mSelectionActionMode != null) { - mSelectionActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT); + if (mTextActionMode != null) { + mTextActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT); } } }; @@ -245,8 +247,8 @@ public class Editor { private final Runnable mShowFloatingToolbar = new Runnable() { @Override public void run() { - if (mSelectionActionMode != null) { - mSelectionActionMode.snooze(0); // snooze off. + if (mTextActionMode != null) { + mTextActionMode.snooze(0); // snooze off. } } }; @@ -310,7 +312,7 @@ public class Editor { void replace() { int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; - stopSelectionActionMode(); + stopTextActionMode(); Selection.setSelection((Spannable) mTextView.getText(), middle); showSuggestions(); } @@ -343,7 +345,7 @@ public class Editor { mTextView.setHasTransientState(false); // We had an active selection from before, start the selection mode. - startSelectionActionModeWithSelection(); + startSelectionActionMode(); } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); @@ -372,8 +374,8 @@ public class Editor { } // Cancel the single tap delayed runnable. - if (mSelectionModeWithoutSelectionRunnable != null) { - mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); } mTextView.removeCallbacks(mHideFloatingToolbar); @@ -390,7 +392,7 @@ public class Editor { mPreserveDetachedSelection = true; hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); mPreserveDetachedSelection = false; mTemporaryDetach = false; } @@ -586,7 +588,7 @@ public class Editor { } if (!mSelectionControllerEnabled) { - stopSelectionActionMode(); + stopTextActionMode(); if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.onDetached(); mSelectionModifierCursorController = null; @@ -984,14 +986,14 @@ public class Editor { mInsertionControllerEnabled) { final int offset = mTextView.getOffsetForPosition(mLastDownPositionX, mLastDownPositionY); - stopSelectionActionMode(); + stopTextActionMode(); Selection.setSelection((Spannable) mTextView.getText(), offset); getInsertionController().show(); - startSelectionActionModeWithoutSelection(); + startInsertionActionMode(); handled = true; } - if (!handled && mSelectionActionMode != null) { + if (!handled && mTextActionMode != null) { if (touchPositionIsInSelection()) { // Start a drag final int start = mTextView.getSelectionStart(); @@ -1001,9 +1003,9 @@ public class Editor { DragLocalState localState = new DragLocalState(mTextView, start, end); mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, View.DRAG_FLAG_GLOBAL); - stopSelectionActionMode(); + stopTextActionMode(); } else { - stopSelectionActionMode(); + stopTextActionMode(); selectCurrentWordAndStartDrag(); } handled = true; @@ -1101,12 +1103,12 @@ public class Editor { final int selStart = mTextView.getSelectionStart(); final int selEnd = mTextView.getSelectionEnd(); hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd); } else { if (mTemporaryDetach) mPreserveDetachedSelection = true; hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); if (mTemporaryDetach) mPreserveDetachedSelection = false; downgradeEasyCorrectionSpans(); } @@ -1149,7 +1151,7 @@ public class Editor { // We do not hide the span controllers, since they can be added when a new text is // inserted into the text view (voice IME). hideCursorControllers(); - stopSelectionActionMode(); + stopTextActionMode(); } private int getLastTapPosition() { @@ -1216,7 +1218,7 @@ public class Editor { } private void updateFloatingToolbarVisibility(MotionEvent event) { - if (mSelectionActionMode != null) { + if (mTextActionMode != null) { switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: hideFloatingToolbar(); @@ -1229,7 +1231,7 @@ public class Editor { } private void hideFloatingToolbar() { - if (mSelectionActionMode != null) { + if (mTextActionMode != null) { mTextView.removeCallbacks(mShowFloatingToolbar); // Delay the "hide" a little bit just in case a "show" will happen almost immediately. mTextView.postDelayed(mHideFloatingToolbar, 100); @@ -1237,7 +1239,7 @@ public class Editor { } private void showFloatingToolbar() { - if (mSelectionActionMode != null) { + if (mTextActionMode != null) { mTextView.removeCallbacks(mHideFloatingToolbar); // Delay "show" so it doesn't interfere with click confirmations // or double-clicks that could "dismiss" the floating toolbar. @@ -1701,26 +1703,20 @@ public class Editor { /** * @return true if the selection mode was actually started. */ - private boolean startSelectionActionModeWithoutSelection() { + private boolean startInsertionActionMode() { + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); + } if (extractedTextModeWillBeStarted()) { - // Cancel the single tap delayed runnable. - if (mSelectionModeWithoutSelectionRunnable != null) { - mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); - } - return false; } + stopTextActionMode(); - if (mSelectionActionMode != null) { - // Selection action mode is already started - // TODO: revisit invocations to minimize this case. - mSelectionActionMode.invalidate(); - return false; - } - ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); - mSelectionActionMode = mTextView.startActionMode( + ActionMode.Callback actionModeCallback = + new TextActionModeCallback(false /* hasSelection */); + mTextActionMode = mTextView.startActionMode( actionModeCallback, ActionMode.TYPE_FLOATING); - return mSelectionActionMode != null; + return mTextActionMode != null; } /** @@ -1730,8 +1726,8 @@ public class Editor { * * @return true if the selection mode was actually started. */ - boolean startSelectionActionModeWithSelection() { - boolean selectionStarted = startSelectionActionModeWithSelectionInternal(); + boolean startSelectionActionMode() { + boolean selectionStarted = startSelectionActionModeInternal(); if (selectionStarted) { getSelectionController().show(); } else if (getInsertionController() != null) { @@ -1749,13 +1745,13 @@ public class Editor { private boolean selectCurrentWordAndStartDrag() { if (extractedTextModeWillBeStarted()) { // Cancel the single tap delayed runnable. - if (mSelectionModeWithoutSelectionRunnable != null) { - mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); } return false; } - if (mSelectionActionMode != null) { - mSelectionActionMode.finish(); + if (mTextActionMode != null) { + mTextActionMode.finish(); } if (!checkFieldAndSelectCurrentWord()) { return false; @@ -1784,10 +1780,10 @@ public class Editor { return true; } - private boolean startSelectionActionModeWithSelectionInternal() { - if (mSelectionActionMode != null) { + private boolean startSelectionActionModeInternal() { + if (mTextActionMode != null) { // Selection action mode is already started - mSelectionActionMode.invalidate(); + mTextActionMode.invalidate(); return false; } @@ -1800,12 +1796,13 @@ public class Editor { // Do not start the action mode when extracted text will show up full screen, which would // immediately hide the newly created action bar and would be visually distracting. if (!willExtract) { - ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); - mSelectionActionMode = mTextView.startActionMode( + ActionMode.Callback actionModeCallback = + new TextActionModeCallback(true /* hasSelection */); + mTextActionMode = mTextView.startActionMode( actionModeCallback, ActionMode.TYPE_FLOATING); } - final boolean selectionStarted = mSelectionActionMode != null || willExtract; + final boolean selectionStarted = mTextActionMode != null || willExtract; if (selectionStarted && !mTextView.isTextSelectable() && mShowSoftInputOnFocus) { // Show the IME to be able to replace text, except when selecting non editable text. final InputMethodManager imm = InputMethodManager.peekInstance(); @@ -1906,7 +1903,7 @@ public class Editor { void onTouchUpEvent(MotionEvent event) { boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect(); hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); CharSequence text = mTextView.getText(); if (!selectAllGotFocus && text.length() > 0) { // Move cursor @@ -1920,8 +1917,8 @@ public class Editor { if (!extractedTextModeWillBeStarted()) { if (isCursorInsideEasyCorrectionSpan()) { // Cancel the single tap delayed runnable. - if (mSelectionModeWithoutSelectionRunnable != null) { - mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); } mShowSuggestionRunnable = new Runnable() { @@ -1939,10 +1936,10 @@ public class Editor { } } - protected void stopSelectionActionMode() { - if (mSelectionActionMode != null) { + protected void stopTextActionMode() { + if (mTextActionMode != null) { // This will hide the mSelectionModifierCursorController - mSelectionActionMode.finish(); + mTextActionMode.finish(); } } @@ -2027,7 +2024,7 @@ public class Editor { mSuggestionsPopupWindow = new SuggestionsPopupWindow(); } hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); mSuggestionsPopupWindow.show(); } @@ -2035,8 +2032,8 @@ public class Editor { if (mPositionListener != null) { mPositionListener.onScrollChanged(); } - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidateContentRect(); + if (mTextActionMode != null) { + mTextActionMode.invalidateContentRect(); } } @@ -3087,45 +3084,51 @@ public class Editor { } /** - * An ActionMode Callback class that is used to provide actions while in text selection mode. + * An ActionMode Callback class that is used to provide actions while in text insertion or + * selection mode. * - * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending - * on which of these this TextView supports. + * The default callback provides a subset of Select All, Cut, Copy, Paste, Share and Replace + * actions, depending on which of these this TextView supports and the current selection. */ - private class SelectionActionModeCallback extends ActionMode.Callback2 { + private class TextActionModeCallback extends ActionMode.Callback2 { private final Path mSelectionPath = new Path(); private final RectF mSelectionBounds = new RectF(); - - private int mSelectionHandleHeight; - private int mInsertionHandleHeight; - - public SelectionActionModeCallback() { - SelectionModifierCursorController selectionController = getSelectionController(); - if (selectionController.mStartHandle == null) { - // As these are for initializing selectionController, hide() must be called. - selectionController.initDrawables(); - selectionController.initHandles(); - selectionController.hide(); - } - mSelectionHandleHeight = Math.max( - mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight()); - InsertionPointCursorController insertionController = getInsertionController(); - if (insertionController != null) { - insertionController.getHandle(); - mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight(); + private final boolean mHasSelection; + + private int mHandleHeight; + + public TextActionModeCallback(boolean hasSelection) { + mHasSelection = hasSelection; + if (mHasSelection) { + SelectionModifierCursorController selectionController = getSelectionController(); + if (selectionController.mStartHandle == null) { + // As these are for initializing selectionController, hide() must be called. + selectionController.initDrawables(); + selectionController.initHandles(); + selectionController.hide(); + } + mHandleHeight = Math.max( + mSelectHandleLeft.getMinimumHeight(), + mSelectHandleRight.getMinimumHeight()); + } else { + InsertionPointCursorController insertionController = getInsertionController(); + if (insertionController != null) { + insertionController.getHandle(); + mHandleHeight = mSelectHandleCenter.getMinimumHeight(); + } } } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mode.setTitle(mTextView.getContext().getString( - com.android.internal.R.string.textSelectionCABTitle)); + mode.setTitle(null); mode.setSubtitle(null); mode.setTitleOptionalHint(true); populateMenuWithItems(menu); - if (mCustomSelectionActionModeCallback != null) { - if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) { + Callback customCallback = getCustomCallback(); + if (customCallback != null) { + if (!customCallback.onCreateActionMode(mode, menu)) { // The custom mode can choose to cancel the action mode return false; } @@ -3141,6 +3144,12 @@ public class Editor { } } + private Callback getCustomCallback() { + return mHasSelection + ? mCustomSelectionActionModeCallback + : mCustomInsertionActionModeCallback; + } + private void populateMenuWithItems(Menu menu) { if (mTextView.canCut()) { menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut). @@ -3203,8 +3212,9 @@ public class Editor { public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateReplaceItem(menu); - if (mCustomSelectionActionModeCallback != null) { - return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu); + Callback customCallback = getCustomCallback(); + if (customCallback != null) { + return customCallback.onPrepareActionMode(mode, menu); } return true; } @@ -3230,8 +3240,8 @@ public class Editor { item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE); return true; } - if (mCustomSelectionActionModeCallback != null && - mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) { + Callback customCallback = getCustomCallback(); + if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } return mTextView.onTextContextMenuItem(item.getItemId()); @@ -3239,8 +3249,9 @@ public class Editor { @Override public void onDestroyActionMode(ActionMode mode) { - if (mCustomSelectionActionModeCallback != null) { - mCustomSelectionActionModeCallback.onDestroyActionMode(mode); + Callback customCallback = getCustomCallback(); + if (customCallback != null) { + customCallback.onDestroyActionMode(mode); } /* @@ -3259,7 +3270,7 @@ public class Editor { mSelectionModifierCursorController.resetTouchOffsets(); } - mSelectionActionMode = null; + mTextActionMode = null; } @Override @@ -3274,7 +3285,7 @@ public class Editor { mTextView.getLayout().getSelectionPath( mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath); mSelectionPath.computeBounds(mSelectionBounds, true); - mSelectionBounds.bottom += mSelectionHandleHeight; + mSelectionBounds.bottom += mHandleHeight; } else if (mCursorCount == 2) { // We have a split cursor. In this case, we take the rectangle that includes both // parts of the cursor to ensure we don't obscure either of them. @@ -3285,7 +3296,7 @@ public class Editor { Math.min(firstCursorBounds.top, secondCursorBounds.top), Math.max(firstCursorBounds.right, secondCursorBounds.right), Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom) - + mInsertionHandleHeight); + + mHandleHeight); } else { // We have a single cursor. int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart()); @@ -3295,7 +3306,7 @@ public class Editor { primaryHorizontal, mTextView.getLayout().getLineTop(line), primaryHorizontal + 1, - mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight); + mTextView.getLayout().getLineTop(line + 1) + mHandleHeight); } // Take TextView's padding and scroll into account. int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); @@ -3847,25 +3858,25 @@ public class Editor { SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime; // Cancel the single tap delayed runnable. - if (mSelectionModeWithoutSelectionRunnable != null + if (mInsertionActionModeRunnable != null && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) { - mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable); + mTextView.removeCallbacks(mInsertionActionModeRunnable); } // Prepare and schedule the single tap runnable to run exactly after the double tap // timeout has passed. if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan() && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) { - if (mSelectionModeWithoutSelectionRunnable == null) { - mSelectionModeWithoutSelectionRunnable = new Runnable() { + if (mInsertionActionModeRunnable == null) { + mInsertionActionModeRunnable = new Runnable() { public void run() { - startSelectionActionModeWithoutSelection(); + startInsertionActionMode(); } }; } mTextView.postDelayed( - mSelectionModeWithoutSelectionRunnable, + mInsertionActionModeRunnable, ViewConfiguration.getDoubleTapTimeout() + 1); } @@ -3934,15 +3945,15 @@ public class Editor { if (distanceSquared < touchSlop * touchSlop) { // Tapping on the handle toggles the selection action mode. - if (mSelectionActionMode != null) { - mSelectionActionMode.finish(); + if (mTextActionMode != null) { + mTextActionMode.finish(); } else { - startSelectionActionModeWithoutSelection(); + startInsertionActionMode(); } } } else { - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidateContentRect(); + if (mTextActionMode != null) { + mTextActionMode.invalidateContentRect(); } } hideAfterDelay(); @@ -3972,8 +3983,8 @@ public class Editor { @Override public void updatePosition(float x, float y) { positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false); - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidate(); + if (mTextActionMode != null) { + mTextActionMode.invalidate(); } } @@ -4024,8 +4035,8 @@ public class Editor { Selection.setSelection((Spannable) mTextView.getText(), offset, mTextView.getSelectionEnd()); updateDrawable(); - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidate(); + if (mTextActionMode != null) { + mTextActionMode.invalidate(); } } @@ -4150,8 +4161,8 @@ public class Editor { public void updateSelection(int offset) { Selection.setSelection((Spannable) mTextView.getText(), mTextView.getSelectionStart(), offset); - if (mSelectionActionMode != null) { - mSelectionActionMode.invalidate(); + if (mTextActionMode != null) { + mTextActionMode.invalidate(); } updateDrawable(); } @@ -4515,7 +4526,7 @@ public class Editor { mEndHandle.showAtLocation(endOffset); // No longer the first dragging motion, reset. - startSelectionActionModeWithSelection(); + startSelectionActionMode(); mDragAcceleratorActive = false; mStartOffset = -1; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3a85476..6872caa 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1481,7 +1481,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (mEditor.hasSelectionController()) { - mEditor.startSelectionActionModeWithSelection(); + mEditor.startSelectionActionMode(); } } } @@ -5282,7 +5282,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // - onFocusChanged cannot start it when focus is given to a view with selected text (after // a screen rotation) since layout is not yet initialized at that point. if (mEditor != null && mEditor.mCreatedWithASelection) { - mEditor.startSelectionActionModeWithSelection(); + mEditor.startSelectionActionMode(); mEditor.mCreatedWithASelection = false; } @@ -5290,7 +5290,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can // not be set. Do the test here instead. if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { - mEditor.startSelectionActionModeWithSelection(); + mEditor.startSelectionActionMode(); } unregisterForPreDraw(); @@ -5908,7 +5908,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null; + boolean isInSelectionMode = mEditor != null && mEditor.mTextActionMode != null; if (isInSelectionMode) { if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { @@ -5923,7 +5923,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { - stopSelectionActionMode(); + stopTextActionMode(); return true; } } @@ -6092,8 +6092,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Has to be done on key down (and not on key up) to correctly be intercepted. case KeyEvent.KEYCODE_BACK: - if (mEditor != null && mEditor.mSelectionActionMode != null) { - stopSelectionActionMode(); + if (mEditor != null && mEditor.mTextActionMode != null) { + stopTextActionMode(); return -1; } break; @@ -6423,7 +6423,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // extracted mode will start. Some text is selected though, and will trigger an action mode // in the extracted view. mEditor.hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); } /** @@ -8258,7 +8258,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onVisibilityChanged(changedView, visibility); if (mEditor != null && visibility != VISIBLE) { mEditor.hideControllers(); - stopSelectionActionMode(); + stopTextActionMode(); } } @@ -8976,7 +8976,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) text, start, end); // Make sure selection mode is engaged. if (mEditor != null) { - mEditor.startSelectionActionModeWithSelection(); + mEditor.startSelectionActionMode(); } return true; } @@ -9100,12 +9100,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case ID_CUT: setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); deleteText_internal(min, max); - stopSelectionActionMode(); + stopTextActionMode(); return true; case ID_COPY: setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); - stopSelectionActionMode(); + stopTextActionMode(); return true; case ID_REPLACE: @@ -9195,14 +9195,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * selection is initiated in this View. * * The standard implementation populates the menu with a subset of Select All, Cut, Copy, - * Paste and Share actions, depending on what this View supports. + * Paste, Replace and Share actions, depending on what this View supports. * * A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The * default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, - * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste} or - * {@link android.R.id#shareText} ids as parameters. + * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, + * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. * * Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent @@ -9230,11 +9230,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * If provided, this ActionMode.Callback will be used to create the ActionMode when text + * insertion is initiated in this View. + * + * The standard implementation populates the menu with a subset of Select All, + * Paste and Replace actions, depending on what this View supports. + * + * A custom implementation can add new entries in the default menu in its + * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The + * default actions can also be removed from the menu using + * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, + * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters. + * + * Returning false from + * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent + * the action mode from being started. + * + * Action click events should be handled by the custom implementation of + * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}. + * + * Note that text insertion mode is not started when a TextView receives focus and the + * {@link android.R.attr#selectAllOnFocus} flag has been set. + */ + public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { + createEditorIfNeeded(); + mEditor.mCustomInsertionActionModeCallback = actionModeCallback; + } + + /** + * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. + * + * @return The current custom insertion callback. + */ + public ActionMode.Callback getCustomInsertionActionModeCallback() { + return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; + } + + /** * @hide */ - protected void stopSelectionActionMode() { + protected void stopTextActionMode() { if (mEditor != null) { - mEditor.stopSelectionActionMode(); + mEditor.stopTextActionMode(); } } @@ -9346,7 +9383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } - stopSelectionActionMode(); + stopTextActionMode(); sLastCutCopyOrTextChangedTime = 0; } } @@ -9359,7 +9396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); getContext().startActivity(Intent.createChooser(sharingIntent, null)); - stopSelectionActionMode(); + stopTextActionMode(); } } diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index 5857de0..55c000c 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -407,6 +407,11 @@ </family> <family> <fileset> + <file>NotoSansSymbols-Regular-Subsetted.ttf</file> + </fileset> + </family> + <family> + <fileset> <file lang="zh-Hans">NotoSansSC-Regular.otf</file> </fileset> </family> @@ -432,11 +437,6 @@ </family> <family> <fileset> - <file>NotoSansSymbols-Regular-Subsetted.ttf</file> - </fileset> - </family> - <family> - <fileset> <file>NotoColorEmoji.ttf</file> </fileset> </family> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 62da0ff..dbe81fa 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -331,6 +331,9 @@ <family> <font weight="400" style="normal">NotoSansYi-Regular.ttf</font> </family> + <family> + <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> + </family> <family lang="zh-Hans"> <font weight="400" style="normal">NotoSansSC-Regular.otf</font> </family> @@ -347,9 +350,6 @@ <font weight="400" style="normal">NanumGothic.ttf</font> </family> <family> - <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> - </family> - <family> <font weight="400" style="normal">NotoColorEmoji.ttf</font> </family> <family> diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java index aa2aa20..23a3ee3 100644 --- a/graphics/java/android/graphics/drawable/RippleComponent.java +++ b/graphics/java/android/graphics/drawable/RippleComponent.java @@ -233,6 +233,10 @@ abstract class RippleComponent { if (mHasPendingHardwareAnimator) { mHasPendingHardwareAnimator = false; + + // Manually jump values to their exited state. Normally we'd do that + // later when starting the hardware exit, but we're aborting early. + jumpValuesToExit(); } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java new file mode 100644 index 0000000..20db41b --- /dev/null +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyFactorySpi.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 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.security.keystore; + +import android.security.Credentials; +import android.security.KeyStore; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * {@link KeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass) + throws InvalidKeySpecException { + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } + if (!(key instanceof AndroidKeyStorePrivateKey)) { + throw new InvalidKeySpecException("Only Android KeyStore private keys supported: " + + ((key != null) ? key.getClass().getName() : "null")); + } + if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpecClass)) { + throw new InvalidKeySpecException( + "Key material export of Android KeyStore keys is not supported"); + } + if (!KeyInfo.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_PRIVATE_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_PRIVATE_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + + @SuppressWarnings("unchecked") + T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo( + mKeyStore, entryAlias, keyAliasInKeystore); + return result; + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException { + throw new UnsupportedOperationException( + "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException { + throw new UnsupportedOperationException( + "To generate a key pair in Android KeyStore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected Key engineTranslateKey(Key arg0) throws InvalidKeyException { + throw new UnsupportedOperationException( + "To import a key into Android KeyStore, use KeyStore.setEntry with " + + KeyProtection.class.getName()); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index de4213e..cb270bb 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -53,6 +53,10 @@ public class AndroidKeyStoreProvider extends Provider { put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + // java.security.KeyFactory + putKeyFactoryImpl("EC"); + putKeyFactoryImpl("RSA"); + // javax.crypto.KeyGenerator put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1"); @@ -101,6 +105,10 @@ public class AndroidKeyStoreProvider extends Provider { put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi"); } + private void putKeyFactoryImpl(String algorithm) { + put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi"); + } + /** * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto * primitive. diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 455f170..8b00821 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -21,9 +21,8 @@ import android.security.KeyStore; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; -import libcore.util.EmptyArray; - import java.security.InvalidKeyException; +import java.security.ProviderException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.ArrayList; @@ -35,7 +34,7 @@ import javax.crypto.SecretKeyFactorySpi; import javax.crypto.spec.SecretKeySpec; /** - * {@link SecretKeyFactorySpi} backed by Android KeyStore. + * {@link SecretKeyFactorySpi} backed by Android Keystore. * * @hide */ @@ -60,7 +59,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { if (!KeyInfo.class.equals(keySpecClass)) { throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); } - String keyAliasInKeystore = ((AndroidKeyStoreSecretKey) key).getAlias(); + String keyAliasInKeystore = ((AndroidKeyStoreKey) key).getAlias(); String entryAlias; if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); @@ -68,11 +67,15 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); } + return getKeyInfo(mKeyStore, entryAlias, keyAliasInKeystore); + } + + static KeyInfo getKeyInfo(KeyStore keyStore, String entryAlias, String keyAliasInKeystore) { KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); int errorCode = - mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); + keyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw new InvalidKeySpecException("Failed to obtain information about key." + throw new ProviderException("Failed to obtain information about key." + " Keystore error: " + errorCode); } @@ -81,6 +84,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { int keySize; @KeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; + String[] signaturePaddings; @KeyProperties.DigestEnum String[] digests; @KeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; @@ -95,29 +99,40 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { origin = KeyProperties.Origin.fromKeymaster( keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1)); } else { - throw new InvalidKeySpecException("Key origin not available"); + throw new ProviderException("Key origin not available"); } Integer keySizeInteger = keyCharacteristics.getInteger(KeymasterDefs.KM_TAG_KEY_SIZE); if (keySizeInteger == null) { - throw new InvalidKeySpecException("Key size not available"); + throw new ProviderException("Key size not available"); } keySize = keySizeInteger; purposes = KeyProperties.Purpose.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PURPOSE)); List<String> encryptionPaddingsList = new ArrayList<String>(); + List<String> signaturePaddingsList = new ArrayList<String>(); + // Keymaster stores both types of paddings in the same array -- we split it into two. for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - @KeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); + @KeyProperties.EncryptionPaddingEnum String jcaPadding = + KeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); + encryptionPaddingsList.add(jcaPadding); } catch (IllegalArgumentException e) { - throw new InvalidKeySpecException( - "Unsupported encryption padding: " + keymasterPadding); + try { + @KeyProperties.SignaturePaddingEnum String padding = + KeyProperties.SignaturePadding.fromKeymaster(keymasterPadding); + signaturePaddingsList.add(padding); + } catch (IllegalArgumentException e2) { + throw new ProviderException( + "Unsupported encryption padding: " + keymasterPadding); + } } - encryptionPaddingsList.add(jcaPadding); + } encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); + signaturePaddings = + signaturePaddingsList.toArray(new String[signaturePaddingsList.size()]); digests = KeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); @@ -128,7 +143,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { keymasterHwEnforcedUserAuthenticators = keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); } catch (IllegalArgumentException e) { - throw new InvalidKeySpecException("Unsupported key characteristic", e); + throw new ProviderException("Unsupported key characteristic", e); } Date keyValidityStart = keyCharacteristics.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME); @@ -164,7 +179,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { keyValidityForConsumptionEnd, purposes, encryptionPaddings, - EmptyArray.STRING, // no signature paddings -- this is symmetric crypto + signaturePaddings, digests, blockModes, userAuthenticationRequired, @@ -175,12 +190,14 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { @Override protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { throw new UnsupportedOperationException( - "Key import into Android KeyStore is not supported"); + "To generate secret key in Android KeyStore, use KeyGenerator initialized with " + + KeyGenParameterSpec.class.getName()); } @Override protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { throw new UnsupportedOperationException( - "Key import into Android KeyStore is not supported"); + "To import a secret key into Android KeyStore, use KeyStore.setEntry with " + + KeyProtection.class.getName()); } } diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 6815254..23339ce 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -116,16 +116,28 @@ public: set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag); } - int64_t operator[](FrameInfoIndex index) const { + inline int64_t operator[](FrameInfoIndex index) const { if (index == FrameInfoIndex::NumIndexes) return 0; return mFrameInfo[static_cast<int>(index)]; } - int64_t operator[](int index) const { + inline int64_t operator[](int index) const { if (index < 0 || index >= static_cast<int>(FrameInfoIndex::NumIndexes)) return 0; return mFrameInfo[index]; } + inline int64_t duration(FrameInfoIndex start, FrameInfoIndex end) const { + int64_t endtime = mFrameInfo[static_cast<int>(end)]; + int64_t starttime = mFrameInfo[static_cast<int>(start)]; + int64_t gap = endtime - starttime; + gap = starttime > 0 ? gap : 0; + return gap > 0 ? gap : 0; + } + + inline int64_t totalDuration() const { + return duration(FrameInfoIndex::IntendedVsync, FrameInfoIndex::FrameCompleted); + } + private: inline int64_t& set(FrameInfoIndex index) { return mFrameInfo[static_cast<int>(index)]; diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index 9557cb0..5b81ac9 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -30,11 +30,13 @@ // Must be NUM_ELEMENTS in size static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; static const SkColor THRESHOLD_COLOR = 0xff5faa4d; -static const SkColor BAR_ALPHA = 0xCF000000; +static const SkColor BAR_FAST_ALPHA = 0x8F000000; +static const SkColor BAR_JANKY_ALPHA = 0xDF000000; // We could get this from TimeLord and use the actual frame interval, but // this is good enough #define FRAME_THRESHOLD 16 +#define FRAME_THRESHOLD_NS 16000000 namespace android { namespace uirenderer { @@ -45,12 +47,10 @@ struct BarSegment { SkColor color; }; -static const std::array<BarSegment,9> Bar {{ - { FrameInfoIndex::IntendedVsync, FrameInfoIndex::Vsync, 0x00695C }, - { FrameInfoIndex::Vsync, FrameInfoIndex::HandleInputStart, 0x00796B }, - { FrameInfoIndex::HandleInputStart, FrameInfoIndex::AnimationStart, 0x00897B }, - { FrameInfoIndex::AnimationStart, FrameInfoIndex::PerformTraversalsStart, 0x009688 }, - { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, 0x26A69A}, +static const std::array<BarSegment,7> Bar {{ + { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, 0x00796B }, + { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, 0x388E3C }, + { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, 0x689F38}, { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, 0x2196F3}, { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, 0x4FC3F7}, { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, 0xF44336}, @@ -74,7 +74,6 @@ void FrameInfoVisualizer::setDensity(float density) { if (CC_UNLIKELY(mDensity != density)) { mDensity = density; mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); - mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); } } @@ -103,73 +102,109 @@ void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) { } if (mType == ProfileType::Bars) { - initializeRects(canvas->getViewportHeight()); + // Patch up the current frame to pretend we ended here. CanvasContext + // will overwrite these values with the real ones after we return. + // This is a bit nicer looking than the vague green bar, as we have + // valid data for almost all the stages and a very good idea of what + // the issue stage will look like, too + FrameInfo& info = mFrameSource.back(); + info.markSwapBuffers(); + info.markFrameCompleted(); + + initializeRects(canvas->getViewportHeight(), canvas->getViewportWidth()); drawGraph(canvas); - drawCurrentFrame(canvas->getViewportHeight(), canvas); drawThreshold(canvas); } } void FrameInfoVisualizer::createData() { - if (mRects.get()) return; + if (mFastRects.get()) return; - mRects.reset(new float[mFrameSource.capacity() * 4]); + mFastRects.reset(new float[mFrameSource.capacity() * 4]); + mJankyRects.reset(new float[mFrameSource.capacity() * 4]); } void FrameInfoVisualizer::destroyData() { - mRects.reset(nullptr); + mFastRects.reset(nullptr); + mJankyRects.reset(nullptr); } -void FrameInfoVisualizer::initializeRects(const int baseline) { - float left = 0; +void FrameInfoVisualizer::initializeRects(const int baseline, const int width) { + // Target the 95% mark for the current frame + float right = width * .95; + float baseLineWidth = right / mFrameSource.capacity(); + mNumFastRects = 0; + mNumJankyRects = 0; + int fast_i = 0, janky_i = 0; // Set the bottom of all the shapes to the baseline - for (size_t i = 0; i < (mFrameSource.capacity() * 4); i += 4) { + for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) { + if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) { + continue; + } + float lineWidth = baseLineWidth; + float* rect; + int ri; // Rects are LTRB - mRects[i + 0] = left; - mRects[i + 1] = baseline; - left += mHorizontalUnit; - mRects[i + 2] = left; - mRects[i + 3] = baseline; + if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) { + rect = mFastRects.get(); + ri = fast_i; + fast_i += 4; + mNumFastRects++; + } else { + rect = mJankyRects.get(); + ri = janky_i; + janky_i += 4; + mNumJankyRects++; + lineWidth *= 2; + } + + rect[ri + 0] = right - lineWidth; + rect[ri + 1] = baseline; + rect[ri + 2] = right; + rect[ri + 3] = baseline; + right -= lineWidth; } } void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) { - for (size_t fi = 0, ri = 0; fi < mFrameSource.size(); fi++, ri += 4) { - // TODO: Skipped frames will leave little holes in the graph, but this - // is better than bogus and freaky lines, so... + int fast_i = (mNumFastRects - 1) * 4; + int janky_i = (mNumJankyRects - 1) * 4;; + for (size_t fi = 0; fi < mFrameSource.size(); fi++) { if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) { continue; } + float* rect; + int ri; + // Rects are LTRB + if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) { + rect = mFastRects.get(); + ri = fast_i; + fast_i -= 4; + } else { + rect = mJankyRects.get(); + ri = janky_i; + janky_i -= 4; + } + // Set the bottom to the old top (build upwards) - mRects[ri + 3] = mRects[ri + 1]; + rect[ri + 3] = rect[ri + 1]; // Move the top up by the duration - mRects[ri + 1] -= mVerticalUnit * duration(fi, start, end); + rect[ri + 1] -= mVerticalUnit * duration(fi, start, end); } } void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) { SkPaint paint; for (size_t i = 0; i < Bar.size(); i++) { - paint.setColor(Bar[i].color | BAR_ALPHA); nextBarSegment(Bar[i].start, Bar[i].end); - canvas->drawRects(mRects.get(), (mFrameSource.size() - 1) * 4, &paint); + paint.setColor(Bar[i].color | BAR_FAST_ALPHA); + canvas->drawRects(mFastRects.get(), mNumFastRects * 4, &paint); + paint.setColor(Bar[i].color | BAR_JANKY_ALPHA); + canvas->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint); } } -void FrameInfoVisualizer::drawCurrentFrame(const int baseline, OpenGLRenderer* canvas) { - // This draws a solid rect over the entirety of the current frame's shape - // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1] - // which will therefore fully overlap the previously drawn rects - SkPaint paint; - paint.setColor(CURRENT_FRAME_COLOR); - size_t fi = mFrameSource.size() - 1; - size_t ri = fi * 4; - float top = baseline - (mVerticalUnit * duration(fi, - FrameInfoIndex::IntendedVsync, FrameInfoIndex::IssueDrawCommandsStart)); - canvas->drawRect(mRects[ri], top, mRects[ri + 2], baseline, &paint); -} - void FrameInfoVisualizer::drawThreshold(OpenGLRenderer* canvas) { SkPaint paint; paint.setColor(THRESHOLD_COLOR); diff --git a/libs/hwui/FrameInfoVisualizer.h b/libs/hwui/FrameInfoVisualizer.h index 3fa4586..f1dc954 100644 --- a/libs/hwui/FrameInfoVisualizer.h +++ b/libs/hwui/FrameInfoVisualizer.h @@ -54,10 +54,9 @@ private: void createData(); void destroyData(); - void initializeRects(const int baseline); + void initializeRects(const int baseline, const int width); void nextBarSegment(FrameInfoIndex start, FrameInfoIndex end); void drawGraph(OpenGLRenderer* canvas); - void drawCurrentFrame(const int baseline, OpenGLRenderer* canvas); void drawThreshold(OpenGLRenderer* canvas); inline float duration(size_t index, FrameInfoIndex start, FrameInfoIndex end) { @@ -75,17 +74,12 @@ private: FrameInfoSource& mFrameSource; int mVerticalUnit = 0; - int mHorizontalUnit = 0; int mThresholdStroke = 0; - /* - * mRects represents an array of rect shapes, divided into NUM_ELEMENTS - * groups such that each group is drawn with the same paint. - * For example mRects[0] is the array of rect floats suitable for - * OpenGLRenderer:drawRects() that makes up all the FrameTimingData:record - * information. - */ - std::unique_ptr<float[]> mRects; + int mNumFastRects; + std::unique_ptr<float[]> mFastRects; + int mNumJankyRects; + std::unique_ptr<float[]> mJankyRects; bool mShowDirtyRegions = false; SkRect mDirtyRegion; diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h index d822cb2..6895f07 100644 --- a/libs/hwui/utils/RingBuffer.h +++ b/libs/hwui/utils/RingBuffer.h @@ -43,11 +43,11 @@ public: } T& front() { - return this[0]; + return (*this)[0]; } T& back() { - return this[size() - 1]; + return (*this)[size() - 1]; } T& operator[](size_t index) { diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 4b84090..d480696 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -933,6 +933,10 @@ public abstract class TvInputService extends Service { * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the * moment. * + * <p>Note that the current playback position should be equal to or greater than the start + * playback position reported by {@link #onTimeShiftGetStartPosition}. Failure to notifying + * the correct current position might lead to bad user experience. + * * @see #onTimeShiftResume * @see #onTimeShiftPause * @see #onTimeShiftSeekTo @@ -1396,6 +1400,12 @@ public abstract class TvInputService extends Service { notifyTimeShiftStartPositionChanged(startPositionMs); } long currentPositionMs = onTimeShiftGetCurrentPosition(); + if (currentPositionMs < mStartPositionMs) { + Log.w(TAG, "Current position (" + currentPositionMs + ") cannot be earlier than" + + " start position (" + mStartPositionMs + "). Reset to the start " + + "position."); + currentPositionMs = mStartPositionMs; + } if (mCurrentPositionMs != currentPositionMs) { mCurrentPositionMs = currentPositionMs; notifyTimeShiftCurrentPositionChanged(currentPositionMs); diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 9d67ccc..a8451f5 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -41,7 +41,7 @@ <string name="label_color">Color</string> <!-- Label of the duplex mode widget. [CHAR LIMIT=20] --> - <string name="label_duplex">Duplex</string> + <string name="label_duplex">Two-sided</string> <!-- Label of the orientation widget. [CHAR LIMIT=20] --> <string name="label_orientation">Orientation</string> diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index 9c6f67c..2b82b05 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> <include layout="@layout/recents_task_view_header" /> - <FrameLayout + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/lock_to_app_fab" android:layout_width="@dimen/recents_lock_to_app_size" android:layout_height="@dimen/recents_lock_to_app_size" @@ -42,7 +42,7 @@ android:layout_height="@dimen/recents_lock_to_app_icon_size" android:layout_gravity="center" android:src="@drawable/recents_lock_to_app_pin" /> - </FrameLayout> + </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> </FrameLayout> </com.android.systemui.recents.views.TaskView> diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index bbd3e60..7d2b5c87 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.AsyncTask; import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; @@ -40,6 +41,7 @@ import android.util.Pair; import android.view.Display; import android.view.LayoutInflater; import android.view.View; + import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SystemUI; @@ -184,12 +186,16 @@ public class Recents extends SystemUI // Header (for transition) TaskViewHeader mHeaderBar; + final Object mHeaderBarLock = new Object(); TaskStackView mDummyStackView; // Variables to keep track of if we need to start recents after binding boolean mTriggeredFromAltTab; long mLastToggleTime; + Bitmap mThumbnailTransitionBitmapCache; + Task mThumbnailTransitionBitmapCacheKey; + public Recents() { } @@ -360,13 +366,16 @@ public class Recents extends SystemUI void preloadRecentsInternal() { // Preload only the raw task list into a new load plan (which will be consumed by the // RecentsActivity) + ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); + MutableBoolean topTaskHome = new MutableBoolean(true); RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); sInstanceLoadPlan = loader.createLoadPlan(mContext); - - ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask(); - MutableBoolean isTopTaskHome = new MutableBoolean(true); - if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) { - sInstanceLoadPlan.preloadRawTasks(isTopTaskHome.value); + if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) { + sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); + loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value); + TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0); + preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView, + topTaskHome.value); } } @@ -513,12 +522,14 @@ public class Recents extends SystemUI algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds); Rect taskViewSize = algo.getUntransformedTaskViewSize(); int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); - mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null, - false); - mHeaderBar.measure( - View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY)); - mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight); + synchronized (mHeaderBarLock) { + mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null, + false); + mHeaderBar.measure( + View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY)); + mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight); + } } /** Prepares the search bar app widget */ @@ -607,30 +618,27 @@ public class Recents extends SystemUI */ ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { + // Update the destination rect Task toTask = new Task(); TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, topTask.id, toTask); - if (toTransform != null && toTask.key != null) { - Rect toTaskRect = toTransform.rect; - int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); - int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); - Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, - Bitmap.Config.ARGB_8888); - if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { - thumbnail.eraseColor(0xFFff0000); - } else { - Canvas c = new Canvas(thumbnail); - c.scale(toTransform.scale, toTransform.scale); - mHeaderBar.rebindToTask(toTask); - mHeaderBar.draw(c); - c.setBitmap(null); - } - Bitmap thumbnailImmutable = thumbnail.createAshmemBitmap(); - + Rect toTaskRect = toTransform.rect; + Bitmap thumbnail; + if (mThumbnailTransitionBitmapCacheKey != null + && mThumbnailTransitionBitmapCacheKey.key != null + && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { + thumbnail = mThumbnailTransitionBitmapCache; + mThumbnailTransitionBitmapCacheKey = null; + mThumbnailTransitionBitmapCache = null; + } else { + preloadIcon(topTask); + thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); + } + if (thumbnail != null) { mStartAnimationTriggered = false; return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, - thumbnailImmutable, toTaskRect.left, toTaskRect.top, toTaskRect.width(), + thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(), toTaskRect.height(), mHandler, this); } @@ -638,6 +646,72 @@ public class Recents extends SystemUI return getUnknownTransitionActivityOptions(); } + /** + * Preloads the icon of a task. + */ + void preloadIcon(ActivityManager.RunningTaskInfo task) { + + // Ensure that we load the running task's icon + RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); + launchOpts.runningTaskId = task.id; + launchOpts.loadThumbnails = false; + launchOpts.onlyLoadForCache = true; + RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts); + } + + /** + * Caches the header thumbnail used for a window animation asynchronously into + * {@link #mThumbnailTransitionBitmapCache}. + */ + void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, + TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) { + preloadIcon(topTask); + + // Update the destination rect + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); + final Task toTask = new Task(); + final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, + topTask.id, toTask); + new AsyncTask<Void, Void, Bitmap>() { + @Override + protected Bitmap doInBackground(Void... params) { + return drawThumbnailTransitionBitmap(toTask, toTransform); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + mThumbnailTransitionBitmapCache = bitmap; + mThumbnailTransitionBitmapCacheKey = toTask; + } + }.execute(); + } + + /** + * Draws the header of a task used for the window animation into a bitmap. + */ + Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { + if (toTransform != null && toTask.key != null) { + Bitmap thumbnail; + synchronized (mHeaderBarLock) { + int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); + int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); + thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, + Bitmap.Config.ARGB_8888); + if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { + thumbnail.eraseColor(0xFFff0000); + } else { + Canvas c = new Canvas(thumbnail); + c.scale(toTransform.scale, toTransform.scale); + mHeaderBar.rebindToTask(toTask); + mHeaderBar.draw(c); + c.setBitmap(null); + } + } + return thumbnail.createAshmemBitmap(); + } + return null; + } + /** Returns the transition rect for the given task id. */ TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, int runningTaskId, Task runningTaskOut) { @@ -694,7 +768,9 @@ public class Recents extends SystemUI return; } - loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); + if (!sInstanceLoadPlan.hasTasks()) { + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); + } ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); TaskStack stack = stacks.get(0); @@ -706,12 +782,6 @@ public class Recents extends SystemUI boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; if (useThumbnailTransition) { - // Ensure that we load the running task's icon - RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); - launchOpts.runningTaskId = topTask.id; - launchOpts.loadThumbnails = false; - launchOpts.onlyLoadForCache = true; - loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts); // Try starting with a thumbnail transition ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index ad97f91..3885799 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -81,6 +81,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Runnables to finish the Recents activity FinishRecentsRunnable mFinishLaunchHomeRunnable; + // Runnable to be executed after we paused ourselves + Runnable mAfterPauseRunnable; + /** * A common Runnable to finish Recents either by calling finish() (with a custom animation) or * launching Home with some ActivityOptions. Generally we always launch home when we exit @@ -441,6 +444,19 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (mConfig.launchedHasConfigurationChanged) { onEnterAnimationTriggered(); } + + if (!mConfig.launchedHasConfigurationChanged) { + mRecentsView.disableLayersForOneFrame(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mAfterPauseRunnable != null) { + mRecentsView.post(mAfterPauseRunnable); + mAfterPauseRunnable = null; + } } @Override @@ -624,6 +640,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView Recents.startScreenPinning(this, ssp); } + @Override + public void runAfterPause(Runnable r) { + mAfterPauseRunnable = r; + } + /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java index 4b5c0bd..3f5d0a8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java @@ -73,4 +73,9 @@ public class FixedSizeImageView extends ImageView { mAllowRelayout = true; mAllowInvalidate = true; } + + @Override + public boolean hasOverlappingRendering() { + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index cec613c..fa97a86 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -24,14 +24,19 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; +import android.os.IRemoteCallback; +import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewStub; import android.view.WindowInsets; +import android.view.WindowManagerGlobal; import android.widget.FrameLayout; + import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsAppWidgetHostView; @@ -52,6 +57,8 @@ import java.util.List; public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks, RecentsPackageMonitor.PackageCallbacks { + private static final String TAG = "RecentsView"; + /** The RecentsView callbacks */ public interface RecentsViewCallbacks { public void onTaskViewClicked(); @@ -59,8 +66,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void onAllTaskViewsDismissed(); public void onExitToHomeAnimationTriggered(); public void onScreenPinningRequest(); - public void onTaskResize(Task t); + public void runAfterPause(Runnable r); } RecentsConfiguration mConfig; @@ -431,11 +438,75 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return false; } + public void disableLayersForOneFrame() { + List<TaskStackView> stackViews = getTaskStackViews(); + for (int i = 0; i < stackViews.size(); i++) { + stackViews.get(i).disableLayersForOneFrame(); + } + } + + private void postDrawHeaderThumbnailTransitionRunnable(final TaskView tv, final int offsetX, + final int offsetY, final TaskViewTransform transform, + final ActivityOptions.OnAnimationStartedListener animStartedListener) { + Runnable r = new Runnable() { + @Override + public void run() { + // Disable any focused state before we draw the header + if (tv.isFocusedTask()) { + tv.unsetFocusedTask(); + } + + float scale = tv.getScaleX(); + int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale); + int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale); + + Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, + Bitmap.Config.ARGB_8888); + if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { + b.eraseColor(0xFFff0000); + } else { + Canvas c = new Canvas(b); + c.scale(tv.getScaleX(), tv.getScaleY()); + tv.mHeaderView.draw(c); + c.setBitmap(null); + } + b = b.createAshmemBitmap(); + int[] pts = new int[2]; + tv.getLocationOnScreen(pts); + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionAspectScaledThumb(b, + pts[0] + offsetX, + pts[1] + offsetY, + transform.rect.width(), + transform.rect.height(), + new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) + throws RemoteException { + post(new Runnable() { + @Override + public void run() { + if (animStartedListener != null) { + animStartedListener.onAnimationStarted(); + } + } + }); + } + }, true); + } catch (RemoteException e) { + Log.w(TAG, "Error overriding app transition", e); + } + } + }; + mCb.runAfterPause(r); + } /**** TaskStackView.TaskStackCallbacks Implementation ****/ @Override public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, final TaskStack stack, final Task task, final boolean lockToTask) { + // Notify any callbacks of the launching of a new task if (mCb != null) { mCb.onTaskViewClicked(); @@ -466,31 +537,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV ActivityOptions opts = null; if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { - Bitmap b; - if (tv != null) { - // Disable any focused state before we draw the header - if (tv.isFocusedTask()) { - tv.unsetFocusedTask(); - } - - float scale = tv.getScaleX(); - int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale); - int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale); - b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight, - Bitmap.Config.ARGB_8888); - if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { - b.eraseColor(0xFFff0000); - } else { - Canvas c = new Canvas(b); - c.scale(tv.getScaleX(), tv.getScaleY()); - tv.mHeaderView.draw(c); - c.setBitmap(null); - } - } else { - // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap - b = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - } - Bitmap bImmut = b.createAshmemBitmap(); ActivityOptions.OnAnimationStartedListener animStartedListener = null; if (lockToTask) { animStartedListener = new ActivityOptions.OnAnimationStartedListener() { @@ -509,6 +555,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } }; } + if (tv != null) { + postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform, + animStartedListener); + } if (mConfig.multiStackEnabled) { opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(), R.anim.recents_from_unknown_enter, @@ -516,7 +566,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV sourceView.getHandler(), animStartedListener); } else { opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, - bImmut, offsetX, offsetY, transform.rect.width(), transform.rect.height(), + Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(), + offsetX, offsetY, transform.rect.width(), transform.rect.height(), sourceView.getHandler(), animStartedListener); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 5f151e8..5711cd6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -19,6 +19,7 @@ package com.android.systemui.recents.views; import android.animation.ValueAnimator; import android.content.ComponentName; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.view.LayoutInflater; @@ -63,7 +64,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onTaskResize(Task t); } - RecentsConfiguration mConfig; TaskStack mStack; @@ -81,7 +81,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean mDismissAllButtonAnimating; int mFocusedTaskIndex = -1; int mPrevAccessibilityFocusedIndex = -1; - // Optimizations int mStackViewsAnimationDuration; boolean mStackViewsDirty = true; @@ -99,6 +98,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>(); List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>(); LayoutInflater mInflater; + boolean mLayersDisabled; // A convenience update listener to request updating clipping of tasks ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = @@ -375,7 +375,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (tv == null) { tv = mViewPool.pickUpViewFromPool(task, task); - + if (mLayersDisabled) { + tv.disableLayersForOneFrame(); + } if (mStackViewsAnimationDuration > 0) { // For items in the list, put them in start animating them from the // approriate ends of the list where they are expected to appear @@ -1031,6 +1033,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mUIDozeTrigger.poke(); } + @Override + protected void dispatchDraw(Canvas canvas) { + mLayersDisabled = false; + super.dispatchDraw(canvas); + } + + public void disableLayersForOneFrame() { + mLayersDisabled = true; + List<TaskView> taskViews = getTaskViews(); + for (int i = 0; i < taskViews.size(); i++) { + taskViews.get(i).disableLayersForOneFrame(); + } + } + /**** TaskStackCallbacks Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 4a5fef3..5906ef1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -367,7 +367,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, .setStartDelay(delay) .setDuration(duration) .setInterpolator(PhoneStatusBar.ALPHA_IN) - .withLayer() .start(); } @@ -416,7 +415,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, .setStartDelay(0) .setDuration(mConfig.taskViewExitToAppDuration) .setInterpolator(mConfig.fastOutLinearInInterpolator) - .withLayer() .start(); } else { // Hide the dismiss button @@ -651,6 +649,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } } + public void disableLayersForOneFrame() { + mHeaderView.disableLayersForOneFrame(); + } + /**** TaskCallbacks Implementation ****/ /** Binds this task view to the task */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 82f0f31..062ded2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -80,6 +80,8 @@ public class TaskViewHeader extends FrameLayout { Paint mDimLayerPaint = new Paint(); PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP); + boolean mLayersDisabled; + public TaskViewHeader(Context context) { this(context, null); } @@ -172,7 +174,9 @@ public class TaskViewHeader extends FrameLayout { void setDimAlpha(int alpha) { mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0)); mDimLayerPaint.setColorFilter(mDimColorFilter); - setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); + if (!mLayersDisabled) { + setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); + } } /** Returns the secondary color for a primary color. */ @@ -262,7 +266,6 @@ public class TaskViewHeader extends FrameLayout { .setStartDelay(0) .setInterpolator(mConfig.fastOutSlowInInterpolator) .setDuration(mConfig.taskViewExitToAppDuration) - .withLayer() .start(); } } @@ -277,7 +280,6 @@ public class TaskViewHeader extends FrameLayout { .setStartDelay(0) .setInterpolator(mConfig.fastOutLinearInInterpolator) .setDuration(mConfig.taskViewEnterFromAppDuration) - .withLayer() .start(); } } @@ -304,6 +306,28 @@ public class TaskViewHeader extends FrameLayout { return new int[] {}; } + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mLayersDisabled) { + mLayersDisabled = false; + postOnAnimation(new Runnable() { + @Override + public void run() { + mLayersDisabled = false; + setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint); + } + }); + } + } + + public void disableLayersForOneFrame() { + mLayersDisabled = true; + + // Disable layer for a frame so we can draw our first frame faster. + setLayerType(LAYER_TYPE_NONE, null); + } + /** Notifies the associated TaskView has been focused. */ void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { // If we are not animating the visible state, just return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index fa172a4..f05ac1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1030,9 +1030,7 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void toggleRecentApps() { - int msg = MSG_TOGGLE_RECENTS_APPS; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); + toggleRecents(); } @Override |
