Merge "Remove the requirement of the --checkin flag." into nyc-mr2-dev
diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 8e9791f..02a92b9 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -58,7 +58,8 @@
IPackageManager pm = IPackageManager.Stub.asInterface(
ServiceManager.getService("package"));
try {
- if (pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0)) {
+ if (pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0) ||
+ pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
INfcAdapter nfc = INfcAdapter.Stub
.asInterface(ServiceManager.getService(Context.NFC_SERVICE));
try {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 7c79b40..6829961 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -79,6 +79,7 @@
public class ZygoteInit {
private static final String TAG = "Zygote";
+ private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload";
private static final String PROPERTY_RUNNING_IN_CONTAINER = "ro.boot.container";
private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
@@ -198,6 +199,9 @@
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
preloadResources();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
+ preloadOpenGL();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
preloadSharedLibraries();
preloadTextResources();
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -238,6 +242,12 @@
System.loadLibrary("jnigraphics");
}
+ private static void preloadOpenGL() {
+ if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) {
+ EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ }
+ }
+
private static void preloadTextResources() {
Hyphenator.init();
TextView.preloadFontCache();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 1823711..f1e24b8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -349,6 +349,34 @@
throw new UnsupportedOperationException(
"Writing operation is not supported by the device.");
}
+
+ final int parentObjectHandle;
+ final int storageId;
+ switch (parentId.mDocumentType) {
+ case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
+ final String[] storageDocumentIds =
+ mDatabase.getStorageDocumentIds(parentId.mDocumentId);
+ if (storageDocumentIds.length == 1) {
+ final String newDocumentId =
+ createDocument(storageDocumentIds[0], mimeType, displayName);
+ notifyChildDocumentsChange(parentDocumentId);
+ return newDocumentId;
+ } else {
+ throw new UnsupportedOperationException(
+ "Cannot create a file under the device.");
+ }
+ case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE:
+ storageId = parentId.mStorageId;
+ parentObjectHandle = -1;
+ break;
+ case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
+ storageId = parentId.mStorageId;
+ parentObjectHandle = parentId.mObjectHandle;
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected document type.");
+ }
+
pipe = ParcelFileDescriptor.createReliablePipe();
int objectHandle = -1;
MtpObjectInfo info = null;
@@ -359,8 +387,8 @@
MtpConstants.FORMAT_ASSOCIATION :
MediaFile.getFormatCode(displayName, mimeType);
info = new MtpObjectInfo.Builder()
- .setStorageId(parentId.mStorageId)
- .setParent(parentId.mObjectHandle)
+ .setStorageId(storageId)
+ .setParent(parentObjectHandle)
.setFormat(formatCode)
.setName(displayName)
.build();
@@ -414,6 +442,24 @@
}
}
+ @Override
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ try {
+ Identifier identifier = mDatabase.createIdentifier(documentId);
+ while (true) {
+ if (parentDocumentId.equals(identifier.mDocumentId)) {
+ return true;
+ }
+ if (identifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
+ return false;
+ }
+ identifier = mDatabase.getParentIdentifier(identifier.mDocumentId);
+ }
+ } catch (FileNotFoundException error) {
+ return false;
+ }
+ }
+
void openDevice(int deviceId) throws IOException {
synchronized (mDeviceListLock) {
if (mDeviceToolkits.containsKey(deviceId)) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index d19b460..8831ae2 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -34,6 +34,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.concurrent.TimeoutException;
import static com.android.mtp.MtpDatabase.strings;
@@ -546,7 +548,7 @@
public void testOpenDocument_writing() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
setupRoots(0, new MtpRoot[] {
- new MtpRoot(0, 0, "Storage", 0, 0, "")
+ new MtpRoot(0, 100, "Storage", 0, 0, "")
});
final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
{
@@ -688,6 +690,29 @@
}
}
+ public void testCreateDocument() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage A", 100, 100, null)
+ });
+ final String documentId = mProvider.createDocument("1", "text/plain", "note.txt");
+ final Uri deviceUri = DocumentsContract.buildChildDocumentsUri(
+ MtpDocumentsProvider.AUTHORITY, "1");
+ final Uri storageUri = DocumentsContract.buildChildDocumentsUri(
+ MtpDocumentsProvider.AUTHORITY, "2");
+ mResolver.waitForNotification(storageUri, 1);
+ mResolver.waitForNotification(deviceUri, 1);
+ try (final Cursor cursor = mProvider.queryDocument(documentId, null)) {
+ assertTrue(cursor.moveToNext());
+ assertEquals(
+ "note.txt",
+ cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(
+ "text/plain",
+ cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)));
+ }
+ }
+
public void testCreateDocument_noWritingSupport() throws Exception {
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(new MtpDeviceRecord(
@@ -769,6 +794,18 @@
assertEquals(0x400000000L, cursor.getLong(0));
}
+ public void testIsChildDocument() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
+ setupHierarchyDocuments("1");
+ assertTrue(mProvider.isChildDocument("1", "1"));
+ assertTrue(mProvider.isChildDocument("1", "14"));
+ assertTrue(mProvider.isChildDocument("2", "14"));
+ assertTrue(mProvider.isChildDocument("5", "14"));
+ assertFalse(mProvider.isChildDocument("3", "14"));
+ assertFalse(mProvider.isChildDocument("6", "14"));
+ }
+
private void setupProvider(int flag) {
mDatabase = new MtpDatabase(getContext(), flag);
mProvider = new MtpDocumentsProvider();
@@ -822,4 +859,63 @@
return getStrings(mProvider.queryChildDocuments(
parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
}
+
+ static class HierarchyDocument {
+ int depth;
+ String documentId;
+ int objectHandle;
+ int parentHandle;
+
+ HierarchyDocument createChildDocument(int newHandle) {
+ final HierarchyDocument doc = new HierarchyDocument();
+ doc.depth = depth - 1;
+ doc.objectHandle = newHandle;
+ doc.parentHandle = objectHandle;
+ return doc;
+ }
+
+ MtpObjectInfo toObjectInfo() {
+ return new MtpObjectInfo.Builder()
+ .setName("doc_" + documentId)
+ .setFormat(depth > 0 ?
+ MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT)
+ .setObjectHandle(objectHandle)
+ .setParent(parentHandle)
+ .build();
+ }
+ }
+
+ private void setupHierarchyDocuments(String documentId) throws Exception {
+ final Queue<HierarchyDocument> ids = new LinkedList<>();
+ final HierarchyDocument firstDocument = new HierarchyDocument();
+ firstDocument.depth = 3;
+ firstDocument.documentId = documentId;
+ firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
+ ids.add(firstDocument);
+
+ int objectHandle = 100;
+ while (!ids.isEmpty()) {
+ final HierarchyDocument document = ids.remove();
+ final HierarchyDocument[] children = new HierarchyDocument[] {
+ document.createChildDocument(objectHandle++),
+ document.createChildDocument(objectHandle++),
+ document.createChildDocument(objectHandle++),
+ };
+ final String[] childDocIds = setupDocuments(
+ 0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] {
+ children[0].toObjectInfo(),
+ children[1].toObjectInfo(),
+ children[2].toObjectInfo(),
+ });
+ children[0].documentId = childDocIds[0];
+ children[1].documentId = childDocIds[1];
+ children[2].documentId = childDocIds[2];
+
+ if (children[0].depth > 0) {
+ ids.add(children[0]);
+ ids.add(children[1]);
+ ids.add(children[2]);
+ }
+ }
+ }
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 9a81489..4dba648 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -26,6 +26,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import junit.framework.Assert;
public class TestMtpManager extends MtpManager {
public static final int CREATED_DOCUMENT_HANDLE = 1000;
@@ -151,6 +152,9 @@
@Override
int createDocument(int deviceId, MtpObjectInfo objectInfo, ParcelFileDescriptor source)
throws IOException {
+ Assert.assertNotSame(0, objectInfo.getStorageId());
+ Assert.assertNotSame(-1, objectInfo.getStorageId());
+ Assert.assertNotSame(0, objectInfo.getParent());
final String key = pack(deviceId, CREATED_DOCUMENT_HANDLE);
if (mObjectInfos.containsKey(key)) {
throw new IOException();
diff --git a/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
new file mode 100644
index 0000000..a85beb8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_grid_task_view_focus_frame_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#61FFFFFF" />
+ <corners android:radius="8dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
index 94ffdb1..0b9836ff 100644
--- a/packages/SystemUI/res/values/dimens_grid.xml
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -21,5 +21,6 @@
<dimen name="recents_grid_padding_task_view">20dp</dimen>
<dimen name="recents_grid_task_view_header_height">44dp</dimen>
<dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
+ <dimen name="recents_grid_task_view_focused_frame_thickness">8dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 914035b..a7f6b70 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -50,7 +50,7 @@
/**
* Returns the task to focus given the current launch state.
*/
- public int getInitialFocusTaskIndex(int numTasks) {
+ public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchedFromApp) {
@@ -66,6 +66,11 @@
return numTasks - 1;
}
+ if (useGridLayout) {
+ // If coming from another app to the grid layout, focus the front most task
+ return numTasks - 1;
+ }
+
// If coming from another app, focus the next task
return Math.max(0, numTasks - 2);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 6cf5d10..86f61e9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -201,8 +201,6 @@
Resources res = mContext.getResources();
reloadResources();
mDummyStackView.reloadOnConfigurationChange();
- mDummyStackView.getStackAlgorithm().getGridState().setHasDockedTasks(
- Recents.getSystemServices().hasDockedTask());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 178cb9f..9b25ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -247,6 +247,9 @@
*/
public static class DockState implements DropTarget {
+ public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+ public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
// The rotation to apply to the hint text
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZONTAL, VERTICAL})
@@ -319,7 +322,8 @@
private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
int hintTextResId) {
dockAreaAlpha = areaAlpha;
- dockAreaOverlay = new ColorDrawable(0xFFffffff);
+ dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+ ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
dockAreaOverlay.setAlpha(0);
hintTextAlpha = hintAlpha;
hintTextOrientation = hintOrientation;
@@ -435,7 +439,7 @@
* @param createMode used to pass to ActivityManager to dock the task
* @param touchArea the area in which touch will initiate this dock state
* @param dockArea the visible dock area
- * @param expandedTouchDockArea the areain which touch will continue to dock after entering
+ * @param expandedTouchDockArea the area in which touch will continue to dock after entering
* the initial touch area. This is also the new dock area to
* draw.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index ed86d4c..a2ee4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -105,6 +105,7 @@
private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR =
Interpolators.LINEAR_OUT_SLOW_IN;
+ private final int mEnterAndExitFromHomeTranslationOffset;
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -113,6 +114,8 @@
public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
mStackView = stackView;
+ mEnterAndExitFromHomeTranslationOffset = Recents.getConfiguration().isGridEnabled
+ ? 0 : DOUBLE_FRAME_OFFSET_MS;
}
/**
@@ -260,7 +263,7 @@
AnimationProps taskAnimation = new AnimationProps()
.setInitialPlayTime(AnimationProps.BOUNDS,
Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
- DOUBLE_FRAME_OFFSET_MS)
+ mEnterAndExitFromHomeTranslationOffset)
.setStartDelay(AnimationProps.ALPHA,
Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
FRAME_OFFSET_MS)
@@ -321,7 +324,7 @@
AnimationProps taskAnimation;
if (animated) {
int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) *
- DOUBLE_FRAME_OFFSET_MS;
+ mEnterAndExitFromHomeTranslationOffset;
taskAnimation = new AnimationProps()
.setStartDelay(AnimationProps.BOUNDS, delay)
.setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index f46de5f..3fa6d75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -214,38 +214,10 @@
}
/**
- * The state telling the algorithm whether to use grid layout or not.
+ * @return True if we should use the grid layout.
*/
- public static class GridState {
- private boolean mDraggingOverDockedState;
- private boolean mHasDockedTask;
-
- private GridState() {
- mDraggingOverDockedState = false;
- mHasDockedTask = false;
- }
-
- /**
- * Check whether we should use the grid layout.
- * We use the grid layout for Recents iff all the following is true:
- * 1. Grid-mode is enabled.
- * 2. The activity is not in split screen mode (there's no docked task).
- * 3. The user is not dragging a task view over the dock state.
- * @return True if we should use the grid layout.
- */
- boolean useGridLayout() {
- return Recents.getConfiguration().isGridEnabled &&
- !mDraggingOverDockedState &&
- !mHasDockedTask;
- }
-
- public void setDragging(boolean draggingOverDockedState) {
- mDraggingOverDockedState = draggingOverDockedState;
- }
-
- public void setHasDockedTasks(boolean hasDockedTask) {
- mHasDockedTask = hasDockedTask;
- }
+ boolean useGridLayout() {
+ return Recents.getConfiguration().isGridEnabled;
}
// A report of the visibility state of the stack
@@ -262,7 +234,6 @@
Context mContext;
private StackState mState = StackState.SPLIT;
- private GridState mGridState = new GridState();
private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
@@ -517,7 +488,7 @@
}
// Initialize the grid layout
- mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect);
+ mTaskGridLayoutAlgorithm.initialize(windowRect);
}
/**
@@ -770,7 +741,7 @@
}
public Rect getStackActionButtonRect() {
- return mGridState.useGridLayout()
+ return useGridLayout()
? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
}
@@ -796,13 +767,6 @@
}
/**
- * Returns the current grid layout state.
- */
- public GridState getGridState() {
- return mGridState;
- }
-
- /**
* Returns whether this stack layout has been initialized.
*/
public boolean isInitialized() {
@@ -903,7 +867,7 @@
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
- } else if (mGridState.useGridLayout()) {
+ } else if (useGridLayout()) {
int taskIndex = mTaskIndexMap.get(task.key.id);
int taskCount = mTaskIndexMap.size();
mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -1324,7 +1288,7 @@
* Returns the proper task rectangle according to the current grid state.
*/
public Rect getTaskRect() {
- return mGridState.useGridLayout() ? mTaskGridLayoutAlgorithm.getTaskGridRect() : mTaskRect;
+ return useGridLayout() ? mTaskGridLayoutAlgorithm.getTaskGridRect() : mTaskRect;
}
public void dump(String prefix, PrintWriter writer) {
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 0bfdb2b3..bc2c424 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -70,6 +70,7 @@
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
@@ -93,6 +94,7 @@
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.grid.GridTaskView;
+import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -206,6 +208,10 @@
private int mLastWidth;
private int mLastHeight;
+ // We keep track of the task view focused by user interaction and draw a frame around it in the
+ // grid layout.
+ private TaskViewFocusFrame mTaskViewFocusFrame;
+
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -265,6 +271,14 @@
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
+ // Create a frame to draw around the focused task view
+ if (Recents.getConfiguration().isGridEnabled) {
+ mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this,
+ mLayoutAlgorithm.mTaskGridLayoutAlgorithm);
+ addView(mTaskViewFocusFrame);
+ getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame);
+ }
+
int taskBarDismissDozeDelaySeconds = getResources().getInteger(
R.integer.recents_task_bar_dismiss_delay_seconds);
mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
@@ -878,7 +892,7 @@
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
+ public boolean setFocusedTask(int taskIndex, boolean scrollToTask,
final boolean requestViewFocus) {
return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
}
@@ -888,7 +902,7 @@
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
+ public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
boolean requestViewFocus, int timerIndicatorDuration) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
@@ -940,6 +954,10 @@
newFocusedTaskView.setFocusedState(true, requestViewFocus);
}
}
+ // Any time a task view gets the focus, we move the focus frame around it.
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask));
+ }
}
return willScroll;
}
@@ -1005,20 +1023,28 @@
float stackScroll = mStackScroller.getStackScroll();
ArrayList<Task> tasks = mStack.getStackTasks();
int taskCount = tasks.size();
- if (forward) {
- // Walk backwards and focus the next task smaller than the current stack scroll
- for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
- float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
- if (Float.compare(taskP, stackScroll) <= 0) {
- break;
- }
- }
+ if (useGridLayout()) {
+ // For the grid layout, we directly set focus to the most recently used task
+ // no matter we're moving forwards or backwards.
+ newIndex = taskCount - 1;
} else {
- // Walk forwards and focus the next task larger than the current stack scroll
- for (newIndex = 0; newIndex < taskCount; newIndex++) {
- float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
- if (Float.compare(taskP, stackScroll) >= 0) {
- break;
+ // For the grid layout we pick a proper task to focus, according to the current
+ // stack scroll.
+ if (forward) {
+ // Walk backwards and focus the next task smaller than the current stack scroll
+ for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
+ float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
+ if (Float.compare(taskP, stackScroll) <= 0) {
+ break;
+ }
+ }
+ } else {
+ // Walk forwards and focus the next task larger than the current stack scroll
+ for (newIndex = 0; newIndex < taskCount; newIndex++) {
+ float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
+ if (Float.compare(taskP, stackScroll) >= 0) {
+ break;
+ }
}
}
}
@@ -1037,20 +1063,23 @@
/**
* Resets the focused task.
*/
- void resetFocusedTask(Task task) {
+ public void resetFocusedTask(Task task) {
if (task != null) {
TaskView tv = getChildViewForTask(task);
if (tv != null) {
tv.setFocusedState(false, false /* requestViewFocus */);
}
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
mFocusedTask = null;
}
/**
* Returns the focused task.
*/
- Task getFocusedTask() {
+ public Task getFocusedTask() {
return mFocusedTask;
}
@@ -1253,6 +1282,9 @@
for (int i = 0; i < taskViewCount; i++) {
measureTaskView(mTmpTaskViews.get(i));
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.measure();
+ }
setMeasuredDimension(width, height);
mLastWidth = width;
@@ -1287,6 +1319,9 @@
for (int i = 0; i < taskViewCount; i++) {
layoutTaskView(changed, mTmpTaskViews.get(i));
}
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.layout();
+ }
if (changed) {
if (mStackScroller.isScrollOutOfBounds()) {
@@ -1339,10 +1374,19 @@
// until after the enter-animation
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
- if (focusedTaskIndex != -1) {
- setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
- false /* requestViewFocus */);
+
+ // We set the initial focused task view iff the following conditions are satisfied:
+ // 1. Recents is showing task views in stack layout.
+ // 2. Recents is launched with ALT + TAB.
+ boolean setFocusOnFirstLayout = !useGridLayout() ||
+ Recents.getConfiguration().getLaunchState().launchedWithAltTab;
+ if (setFocusOnFirstLayout) {
+ int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
+ useGridLayout());
+ if (focusedTaskIndex != -1) {
+ setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
+ false /* requestViewFocus */);
+ }
}
updateStackActionButtonVisibility();
}
@@ -1443,6 +1487,11 @@
// Remove the task from the ignored set
removeIgnoreTask(removedTask);
+ // Resize the grid layout task view focus frame
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.resize();
+ }
+
// If requested, relayout with the given animation
if (animation != null) {
updateLayoutAlgorithm(true /* boundScroll */);
@@ -1740,10 +1789,18 @@
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
Interpolators.FAST_OUT_SLOW_IN));
+
+ // Dismiss the grid task view focus frame
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
if (mFocusedTask != null) {
+ if (mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
TaskView tv = getChildViewForTask(mFocusedTask);
if (tv != null) {
tv.dismissTask();
@@ -1858,7 +1915,6 @@
Interpolators.FAST_OUT_SLOW_IN);
boolean ignoreTaskOverrides = false;
if (event.dropTarget instanceof TaskStack.DockState) {
- mLayoutAlgorithm.getGridState().setDragging(true);
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
@@ -1878,7 +1934,6 @@
updateLayoutAlgorithm(true /* boundScroll */);
ignoreTaskOverrides = true;
} else {
- mLayoutAlgorithm.getGridState().setDragging(false);
// Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
// task view, so add it back to the ignore set after updating the layout
removeIgnoreTask(event.task);
@@ -1889,7 +1944,6 @@
}
public final void onBusEvent(final DragEndEvent event) {
- mLayoutAlgorithm.getGridState().setDragging(false);
// We don't handle drops on the dock regions
if (event.dropTarget instanceof TaskStack.DockState) {
// However, we do need to reset the overrides, since the last state of this task stack
@@ -2076,13 +2130,17 @@
mResetToInitialStateWhenResized = true;
}
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible && mTaskViewFocusFrame != null) {
+ mTaskViewFocusFrame.moveGridTaskViewFocus(null);
+ }
+ }
+
public void reloadOnConfigurationChange() {
mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
boolean hasDockedTask = Recents.getSystemServices().hasDockedTask();
- mStableLayoutAlgorithm.getGridState().setHasDockedTasks(hasDockedTask);
- mLayoutAlgorithm.getGridState().setHasDockedTasks(hasDockedTask);
}
/**
@@ -2139,7 +2197,7 @@
* Check whether we should use the grid layout.
*/
public boolean useGridLayout() {
- return mLayoutAlgorithm.getGridState().useGridLayout();
+ return mLayoutAlgorithm.useGridLayout();
}
/**
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 aeb85d0..003138f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -342,8 +342,9 @@
mSv.invalidate();
}
- // Reset the focused task after the user has scrolled
- if (!mSv.mTouchExplorationEnabled) {
+ // Reset the focused task after the user has scrolled, but we have no scrolling
+ // in grid layout and therefore we don't want to reset the focus there.
+ if (!mSv.mTouchExplorationEnabled && !mSv.useGridLayout()) {
mSv.resetFocusedTask(mSv.getFocusedTask());
}
} else if (mActiveTaskView == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index 5d969f9..70536b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -39,7 +39,6 @@
/** The padding between task views. */
private int mPaddingTaskView;
- private Rect mDisplayRect;
private Rect mWindowRect;
private Point mScreenSize = new Point();
@@ -52,6 +51,9 @@
private float mAppAspectRatio;
private Rect mSystemInsets = new Rect();
+ /** The thickness of the focused task view frame. */
+ private int mFocusedFrameThickness;
+
/**
* When the amount of tasks is determined, the size and position of every task view can be
* decided. Each instance of TaskGridRectInfo store the task view information for a certain
@@ -62,7 +64,7 @@
int[] xOffsets;
int[] yOffsets;
- public TaskGridRectInfo(int taskCount) {
+ TaskGridRectInfo(int taskCount) {
size = new Rect();
xOffsets = new int[taskCount];
yOffsets = new int[taskCount];
@@ -74,10 +76,26 @@
layoutTaskCount < 7 ? 3 : 4));
int lines = layoutTaskCount < 3 ? 1 : 2;
+ // A couple of special cases.
+ boolean landscapeWindow = mWindowRect.width() > mWindowRect.height();
+ boolean landscapeTaskView = mAppAspectRatio > 1;
+ // If we're in portrait but task views are landscape, show more lines of fewer tasks.
+ if (!landscapeWindow && landscapeTaskView) {
+ tasksPerLine = layoutTaskCount < 2 ? 1 : 2;
+ lines = layoutTaskCount < 3 ? 1 : (
+ layoutTaskCount < 5 ? 2 : (
+ layoutTaskCount < 7 ? 3 : 4));
+ }
+ // If we're in landscape but task views are portrait, show fewer lines of more tasks.
+ if (landscapeWindow && !landscapeTaskView) {
+ tasksPerLine = layoutTaskCount < 7 ? layoutTaskCount : 6;
+ lines = layoutTaskCount < 7 ? 1 : 2;
+ }
+
int taskWidth, taskHeight;
- int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
+ int maxTaskWidth = (mWindowRect.width() - 2 * mPaddingLeftRight
- (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
- int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
+ int maxTaskHeight = (mWindowRect.height() - 2 * mPaddingTopBottom
- (lines - 1) * mPaddingTaskView) / lines;
if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
@@ -91,9 +109,9 @@
}
size.set(0, 0, taskWidth, taskHeight);
- int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
+ int emptySpaceX = mWindowRect.width() - 2 * mPaddingLeftRight
- (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
- int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
+ int emptySpaceY = mWindowRect.height() - 2 * mPaddingTopBottom
- (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) {
// We also need to invert the index in order to display the most recent tasks first.
@@ -101,9 +119,9 @@
int xIndex = taskLayoutIndex % tasksPerLine;
int yIndex = taskLayoutIndex / tasksPerLine;
- xOffsets[taskIndex] =
+ xOffsets[taskIndex] = mWindowRect.left +
emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
- yOffsets[taskIndex] =
+ yOffsets[taskIndex] = mWindowRect.top +
emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
}
}
@@ -113,7 +131,7 @@
* We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there
* are k tasks.
*/
- TaskGridRectInfo[] mTaskGridRectInfoList;
+ private TaskGridRectInfo[] mTaskGridRectInfoList;
public TaskGridLayoutAlgorithm(Context context) {
reloadOnConfigurationChange(context);
@@ -121,9 +139,9 @@
public void reloadOnConfigurationChange(Context context) {
Resources res = context.getResources();
- mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right);
- mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom);
mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
+ mFocusedFrameThickness = res.getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_focused_frame_thickness);
mTaskGridRect = new Rect();
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
@@ -147,6 +165,10 @@
*/
public TaskViewTransform getTransform(int taskIndex, int taskCount,
TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+ if (taskCount == 0) {
+ transformOut.reset();
+ return transformOut;
+ }
TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
mTaskGridRect.set(gridInfo.size);
@@ -162,7 +184,7 @@
// We also need to invert the index in order to display the most recent tasks first.
int taskLayoutIndex = taskCount - taskIndex - 1;
- boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
+ boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT;
// Fill out the transform
transformOut.scale = 1f;
@@ -178,9 +200,11 @@
return transformOut;
}
- public void initialize(Rect displayRect, Rect windowRect) {
- mDisplayRect = displayRect;
+ public void initialize(Rect windowRect) {
mWindowRect = windowRect;
+ // Define paddings in terms of percentage of the total area.
+ mPaddingLeftRight = (int) (0.025f * Math.min(mWindowRect.width(), mWindowRect.height()));
+ mPaddingTopBottom = (int) (0.1 * mWindowRect.height());
// Pre-calculate the positions and offsets of task views so that we can reuse them directly
// in the future.
@@ -202,14 +226,25 @@
}
public Rect getStackActionButtonRect() {
- Rect buttonRect = new Rect(mDisplayRect);
+ Rect buttonRect = new Rect(mWindowRect);
buttonRect.right -= mPaddingLeftRight;
buttonRect.left += mPaddingLeftRight;
buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
return buttonRect;
}
+ public void updateTaskGridRect(int taskCount) {
+ if (taskCount > 0) {
+ TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
+ mTaskGridRect.set(gridInfo.size);
+ }
+ }
+
public Rect getTaskGridRect() {
return mTaskGridRect;
}
+
+ public int getFocusFrameThickness() {
+ return mFocusedFrameThickness;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
new file mode 100644
index 0000000..86ed583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 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.views.grid;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
+import com.android.systemui.R;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+
+public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
+
+ private TaskStackView mSv;
+ private TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
+ public TaskViewFocusFrame(Context context) {
+ this(context, null);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setBackground(mContext.getDrawable(
+ R.drawable.recents_grid_task_view_focus_frame_background));
+ setFocusable(false);
+ hide();
+ }
+
+ public TaskViewFocusFrame(Context context, TaskStackView stackView,
+ TaskGridLayoutAlgorithm taskGridLayoutAlgorithm) {
+ this(context);
+ mSv = stackView;
+ mTaskGridLayoutAlgorithm = taskGridLayoutAlgorithm;
+ }
+
+ /**
+ * Measure the width and height of the focus frame according to the current grid task view size.
+ */
+ public void measure() {
+ int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
+ Rect rect = mTaskGridLayoutAlgorithm.getTaskGridRect();
+ measure(
+ MeasureSpec.makeMeasureSpec(rect.width() + thickness * 2, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(rect.height() + thickness * 2, MeasureSpec.EXACTLY));
+ }
+
+ /**
+ * Layout the focus frame with its size.
+ */
+ public void layout() {
+ layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ /**
+ * Update the current size of grid task view and the focus frame.
+ */
+ public void resize() {
+ if (mSv.useGridLayout()) {
+ mTaskGridLayoutAlgorithm.updateTaskGridRect(mSv.getStack().getTaskCount());
+ measure();
+ requestLayout();
+ }
+ }
+
+ /**
+ * Move the task view focus frame to surround the newly focused view. If it's {@code null} or
+ * it's not an instance of GridTaskView, we hide the focus frame.
+ * @param newFocus The newly focused view.
+ */
+ public void moveGridTaskViewFocus(View newFocus) {
+ if (mSv.useGridLayout()) {
+ // The frame only shows up in the grid layout. It shouldn't show up in the stack
+ // layout including when we're in the split screen.
+ if (newFocus instanceof GridTaskView) {
+ // If the focus goes to a GridTaskView, we show the frame and layout it.
+ int[] location = new int[2];
+ newFocus.getLocationInWindow(location);
+ int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
+ setTranslationX(location[0] - thickness);
+ setTranslationY(location[1] - thickness);
+ show();
+ } else {
+ // If focus goes to other views, we hide the frame.
+ hide();
+ }
+ }
+ }
+
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (!mSv.useGridLayout()) {
+ return;
+ }
+ if (newFocus == null) {
+ // We're going to touch mode, unset the focus.
+ moveGridTaskViewFocus(null);
+ return;
+ }
+ if (oldFocus == null) {
+ // We're returning from touch mode, set the focus to the previously focused task.
+ final TaskStack stack = mSv.getStack();
+ final int taskCount = stack.getTaskCount();
+ final int k = stack.indexOfStackTask(mSv.getFocusedTask());
+ final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
+ mSv.setFocusedTask(taskIndexToFocus, false, true);
+ }
+ }
+
+ private void show() {
+ setAlpha(1f);
+ }
+
+ private void hide() {
+ setAlpha(0f);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 05a9fc7..3052bf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -394,20 +394,6 @@
return false;
}
- /**
- * Return whether there are any clearable notifications (that aren't errors).
- */
- public boolean hasActiveClearableNotifications() {
- for (Entry e : mSortedAndFiltered) {
- if (e.getContentView() != null) { // the view successfully inflated
- if (e.notification.isClearable()) {
- return true;
- }
- }
- }
- return false;
- }
-
// Q: What kinds of notifications should show during setup?
// A: Almost none! Only things coming from the system (package is "android") that also
// have special "kind" tags marking them as relevant for setup (see below).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 91f3cc9..14868a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1196,8 +1196,10 @@
List<ExpandableNotificationRow> children = row.getNotificationChildren();
if (row.areChildrenExpanded() && children != null) {
for (ExpandableNotificationRow childRow : children) {
- if (childRow.getVisibility() == View.VISIBLE) {
- viewsToHide.add(childRow);
+ if (mStackScroller.canChildBeDismissed(childRow)) {
+ if (childRow.getVisibility() == View.VISIBLE) {
+ viewsToHide.add(childRow);
+ }
}
}
}
@@ -1682,8 +1684,15 @@
}
List<ExpandableNotificationRow> notificationChildren =
entry.row.getNotificationChildren();
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(notificationChildren);
- for (int i = 0; i < toRemove.size(); i++) {
+ ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ if ((row.getStatusBarNotification().getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ // the child is a forground service notification which we can't remove!
+ continue;
+ }
+ toRemove.add(row);
toRemove.get(i).setKeepInParent(true);
// we need to set this state earlier as otherwise we might generate some weird
// animations
@@ -1949,10 +1958,27 @@
private void updateClearAll() {
boolean showDismissView =
mState != StatusBarState.KEYGUARD &&
- mNotificationData.hasActiveClearableNotifications();
+ hasActiveClearableNotifications();
mStackScroller.updateDismissView(showDismissView);
}
+ /**
+ * Return whether there are any clearable notifications
+ */
+ private boolean hasActiveClearableNotifications() {
+ int childCount = mStackScroller.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ if (((ExpandableNotificationRow) child).canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void updateEmptyShadeView() {
boolean showEmptyShade =
mState != StatusBarState.KEYGUARD &&
@@ -1999,7 +2025,7 @@
if (SPEW) {
final boolean clearable = hasActiveNotifications() &&
- mNotificationData.hasActiveClearableNotifications();
+ hasActiveClearableNotifications();
Log.d(TAG, "setAreThereNotifications: N=" +
mNotificationData.getActiveNotifications().size() + " any=" +
hasActiveNotifications() + " clearable=" + clearable);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index f5e50c6..4ae6e47 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -125,6 +125,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -151,6 +152,8 @@
// Bump if the stored widgets need to be upgraded.
private static final int CURRENT_VERSION = 1;
+ private static final AtomicLong REQUEST_COUNTER = new AtomicLong();
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -767,7 +770,8 @@
LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
for (int i = 0; i < N; i++) {
if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
- // We key the updates based on time, so that the values are sorted by time.
+ // We key the updates based on request id, so that the values are sorted in the
+ // order they were received.
int M = updatesMap.size();
for (int j = 0; j < M; j++) {
outUpdates.add(updatesMap.valueAt(j));
@@ -1820,9 +1824,9 @@
// method with a wrong id. In that case, ignore the call.
return;
}
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateTimes.put(viewId, requestTime);
+ widget.updateRequestIds.put(viewId, requestId);
}
if (widget == null || widget.host == null || widget.host.zombie
|| widget.host.callbacks == null || widget.provider == null
@@ -1833,7 +1837,7 @@
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
- args.arg3 = requestTime;
+ args.arg3 = requestId;
args.argi1 = widget.appWidgetId;
args.argi2 = viewId;
@@ -1844,10 +1848,10 @@
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
- int appWidgetId, int viewId, long requestTime) {
+ int appWidgetId, int viewId, long requestId) {
try {
callbacks.viewDataChanged(appWidgetId, viewId);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
@@ -1894,9 +1898,9 @@
}
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
- widget.updateTimes.put(ID_VIEWS_UPDATE, requestTime);
+ widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -1907,7 +1911,7 @@
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = (updateViews != null) ? updateViews.clone() : null;
- args.arg4 = requestTime;
+ args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
@@ -1916,10 +1920,10 @@
}
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
- int appWidgetId, RemoteViews views, long requestTime) {
+ int appWidgetId, RemoteViews views, long requestId) {
try {
callbacks.updateAppWidget(appWidgetId, views);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -1929,11 +1933,11 @@
}
private void scheduleNotifyProviderChangedLocked(Widget widget) {
- long requestTime = SystemClock.uptimeMillis();
+ long requestId = REQUEST_COUNTER.incrementAndGet();
if (widget != null) {
// When the provider changes, reset everything else.
- widget.updateTimes.clear();
- widget.updateTimes.append(ID_PROVIDER_CHANGED, requestTime);
+ widget.updateRequestIds.clear();
+ widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
@@ -1944,7 +1948,7 @@
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = widget.provider.info;
- args.arg4 = requestTime;
+ args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
@@ -1953,10 +1957,10 @@
}
private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
- int appWidgetId, AppWidgetProviderInfo info, long requestTime) {
+ int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
callbacks.providerChanged(appWidgetId, info);
- host.lastWidgetUpdateTime = requestTime;
+ host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
synchronized (mLock){
Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -3429,11 +3433,11 @@
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
RemoteViews views = (RemoteViews) args.arg3;
- long requestTime = (Long) args.arg4;
+ long requestId = (Long) args.arg4;
final int appWidgetId = args.argi1;
args.recycle();
- handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestTime);
+ handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId);
} break;
case MSG_NOTIFY_PROVIDER_CHANGED: {
@@ -3441,11 +3445,11 @@
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
- long requestTime = (Long) args.arg4;
+ long requestId = (Long) args.arg4;
final int appWidgetId = args.argi1;
args.recycle();
- handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestTime);
+ handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestId);
} break;
case MSG_NOTIFY_PROVIDERS_CHANGED: {
@@ -3461,13 +3465,13 @@
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
- long requestTime = (Long) args.arg3;
+ long requestId = (Long) args.arg3;
final int appWidgetId = args.argi1;
final int viewId = args.argi2;
args.recycle();
handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
- requestTime);
+ requestId);
} break;
}
}
@@ -3783,7 +3787,7 @@
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
int tag = TAG_UNDEFINED; // for use while saving state (the index)
- long lastWidgetUpdateTime; // last time we were successfully able to send an update.
+ long lastWidgetUpdateRequestId; // request id for the last update successfully sent
public int getUserId() {
return UserHandle.getUserId(id.uid);
@@ -3810,18 +3814,18 @@
*/
public boolean getPendingUpdatesForId(int appWidgetId,
LongSparseArray<PendingHostUpdate> outUpdates) {
- long updateTime = lastWidgetUpdateTime;
+ long updateRequestId = lastWidgetUpdateRequestId;
int N = widgets.size();
for (int i = 0; i < N; i++) {
Widget widget = widgets.get(i);
if (widget.appWidgetId == appWidgetId) {
outUpdates.clear();
- for (int j = widget.updateTimes.size() - 1; j >= 0; j--) {
- long time = widget.updateTimes.valueAt(j);
- if (time <= updateTime) {
+ for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) {
+ long requestId = widget.updateRequestIds.valueAt(j);
+ if (requestId <= updateRequestId) {
continue;
}
- int id = widget.updateTimes.keyAt(j);
+ int id = widget.updateRequestIds.keyAt(j);
final PendingHostUpdate update;
switch (id) {
case ID_PROVIDER_CHANGED:
@@ -3835,7 +3839,7 @@
default:
update = PendingHostUpdate.viewDataChanged(appWidgetId, id);
}
- outUpdates.put(time, update);
+ outUpdates.put(requestId, update);
}
return true;
}
@@ -3917,8 +3921,8 @@
RemoteViews maskedViews;
Bundle options;
Host host;
- // timestamps for various operations
- SparseLongArray updateTimes = new SparseLongArray(2);
+ // Request ids for various operations
+ SparseLongArray updateRequestIds = new SparseLongArray(2);
@Override
public String toString() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b1468f1..4973e17 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3576,7 +3576,8 @@
NotificationRecord childR = mNotificationList.get(i);
StatusBarNotification childSbn = childR.sbn;
if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) &&
- childR.getGroupKey().equals(r.getGroupKey())) {
+ childR.getGroupKey().equals(r.getGroupKey())
+ && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
mNotificationList.remove(i);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b9d06a0..39e2914 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10116,12 +10116,30 @@
int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
- if (permissionsState.grantRuntimePermission(bp, userId) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // If we cannot put the permission as it was, we have to write.
+ // Don't propagate the permission in a permission review mode if
+ // the former was revoked, i.e. marked to not propagate on upgrade.
+ // Note that in a permission review mode install permissions are
+ // represented as constantly granted runtime ones since we need to
+ // keep a per user state associated with the permission. Also the
+ // revoke on upgrade flag is no longer applicable and is reset.
+ final boolean revokeOnUpgrade = (flags & PackageManager
+ .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+ if (revokeOnUpgrade) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ // Since we changed the flags, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
+ if (!mPermissionReviewRequired || !revokeOnUpgrade) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we cannot put the permission as it was,
+ // we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+
// If the app supports runtime permissions no need for a review.
if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
&& appSupportsRuntimePermissions
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9f074920..c2ccf41 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2567,14 +2567,14 @@
// Ignore
}
+ // Generate a list of admins from the admin map
+ policy.mAdminList.addAll(policy.mAdminMap.values());
+
// Might need to upgrade the file by rewriting it
if (needsRewrite) {
saveSettingsLocked(userHandle);
}
- // Generate a list of admins from the admin map
- policy.mAdminList.addAll(policy.mAdminMap.values());
-
validatePasswordOwnerLocked(policy);
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 8c2ee25..8a04d26 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -533,6 +533,8 @@
SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+
+ registerBroadcastReceiver();
}
@Override
@@ -568,7 +570,6 @@
case PHASE_BOOT_COMPLETED:
if (UserManager.isDeviceInDemoMode(getContext())) {
putDeviceInDemoMode();
- registerBroadcastReceiver();
}
break;
}