diff options
22 files changed, 422 insertions, 694 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7d39a0cc20d9..e17bdd7d9a3e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4191,7 +4191,10 @@ public final class ViewRootImpl implements ViewParent, if (mPointerIconShape != pointerShape) { mPointerIconShape = pointerShape; - event.getDevice().setPointerShape(pointerShape); + final InputDevice inputDevice = event.getDevice(); + if (inputDevice != null) { + inputDevice.setPointerShape(pointerShape); + } } } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED; diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk deleted file mode 100644 index 8baadba982dd..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk +++ /dev/null @@ -1,14 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := TestDocumentsProvider -LOCAL_CERTIFICATE := platform -LOCAL_MODULE_TAGS := tests -#LOCAL_SDK_VERSION := current - -LOCAL_PROGUARD_ENABLED := disabled -LOCAL_DEX_PREOPT := false - -include $(BUILD_PACKAGE) diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml deleted file mode 100644 index 66988a17db8b..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.documentsui.testing"> - <application> - <provider android:name="TestDocumentsProvider" - android:authorities="com.android.documentsui.testing" - android:exported="true" - android:grantUriPermissions="true" - android:permission="android.permission.MANAGE_DOCUMENTS"> - <intent-filter> - <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> - </intent-filter> - </provider> - </application> -</manifest> diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java deleted file mode 100644 index 63ff0de56998..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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 com.android.documentsui.testing; - -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MatrixCursor.RowBuilder; -import android.os.AsyncTask; -import android.os.CancellationSignal; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.Document; -import android.provider.DocumentsContract.Root; -import android.provider.DocumentsProvider; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TestDocumentsProvider extends DocumentsProvider { - private static final String TAG = "TestDocumentsProvider"; - - private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_FLAGS, - Root.COLUMN_ICON, - Root.COLUMN_TITLE, - Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, - }; - - private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { - Document.COLUMN_DOCUMENT_ID, - Document.COLUMN_MIME_TYPE, - Document.COLUMN_DISPLAY_NAME, - Document.COLUMN_LAST_MODIFIED, - Document.COLUMN_FLAGS, - Document.COLUMN_SIZE, - }; - - private static String[] resolveRootProjection(String[] projection) { - return projection != null ? projection : DEFAULT_ROOT_PROJECTION; - } - - private static String[] resolveDocumentProjection(String[] projection) { - return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; - } - - @Override - public boolean onCreate() { - resetRoots(); - return true; - } - - @Override - public Cursor queryRoots(String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); - - RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, "local"); - row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); - row.add(Root.COLUMN_TITLE, "TEST-Local"); - row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary"); - row.add(Root.COLUMN_DOCUMENT_ID, "doc:local"); - - row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, "create"); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, "TEST-Create"); - row.add(Root.COLUMN_DOCUMENT_ID, "doc:create"); - - return result; - } - - private Map<String, Doc> mDocs = new HashMap<>(); - - private Doc mLocalRoot; - private Doc mCreateRoot; - - private Doc buildDoc(String docId, String displayName, String mimeType) { - final Doc doc = new Doc(); - doc.docId = docId; - doc.displayName = displayName; - doc.mimeType = mimeType; - mDocs.put(doc.docId, doc); - return doc; - } - - public void resetRoots() { - Log.d(TAG, "resetRoots()"); - - mDocs.clear(); - - mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR); - - mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR); - mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE; - - { - Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1"); - file1.contents = "fileone".getBytes(); - file1.flags = Document.FLAG_SUPPORTS_WRITE; - mLocalRoot.children.add(file1); - mCreateRoot.children.add(file1); - } - - { - Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2"); - file2.contents = "filetwo".getBytes(); - file2.flags = Document.FLAG_SUPPORTS_WRITE; - mLocalRoot.children.add(file2); - mCreateRoot.children.add(file2); - } - - Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR); - mLocalRoot.children.add(dir1); - - { - Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3"); - file3.contents = "filethree".getBytes(); - file3.flags = Document.FLAG_SUPPORTS_WRITE; - dir1.children.add(file3); - } - - Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR); - mCreateRoot.children.add(dir2); - - { - Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4"); - file4.contents = "filefour".getBytes(); - file4.flags = Document.FLAG_SUPPORTS_WRITE; - dir2.children.add(file4); - } - } - - private static class Doc { - public String docId; - public int flags; - public String displayName; - public long size; - public String mimeType; - public long lastModified; - public byte[] contents; - public List<Doc> children = new ArrayList<>(); - - public void include(MatrixCursor result) { - final RowBuilder row = result.newRow(); - row.add(Document.COLUMN_DOCUMENT_ID, docId); - row.add(Document.COLUMN_DISPLAY_NAME, displayName); - row.add(Document.COLUMN_SIZE, size); - row.add(Document.COLUMN_MIME_TYPE, mimeType); - row.add(Document.COLUMN_FLAGS, flags); - row.add(Document.COLUMN_LAST_MODIFIED, lastModified); - } - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - for (Doc doc : mDocs.get(parentDocumentId).children) { - if (doc.docId.equals(documentId)) { - return true; - } - if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) { - return isChildDocument(doc.docId, documentId); - } - } - return false; - } - - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) - throws FileNotFoundException { - final String docId = "doc:" + System.currentTimeMillis(); - final Doc doc = buildDoc(docId, displayName, mimeType); - doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME; - mDocs.get(parentDocumentId).children.add(doc); - return docId; - } - - @Override - public String renameDocument(String documentId, String displayName) - throws FileNotFoundException { - mDocs.get(documentId).displayName = displayName; - return null; - } - - @Override - public void deleteDocument(String documentId) throws FileNotFoundException { - mDocs.remove(documentId); - for (Doc doc : mDocs.values()) { - doc.children.remove(documentId); - } - } - - @Override - public Cursor queryDocument(String documentId, String[] projection) - throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - mDocs.get(documentId).include(result); - return result; - } - - @Override - public Cursor queryChildDocuments(String parentDocumentId, String[] projection, - String sortOrder) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - for (Doc doc : mDocs.get(parentDocumentId).children) { - doc.include(result); - } - return result; - } - - @Override - public ParcelFileDescriptor openDocument(String documentId, String mode, - CancellationSignal signal) throws FileNotFoundException { - final Doc doc = mDocs.get(documentId); - if (doc == null) { - throw new FileNotFoundException(); - } - final ParcelFileDescriptor[] pipe; - try { - pipe = ParcelFileDescriptor.createPipe(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - if (mode.contains("w")) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - synchronized (doc) { - try { - final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( - pipe[0]); - doc.contents = readFullyNoClose(is); - is.close(); - doc.notifyAll(); - } catch (IOException e) { - Log.w(TAG, "Failed to stream", e); - } - } - return null; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - return pipe[1]; - } else { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - synchronized (doc) { - try { - final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream( - pipe[1]); - while (doc.contents == null) { - doc.wait(); - } - os.write(doc.contents); - os.close(); - } catch (IOException e) { - Log.w(TAG, "Failed to stream", e); - } catch (InterruptedException e) { - Log.w(TAG, "Interuppted", e); - } - } - return null; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - return pipe[0]; - } - } - - private static byte[] readFullyNoClose(InputStream in) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } -} diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk index cf486b1d1a5d..2a540d4d77e9 100644 --- a/packages/DocumentsUI/tests/Android.mk +++ b/packages/DocumentsUI/tests/Android.mk @@ -17,4 +17,3 @@ LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE) -include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bae801716c19..07c59a9b3b94 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -136,7 +136,7 @@ <integer name="touch_acceptance_delay">700</integer> <!-- The duration in seconds to wait before the dismiss buttons are shown. --> - <integer name="recents_task_bar_dismiss_delay_seconds">1</integer> + <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer> <!-- The min animation duration for animating views that are currently visible. --> <integer name="recents_filter_animate_current_views_duration">250</integer> diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index cdb6b932de9f..6668df93511d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -37,6 +37,8 @@ public class Constants { public static final boolean EnableTaskFiltering = false; // Enables dismiss-all public static final boolean EnableDismissAll = false; + // Enables fast-toggling + public static final boolean EnableFastToggleRecents = false; // Enables the thumbnail alpha on the front-most task public static final boolean EnableThumbnailAlphaOnFrontmost = false; // This disables the search bar integration diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index c416967599f6..0adad855628f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; @@ -41,16 +42,21 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.ResizeTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.misc.Console; +import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsPackageMonitor; @@ -69,6 +75,9 @@ import java.util.ArrayList; */ public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks { + private final static String TAG = "RecentsActivity"; + private final static boolean DEBUG = false; + public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; RecentsConfiguration mConfig; @@ -96,6 +105,14 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Runnable to be executed after we paused ourselves Runnable mAfterPauseRunnable; + // The trigger to automatically launch the current task + DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() { + @Override + public void run() { + boolean dismissed = dismissRecentsToFocusedTask(false); + } + }); + /** * 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 @@ -244,7 +261,24 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView MetricsLogger.histogram(this, "overview_task_count", taskCount); } - /** Dismisses recents if we are already visible and the intent is to toggle the recents view */ + /** + * Dismisses recents if we are already visible and the intent is to toggle the recents view. + */ + boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) { + // If we currently have filtered stacks, then unfilter those first + if (checkFilteredStackState && + mRecentsView.unfilterFilteredStacks()) return true; + // If we have a focused Task, launch that Task now + if (mRecentsView.launchFocusedTask()) return true; + } + return false; + } + + /** + * Dismisses recents if we are already visible and the intent is to toggle the recents view. + */ boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) { RecentsActivityLaunchState launchState = mConfig.getLaunchState(); SystemServicesProxy ssp = Recents.getSystemServices(); @@ -390,6 +424,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mRecentsView.post(mAfterPauseRunnable); mAfterPauseRunnable = null; } + + if (Constants.DebugFlags.App.EnableFastToggleRecents) { + // Stop the fast-toggle dozer + mIterateTrigger.stopDozing(); + } } @Override @@ -467,22 +506,27 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) { // Focus the next task in the stack final boolean backward = event.isShiftPressed(); - mRecentsView.focusNextTask(!backward); + if (backward) { + EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); + } else { + EventBus.getDefault().send(new FocusNextTaskViewEvent()); + } mLastTabKeyEventTime = SystemClock.elapsedRealtime(); } return true; } case KeyEvent.KEYCODE_DPAD_UP: { - mRecentsView.focusNextTask(true); + EventBus.getDefault().send(new FocusNextTaskViewEvent()); return true; } case KeyEvent.KEYCODE_DPAD_DOWN: { - mRecentsView.focusNextTask(false); + EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); return true; } case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_FORWARD_DEL: { - mRecentsView.dismissFocusedTask(); + EventBus.getDefault().send(new DismissFocusedTaskViewEvent()); + // Keep track of deletions by keyboard MetricsLogger.histogram(this, "overview_task_dismissed_source", Constants.Metrics.DismissSourceKeyboard); @@ -542,6 +586,16 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */); } + public final void onBusEvent(IterateRecentsEvent event) { + // Focus the next task + EventBus.getDefault().send(new FocusNextTaskViewEvent()); + mIterateTrigger.poke(); + } + + public final void onBusEvent(UserInteractionEvent event) { + mIterateTrigger.stopDozing(); + } + public final void onBusEvent(HideRecentsEvent event) { if (event.triggeredFromAltTab) { // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app @@ -558,7 +612,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Try and start the enter animation (or restart it on configuration changed) ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null); ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t); - mRecentsView.startEnterRecentsAnimation(ctx); + ctx.postAnimationTrigger.increment(); if (mSearchWidgetInfo != null) { ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { @Override @@ -570,6 +624,20 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } }); } + ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + // If we are not launching with alt-tab and fast-toggle is enabled, then start + // the dozer now + RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + if (Constants.DebugFlags.App.EnableFastToggleRecents && + !launchState.launchedWithAltTab) { + mIterateTrigger.startDozing(); + } + } + }); + mRecentsView.startEnterRecentsAnimation(ctx); + ctx.postAnimationTrigger.decrement(); } public final void onBusEvent(AppWidgetProviderChangedEvent event) { @@ -589,7 +657,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView MetricsLogger.count(this, "overview_app_info", 1); } - public final void onBusEvent(DismissTaskEvent event) { + public final void onBusEvent(DismissTaskViewEvent event) { // Remove any stored data from the loader RecentsTaskLoader loader = Recents.getTaskLoader(); loader.deleteTaskData(event.task, false); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 3a30a8fbc820..2c8937aa615f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -40,6 +40,7 @@ import com.android.systemui.SystemUIApplication; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; @@ -265,26 +266,38 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub mTriggeredFromAltTab = false; try { - // If the user has toggled it too quickly, then just eat up the event here (it's better - // than showing a janky screenshot). - // NOTE: Ideally, the screenshot mechanism would take the window transform into account - if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { - return; - } - - // If Recents is the front most activity, then we should just communicate with it - // directly to launch the first task or dismiss itself SystemServicesProxy ssp = Recents.getSystemServices(); ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); MutableBoolean isTopTaskHome = new MutableBoolean(true); if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) { - // Notify recents to toggle itself - EventBus.getDefault().post(new ToggleRecentsEvent()); - mLastToggleTime = SystemClock.elapsedRealtime(); + if (Constants.DebugFlags.App.EnableFastToggleRecents) { + // Notify recents to move onto the next task + EventBus.getDefault().post(new IterateRecentsEvent()); + } else { + // If the user has toggled it too quickly, then just eat up the event here (it's + // better than showing a janky screenshot). + // NOTE: Ideally, the screenshot mechanism would take the window transform into + // account + if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { + return; + } + + EventBus.getDefault().post(new ToggleRecentsEvent()); + mLastToggleTime = SystemClock.elapsedRealtime(); + } return; } else { + // If the user has toggled it too quickly, then just eat up the event here (it's + // better than showing a janky screenshot). + // NOTE: Ideally, the screenshot mechanism would take the window transform into + // account + if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { + return; + } + // Otherwise, start the recents activity startRecentsActivity(topTask, isTopTaskHome.value); + mLastToggleTime = SystemClock.elapsedRealtime(); } } catch (ActivityNotFoundException e) { Console.logRawError("Failed to launch RecentAppsIntent", e); diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java index fec0fc57a766..deae4c8d5ff9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java @@ -200,7 +200,8 @@ class EventHandlerMethod { */ public class EventBus extends BroadcastReceiver { - public static final String TAG = "EventBus"; + private static final String TAG = "EventBus"; + private static final boolean DEBUG_TRACE_ALL = false; /** * An event super class that allows us to track internal event state across subscriber @@ -277,9 +278,6 @@ public class EventBus extends BroadcastReceiver { // The default priority of all subscribers private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1; - // Used for debugging everything - private static final boolean DEBUG_TRACE_ALL = false; - // Orders the handlers by priority and registration time private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java new file mode 100644 index 000000000000..f7b2706b9c57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java @@ -0,0 +1,27 @@ +/* + * 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 com.android.systemui.recents.events.activity; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when the user taps on the Overview button to iterate to the next item in the + * Recents list. + */ +public class IterateRecentsEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java index 12e5d3d61cf7..968890aea2f1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java @@ -23,12 +23,12 @@ import com.android.systemui.recents.views.TaskView; /** * This is sent when a {@link TaskView} has been dismissed. */ -public class DismissTaskEvent extends EventBus.Event { +public class DismissTaskViewEvent extends EventBus.Event { public final Task task; public final TaskView taskView; - public DismissTaskEvent(Task task, TaskView taskView) { + public DismissTaskViewEvent(Task task, TaskView taskView) { this.task = task; this.taskView = taskView; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java new file mode 100644 index 000000000000..9f3e9d5e3362 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.systemui.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Dismisses the currently focused task view. + */ +public class DismissFocusedTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java new file mode 100644 index 000000000000..171ab5e8bcca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.systemui.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Focuses the next task view in the stack. + */ +public class FocusNextTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java new file mode 100644 index 000000000000..22469e758e70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.systemui.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Focuses the previous task view in the stack. + */ +public class FocusPreviousTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java index 735f79f4021d..336d2db443f9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java @@ -19,8 +19,8 @@ package com.android.systemui.recents.misc; import android.os.Handler; /** - * A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to - * wake it up, but it will fall asleep if left untouched. + * A dozer is a class that fires a trigger after it falls asleep. + * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched. */ public class DozeTrigger { @@ -28,7 +28,7 @@ public class DozeTrigger { boolean mIsDozing; boolean mHasTriggered; - int mDozeDurationSeconds; + int mDozeDurationMilliseconds; Runnable mSleepRunnable; // Sleep-runnable @@ -41,9 +41,9 @@ public class DozeTrigger { } }; - public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) { + public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) { mHandler = new Handler(); - mDozeDurationSeconds = dozeDurationSeconds; + mDozeDurationMilliseconds = dozeDurationMilliseconds; mSleepRunnable = sleepRunnable; } @@ -69,7 +69,7 @@ public class DozeTrigger { /** Poke this dozer to wake it up for a little bit. */ void forcePoke() { mHandler.removeCallbacks(mDozeRunnable); - mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000); + mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds); mIsDozing = true; } 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 98e9c756aead..d5d07131701a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -49,7 +49,7 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; @@ -69,6 +69,7 @@ import static android.app.ActivityManager.INVALID_STACK_ID; public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks { private static final String TAG = "RecentsView"; + private static final boolean DEBUG = false; private static final boolean ADD_HEADER_BITMAP = true; @@ -404,22 +405,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return super.verifyDrawable(who); } - /** Focuses the next task in the first stack view */ - public void focusNextTask(boolean forward) { - // Get the first stack view - if (mTaskStackView != null) { - mTaskStackView.focusNextTask(forward, true); - } - } - - /** Dismisses the focused task. */ - public void dismissFocusedTask() { - // Get the first stack view - if (mTaskStackView != null) { - mTaskStackView.dismissFocusedTask(); - } - } - /** Unfilters any filtered stacks */ public boolean unfilterFilteredStacks() { if (mStacks != null) { @@ -562,7 +547,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Disable any focused state before we draw the header // Upfront the processing of the thumbnail if (tv.isFocusedTask()) { - tv.unsetFocusedTask(); + tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); } TaskViewTransform transform = new TaskViewTransform(); transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll, @@ -682,7 +667,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } else { // Dismiss the task and return the user to home if we fail to // launch the task - EventBus.getDefault().send(new DismissTaskEvent(task, tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(task, tv)); if (mCb != null) { mCb.onTaskLaunchFailed(); } 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 592885412009..9ef37335d587 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -23,6 +23,8 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; +import android.os.SystemService; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -30,6 +32,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import com.android.systemui.R; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; @@ -37,8 +40,11 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; +import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; @@ -60,6 +66,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task> { + private final static String TAG = "TaskStackView"; + private final static boolean DEBUG = false; + /** The TaskView callbacks */ interface TaskStackViewCallbacks { public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, @@ -80,14 +89,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); DozeTrigger mUIDozeTrigger; int mFocusedTaskIndex = -1; - int mPrevAccessibilityFocusedIndex = -1; // Optimizations int mStackViewsAnimationDuration; boolean mStackViewsDirty = true; boolean mStackViewsClipDirty = true; boolean mAwaitingFirstLayout = true; boolean mStartEnterAnimationRequestedAfterLayout; - boolean mStartEnterAnimationCompleted; ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; Rect mTaskStackBounds = new Rect(); @@ -219,7 +226,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackViewsDirty = true; mStackViewsClipDirty = true; mAwaitingFirstLayout = true; - mPrevAccessibilityFocusedIndex = -1; if (mUIDozeTrigger != null) { mUIDozeTrigger.stopDozing(); mUIDozeTrigger.resetTrigger(); @@ -332,8 +338,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Synchronizes the views with the model */ boolean synchronizeStackViewsWithModel() { if (mStackViewsDirty) { - SystemServicesProxy ssp = Recents.getSystemServices(); - // Get all the task transforms ArrayList<Task> tasks = mStack.getTasks(); float stackScroll = mStackScroller.getStackScroll(); @@ -344,8 +348,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Return all the invisible children to the pool mTmpTaskViewMap.clear(); List<TaskView> taskViews = getTaskViews(); + boolean wasLastFocusedTaskAnimated = false; + int lastFocusedTaskIndex = -1; int taskViewCount = taskViews.size(); - boolean reaquireAccessibilityFocus = false; for (int i = taskViewCount - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); @@ -353,8 +358,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { mTmpTaskViewMap.put(task, tv); } else { + if (tv.isFocusedTask()) { + wasLastFocusedTaskAnimated = tv.isFocusAnimated(); + lastFocusedTaskIndex = taskIndex; + resetFocusedTask(); + } mViewPool.returnViewToPool(tv); - reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex); } } @@ -385,21 +394,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Animate the task into place tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), mStackViewsAnimationDuration, mRequestUpdateClippingListener); + } - // Request accessibility focus on the next view if we removed the task - // that previously held accessibility focus - if (reaquireAccessibilityFocus) { - taskViews = getTaskViews(); - taskViewCount = taskViews.size(); - if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() && - mPrevAccessibilityFocusedIndex != -1) { - TaskView atv = taskViews.get(taskViewCount - 1); - int indexOfTask = mStack.indexOfTask(atv.getTask()); - if (mPrevAccessibilityFocusedIndex != indexOfTask) { - tv.requestAccessibilityFocus(); - mPrevAccessibilityFocusedIndex = indexOfTask; - } - } + // Update the focus if the previous focused task was returned to the view pool + if (lastFocusedTaskIndex != -1) { + if (lastFocusedTaskIndex < visibleRange[1]) { + setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated); + } else { + setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated); } } @@ -473,118 +475,80 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return mStackScroller; } - /** Focuses the task at the specified index in the stack */ - void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) { - // Return early if the task is already focused - if (taskIndex == mFocusedTaskIndex) return; + /** + * Sets the focused task to the provided (bounded taskIndex). + */ + private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) { + setFocusedTask(taskIndex, scrollToTask, animated, true); + } - if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { - mFocusedTaskIndex = taskIndex; - mPrevAccessibilityFocusedIndex = taskIndex; + /** + * Sets the focused task to the provided (bounded taskIndex). + */ + private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated, + final boolean requestViewFocus) { + // Find the next task to focus + int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? + Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1; + final Task newFocusedTask = (newFocusedTaskIndex != -1) ? + mStack.getTasks().get(newFocusedTaskIndex) : null; + + // Reset the last focused task state if changed + if (mFocusedTaskIndex != -1) { + Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex); + if (focusedTask != newFocusedTask) { + resetFocusedTask(); + } + } - // Focus the view if possible, otherwise, focus the view after we scroll into position - final Task t = mStack.getTasks().get(mFocusedTaskIndex); - Runnable postScrollRunnable = new Runnable() { + mFocusedTaskIndex = newFocusedTaskIndex; + if (mFocusedTaskIndex != -1) { + Runnable focusTaskRunnable = new Runnable() { @Override public void run() { - TaskView tv = getChildViewForTask(t); + TaskView tv = getChildViewForTask(newFocusedTask); if (tv != null) { - tv.setFocusedTask(animateFocusedState); - tv.requestAccessibilityFocus(); + tv.setFocusedState(true, animated, requestViewFocus); } } }; - // Scroll the view into position (just center it in the curve) - if (scrollToNewPosition) { - float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f; + if (scrollToTask) { + // TODO: Center the newly focused task view + float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f; newScroll = mStackScroller.getBoundedStackScroll(newScroll); - mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable); + mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, + focusTaskRunnable); } else { - if (postScrollRunnable != null) { - postScrollRunnable.run(); - } + focusTaskRunnable.run(); } - } } /** - * Ensures that there is a task focused, if nothing is focused, then we will use the task - * at the center of the visible stack. + * Sets the focused task relative to the currently focused task. + * + * @param animated determines whether to actually draw the highlight along with the change in + * focus. */ - public boolean ensureFocusedTask(boolean findClosestToCenter) { - if (mFocusedTaskIndex < 0) { - List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - if (findClosestToCenter) { - // If there is no task focused, then find the task that is closes to the center - // of the screen and use that as the currently focused task - int x = mLayoutAlgorithm.mStackRect.centerX(); - int y = mLayoutAlgorithm.mStackRect.centerY(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - tv.getHitRect(mTmpRect); - if (mTmpRect.contains(x, y)) { - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; - break; - } - } - } - // If we can't find the center task, then use the front most index - if (mFocusedTaskIndex < 0 && taskViewCount > 0) { - TaskView tv = taskViews.get(taskViewCount - 1); - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; - } - } - return mFocusedTaskIndex >= 0; + public void setRelativeFocusedTask(boolean forward, boolean animated) { + // Find the next index to focus + int newIndex = mFocusedTaskIndex + (forward ? -1 : 1); + setFocusedTask(newIndex, true, animated); } /** - * Focuses the next task in the stack. - * @param animateFocusedState determines whether to actually draw the highlight along with - * the change in focus, as well as whether to scroll to fit the - * task into view. + * Resets the focused task. */ - public void focusNextTask(boolean forward, boolean animateFocusedState) { - // Find the next index to focus - int numTasks = mStack.getTaskCount(); - if (numTasks == 0) return; - - int direction = (forward ? -1 : 1); - int newIndex = mFocusedTaskIndex + direction; - if (newIndex >= 0 && newIndex <= (numTasks - 1)) { - newIndex = Math.max(0, Math.min(numTasks - 1, newIndex)); - focusTask(newIndex, true, animateFocusedState); - } - } - - /** Dismisses the focused task. */ - public void dismissFocusedTask() { - // Return early if the focused task index is invalid - if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) { - mFocusedTaskIndex = -1; - return; - } - - Task t = mStack.getTasks().get(mFocusedTaskIndex); - TaskView tv = getChildViewForTask(t); - tv.dismissTask(); - } - - /** Resets the focused task. */ void resetFocusedTask() { - if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) { + if (mFocusedTaskIndex != -1) { Task t = mStack.getTasks().get(mFocusedTaskIndex); TaskView tv = getChildViewForTask(t); if (tv != null) { - tv.unsetFocusedTask(); + tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); } } mFocusedTaskIndex = -1; - mPrevAccessibilityFocusedIndex = -1; } @Override @@ -609,12 +573,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal super.onInitializeAccessibilityNodeInfo(info); List<TaskView> taskViews = getTaskViews(); int taskViewCount = taskViews.size(); - if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) { + if (taskViewCount > 1 && mFocusedTaskIndex != -1) { info.setScrollable(true); - if (mPrevAccessibilityFocusedIndex > 0) { + if (mFocusedTaskIndex > 0) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } - if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { + if (mFocusedTaskIndex < mStack.getTaskCount() - 1) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } } @@ -630,22 +594,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (super.performAccessibilityAction(action, arguments)) { return true; } - if (ensureFocusedTask(false)) { - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (mPrevAccessibilityFocusedIndex > 0) { - focusNextTask(true, false); - return true; - } - } - break; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { - focusNextTask(false, false); - return true; - } - } - break; + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + setRelativeFocusedTask(true, false /* animated */); + return true; + } + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + setRelativeFocusedTask(false, false /* animated */); + return true; } } return false; @@ -678,7 +634,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Computes the stack and task rects */ public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, - boolean launchedWithAltTab, boolean launchedFromHome) { + boolean launchedWithAltTab, boolean launchedFromHome) { // Compute the rects in the stack algorithm mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); @@ -741,12 +697,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTmpRect.setEmpty(); } tv.measure( - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, - MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, + MeasureSpec.EXACTLY)); } setMeasuredDimension(width, height); @@ -815,18 +771,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStartEnterAnimationContext = null; } - // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the - // enter animation). + // Set the task focused state without requesting view focus, and leave the focus animations + // until after the enter-animation RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - if (launchState.launchedWithAltTab) { - if (launchState.launchedFromAppWithThumbnail) { - focusTask(Math.max(0, mStack.getTaskCount() - 2), false, - launchState.launchedHasConfigurationChanged); - } else { - focusTask(Math.max(0, mStack.getTaskCount() - 1), false, - launchState.launchedHasConfigurationChanged); - } - } + int taskOffset = launchState.launchedFromHome ? -1 : -2; + setFocusedTask(mStack.getTaskCount() + taskOffset, false /* scrollToTask */, + false /* animated */, false /* requestViewFocus */); // Start dozing mUIDozeTrigger.startDozing(); @@ -874,32 +824,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { @Override public void run() { - mStartEnterAnimationCompleted = true; // Poke the dozer to restart the trigger after the animation completes mUIDozeTrigger.poke(); - SystemServicesProxy ssp = Recents.getSystemServices(); - List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - if (taskViewCount > 0) { - // Focus the first view if accessibility is enabled - if (ssp.isTouchExplorationEnabled()) { - TaskView tv = taskViews.get(taskViewCount - 1); - tv.requestAccessibilityFocus(); - mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); - } - } - - // Start the focus animation when alt-tabbing - ArrayList<Task> tasks = mStack.getTasks(); - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - if (launchState.launchedWithAltTab && - !launchState.launchedHasConfigurationChanged && - 0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) { - TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex)); - if (tv != null) { - tv.setFocusedTask(true); - } + // Update the focused state here -- since we only set the focused task without + // requesting view focus in onFirstLayout(), actually request view focus and + // animate the focused state if we are alt-tabbing now, after the window enter + // animation is completed + if (mFocusedTaskIndex != -1) { + RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + setFocusedTask(mFocusedTaskIndex, false /* scrollToTask */, + launchState.launchedWithAltTab); } } }); @@ -1132,11 +1067,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void prepareViewToEnterPool(TaskView tv) { Task task = tv.getTask(); - // Clear the accessibility focus for that view - if (tv.isAccessibilityFocused()) { - tv.clearAccessibilityFocus(); - } - // Report that this tasks's data is no longer being used Recents.getTaskLoader().unloadTaskData(task); @@ -1167,11 +1097,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // If the doze trigger has already fired, then update the state for this task view tv.setNoUserInteractionState(); - // If we've finished the start animation, then ensure we always enable the focus animations - if (mStartEnterAnimationCompleted) { - tv.enableFocusAnimations(); - } - // Find the index where this task should be placed in the stack int insertIndex = -1; int taskIndex = mStack.indexOfTask(task); @@ -1231,13 +1156,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - @Override - public void onTaskViewFocusChanged(TaskView tv, boolean focused) { - if (focused) { - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - } - } - /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override @@ -1259,13 +1177,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal for (int i = tasks.size() - 1; i >= 0; i--) { final Task t = tasks.get(i); if (removedComponents.contains(t.key.getComponent())) { - TaskView tv = getChildViewForTask(t); + final TaskView tv = getChildViewForTask(t); if (tv != null) { // For visible children, defer removing the task until after the animation tv.startDeleteTaskAnimation(new Runnable() { @Override public void run() { - mStack.removeTask(t); + removeTaskViewFromStack(tv); } }, 0); } else { @@ -1276,33 +1194,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - public final void onBusEvent(DismissTaskEvent event) { - TaskView tv = event.taskView; - Task task = tv.getTask(); - int taskIndex = mStack.indexOfTask(task); - boolean taskWasFocused = tv.isFocusedTask(); + public final void onBusEvent(DismissTaskViewEvent event) { + removeTaskViewFromStack(event.taskView); + } - // Announce for accessibility - tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, - tv.getTask().activityLabel)); + public final void onBusEvent(FocusNextTaskViewEvent event) { + setRelativeFocusedTask(true, true); + } - // Remove the task from the view - mStack.removeTask(task); + public final void onBusEvent(FocusPreviousTaskViewEvent event) { + setRelativeFocusedTask(false, true); + } - // If the dismissed task was focused, then we should focus the new task in the same index - if (taskWasFocused) { - ArrayList<Task> tasks = mStack.getTasks(); - int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1); - if (nextTaskIndex >= 0) { - Task nextTask = tasks.get(nextTaskIndex); - TaskView nextTv = getChildViewForTask(nextTask); - if (nextTv != null) { - // Focus the next task, and only animate the visible state if we are launched - // from Alt-Tab - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - nextTv.setFocusedTask(launchState.launchedWithAltTab); - } - } + public final void onBusEvent(DismissFocusedTaskViewEvent event) { + if (mFocusedTaskIndex != -1) { + Task t = mStack.getTasks().get(mFocusedTaskIndex); + TaskView tv = getChildViewForTask(t); + tv.dismissTask(); } } @@ -1316,4 +1224,32 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal reset(); } } + + /** + * Removes the task from the stack, and updates the focus to the next task in the stack if the + * removed TaskView was focused. + */ + private void removeTaskViewFromStack(TaskView tv) { + SystemServicesProxy ssp = Recents.getSystemServices(); + Task task = tv.getTask(); + int taskIndex = mStack.indexOfTask(task); + boolean taskWasFocused = tv.isFocusedTask(); + + // Reset the previously focused task before it is removed from the stack + resetFocusedTask(); + + // Announce for accessibility + tv.announceForAccessibility(getContext().getString( + R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel)); + + // Remove the task from the stack + mStack.removeTask(task); + + if (taskWasFocused || ssp.isTouchExplorationEnabled()) { + // If the dismissed task was focused or if we are in touch exploration mode, then focus + // the next task + RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 1274318562ea..3a1a9876926f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -28,7 +28,7 @@ import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import java.util.List; @@ -408,13 +408,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Find the front most task and scroll the next task to the front float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL); if (vScroll > 0) { - if (mSv.ensureFocusedTask(true)) { - mSv.focusNextTask(true, false); - } + mSv.setRelativeFocusedTask(true, false /* animated */); } else { - if (mSv.ensureFocusedTask(true)) { - mSv.focusNextTask(false, false); - } + mSv.setRelativeFocusedTask(false, false /* animated */); } return true; } @@ -461,7 +457,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Re-enable touch events from this task view tv.setTouchEnabled(true); // Remove the task view from the stack - EventBus.getDefault().send(new DismissTaskEvent(tv.getTask(), tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(tv.getTask(), tv)); // Keep track of deletions by keyboard MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source", Constants.Metrics.DismissSourceSwipeGesture); 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 bab4da75f691..0a5ee79b4cb8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -29,8 +29,8 @@ import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; @@ -40,13 +40,15 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.statusbar.phone.PhoneStatusBar; @@ -55,11 +57,13 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar; public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener, View.OnLongClickListener { + private final static String TAG = "TaskView"; + private final static boolean DEBUG = false; + /** The TaskView callbacks */ interface TaskViewCallbacks { public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); public void onTaskViewClipStateChanged(TaskView tv); - public void onTaskViewFocusChanged(TaskView tv, boolean focused); } RecentsConfiguration mConfig; @@ -76,6 +80,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, Task mTask; boolean mTaskDataLoaded; boolean mIsFocused; + boolean mIsFocusAnimated; boolean mFocusAnimationsEnabled; boolean mClipViewInStack; AnimateableViewBounds mViewBounds; @@ -397,15 +402,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, ctx.postAnimationTrigger.increment(); startDelay = delay; } - - // Enable the focus animations from this point onwards so that they aren't affected by the - // window transitions - postDelayed(new Runnable() { - @Override - public void run() { - enableFocusAnimations(); - } - }, startDelay); } public void fadeInActionButton(int delay, int duration) { @@ -547,7 +543,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, startDeleteTaskAnimation(new Runnable() { @Override public void run() { - EventBus.getDefault().send(new DismissTaskEvent(mTask, tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv)); } }, 0); } @@ -620,6 +616,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, anim.addListener(postAnimRunnable); } anim.start(); + } else { + postAnimRunnable.onAnimationEnd(null); } } @@ -641,58 +639,32 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /**** View focus state ****/ /** - * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen - * if the view is not currently visible, or we are in touch state (where we still want to keep - * track of focus). - */ - public void setFocusedTask(boolean animateFocusedState) { - mIsFocused = true; - if (mFocusAnimationsEnabled) { - // Focus the header bar - mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); - } - // Update the thumbnail alpha with the focus - mThumbnailView.onFocusChanged(true); - // Call the callback - if (mCb != null) { - mCb.onTaskViewFocusChanged(this, true); - } - // Workaround, we don't always want it focusable in touch mode, but we want the first task - // to be focused after the enter-recents animation, which can be triggered from either touch - // or keyboard - setFocusableInTouchMode(true); - requestFocus(); - setFocusableInTouchMode(false); - invalidate(); - } - - /** - * Unsets the focused task explicitly. + * Explicitly sets the focused state of this task. */ - void unsetFocusedTask() { - mIsFocused = false; - if (mFocusAnimationsEnabled) { - // Un-focus the header bar - mHeaderView.onTaskViewFocusChanged(false, true); + public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) { + if (DEBUG) { + Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused + + " mIsFocused: " + mIsFocused + " animated: " + animated + + " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() + + " isAccessibilityFocused(): " + isAccessibilityFocused()); } - // Update the thumbnail alpha with the focus - mThumbnailView.onFocusChanged(false); - // Call the callback - if (mCb != null) { - mCb.onTaskViewFocusChanged(this, false); - } - invalidate(); - } - - /** - * Updates the explicitly focused state when the view focus changes. - */ - @Override - protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - if (!gainFocus) { - unsetFocusedTask(); + SystemServicesProxy ssp = Recents.getSystemServices(); + mIsFocused = isFocused; + mIsFocusAnimated = animated; + mHeaderView.onTaskViewFocusChanged(isFocused, animated); + mThumbnailView.onFocusChanged(isFocused); + if (isFocused) { + if (requestViewFocus && !isFocused()) { + requestFocus(); + } + if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { + requestAccessibilityFocus(); + } + } else { + if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { + clearAccessibilityFocus(); + } } } @@ -700,17 +672,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, * Returns whether we have explicitly been focused. */ public boolean isFocusedTask() { - return mIsFocused || isFocused(); + return mIsFocused; } - /** Enables all focus animations. */ - void enableFocusAnimations() { - boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; - mFocusAnimationsEnabled = true; - if (mIsFocused && !wasFocusAnimationsEnabled) { - // Re-notify the header if we were focused and animations were not previously enabled - mHeaderView.onTaskViewFocusChanged(true, true); - } + /** + * Returns whether this focused task is animated. + */ + public boolean isFocusAnimated() { + return mIsFocusAnimated; } public void disableLayersForOneFrame() { 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 e1e07efdd953..f6353f83da52 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -39,7 +39,6 @@ import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; -import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; @@ -244,9 +243,8 @@ public class TaskViewHeader extends FrameLayout mMoveTaskButton.setOnClickListener(this); // In accessibility, a single click on the focused app info button will show it - AccessibilityManager am = (AccessibilityManager) getContext(). - getSystemService(Context.ACCESSIBILITY_SERVICE); - if (am != null && am.isEnabled()) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.isTouchExplorationEnabled()) { mApplicationIcon.setOnClickListener(this); } } @@ -369,9 +367,6 @@ public class TaskViewHeader extends FrameLayout /** Notifies the associated TaskView has been focused. */ void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { - // If we are not animating the visible state, just return - if (!animateFocusedState) return; - boolean isRunning = false; if (mFocusAnimator != null) { isRunning = mFocusAnimator.isRunning(); @@ -379,6 +374,9 @@ public class TaskViewHeader extends FrameLayout } if (focused) { + // If we are not animating the visible state, just return + if (!animateFocusedState) return; + int currentColor = mBackgroundColor; int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); int[][] states = new int[][] { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 4793d6af1485..cd2f124c715e 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2749,8 +2749,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; - final int numStacks = stacks.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (stack.finishDisabledPackageActivitiesLocked( packageName, filterByClasses, doit, evenPersistent, userId)) { |