Merge "Add SkiaProfileRenderer to draw visual debugging info"
diff --git a/cmds/settings/Android.mk b/cmds/settings/Android.mk
index 05deb99..8a8d1bb 100644
--- a/cmds/settings/Android.mk
+++ b/cmds/settings/Android.mk
@@ -3,11 +3,6 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := settings
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_JAVA_LIBRARY)
-
include $(CLEAR_VARS)
LOCAL_MODULE := settings
LOCAL_SRC_FILES := settings
diff --git a/cmds/settings/settings b/cmds/settings/settings
index ef459ca..d41ccc62 100755
--- a/cmds/settings/settings
+++ b/cmds/settings/settings
@@ -1,5 +1,2 @@
-# Script to start "settings" on the device
-#
-base=/system
-export CLASSPATH=$base/framework/settings.jar
-exec app_process $base/bin com.android.commands.settings.SettingsCmd "$@"
+#!/system/bin/sh
+cmd settings "$@"
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
deleted file mode 100644
index e63a1f5..0000000
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.commands.settings;
-
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
-import android.app.IActivityManager.ContentProviderHolder;
-import android.content.IContentProvider;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public final class SettingsCmd {
-
- enum CommandVerb {
- UNSPECIFIED,
- GET,
- PUT,
- DELETE,
- LIST,
- }
-
- static String[] mArgs;
- int mNextArg;
- int mUser = -1; // unspecified
- CommandVerb mVerb = CommandVerb.UNSPECIFIED;
- String mTable = null;
- String mKey = null;
- String mValue = null;
-
- public static void main(String[] args) {
- if (args == null || args.length < 2) {
- printUsage();
- return;
- }
-
- mArgs = args;
- try {
- new SettingsCmd().run();
- } catch (Exception e) {
- System.err.println("Unable to run settings command");
- }
- }
-
- public void run() {
- boolean valid = false;
- String arg;
- try {
- while ((arg = nextArg()) != null) {
- if ("--user".equals(arg)) {
- if (mUser != -1) {
- // --user specified more than once; invalid
- break;
- }
- arg = nextArg();
- if ("current".equals(arg) || "cur".equals(arg)) {
- mUser = UserHandle.USER_CURRENT;
- } else {
- mUser = Integer.parseInt(arg);
- }
- } else if (mVerb == CommandVerb.UNSPECIFIED) {
- if ("get".equalsIgnoreCase(arg)) {
- mVerb = CommandVerb.GET;
- } else if ("put".equalsIgnoreCase(arg)) {
- mVerb = CommandVerb.PUT;
- } else if ("delete".equalsIgnoreCase(arg)) {
- mVerb = CommandVerb.DELETE;
- } else if ("list".equalsIgnoreCase(arg)) {
- mVerb = CommandVerb.LIST;
- } else {
- // invalid
- System.err.println("Invalid command: " + arg);
- break;
- }
- } else if (mTable == null) {
- if (!"system".equalsIgnoreCase(arg)
- && !"secure".equalsIgnoreCase(arg)
- && !"global".equalsIgnoreCase(arg)) {
- System.err.println("Invalid namespace '" + arg + "'");
- break; // invalid
- }
- mTable = arg.toLowerCase();
- if (mVerb == CommandVerb.LIST) {
- valid = true;
- break;
- }
- } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
- mKey = arg;
- if (mNextArg >= mArgs.length) {
- valid = true;
- } else {
- System.err.println("Too many arguments");
- }
- break;
- } else if (mKey == null) {
- mKey = arg;
- // keep going; there's another PUT arg
- } else { // PUT, final arg
- mValue = arg;
- if (mNextArg >= mArgs.length) {
- valid = true;
- } else {
- System.err.println("Too many arguments");
- }
- break;
- }
- }
- } catch (Exception e) {
- valid = false;
- }
-
- if (valid) {
- try {
- IActivityManager activityManager = ActivityManagerNative.getDefault();
- if (mUser == UserHandle.USER_CURRENT) {
- mUser = activityManager.getCurrentUser().id;
- }
- if (mUser < 0) {
- mUser = UserHandle.USER_SYSTEM;
- }
- IContentProvider provider = null;
- IBinder token = new Binder();
- try {
- ContentProviderHolder holder = activityManager.getContentProviderExternal(
- "settings", UserHandle.USER_SYSTEM, token);
- if (holder == null) {
- throw new IllegalStateException("Could not find settings provider");
- }
- provider = holder.provider;
-
- switch (mVerb) {
- case GET:
- System.out.println(getForUser(provider, mUser, mTable, mKey));
- break;
- case PUT:
- putForUser(provider, mUser, mTable, mKey, mValue);
- break;
- case DELETE:
- System.out.println("Deleted "
- + deleteForUser(provider, mUser, mTable, mKey) + " rows");
- break;
- case LIST:
- for (String line : listForUser(provider, mUser, mTable)) {
- System.out.println(line);
- }
- break;
- default:
- System.err.println("Unspecified command");
- break;
- }
-
- } finally {
- if (provider != null) {
- activityManager.removeContentProviderExternal("settings", token);
- }
- }
- } catch (Exception e) {
- System.err.println("Error while accessing settings provider");
- e.printStackTrace();
- }
-
- } else {
- printUsage();
- }
- }
-
- private List<String> listForUser(IContentProvider provider, int userHandle, String table) {
- final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI
- : "secure".equals(table) ? Settings.Secure.CONTENT_URI
- : "global".equals(table) ? Settings.Global.CONTENT_URI
- : null;
- final ArrayList<String> lines = new ArrayList<String>();
- if (uri == null) {
- return lines;
- }
- try {
- final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null,
- null, null);
- try {
- while (cursor != null && cursor.moveToNext()) {
- lines.add(cursor.getString(1) + "=" + cursor.getString(2));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- Collections.sort(lines);
- } catch (RemoteException e) {
- System.err.println("List failed in " + table + " for user " + userHandle);
- }
- return lines;
- }
-
- private String nextArg() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- mNextArg++;
- return arg;
- }
-
- String getForUser(IContentProvider provider, int userHandle,
- final String table, final String key) {
- final String callGetCommand;
- if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM;
- else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE;
- else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL;
- else {
- System.err.println("Invalid table; no put performed");
- throw new IllegalArgumentException("Invalid table " + table);
- }
-
- String result = null;
- try {
- Bundle arg = new Bundle();
- arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg);
- if (b != null) {
- result = b.getPairValue();
- }
- } catch (RemoteException e) {
- System.err.println("Can't read key " + key + " in " + table + " for user " + userHandle);
- }
- return result;
- }
-
- void putForUser(IContentProvider provider, int userHandle,
- final String table, final String key, final String value) {
- final String callPutCommand;
- if ("system".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
- else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
- else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL;
- else {
- System.err.println("Invalid table; no put performed");
- return;
- }
-
- try {
- Bundle arg = new Bundle();
- arg.putString(Settings.NameValueTable.VALUE, value);
- arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
- provider.call(resolveCallingPackage(), callPutCommand, key, arg);
- } catch (RemoteException e) {
- System.err.println("Can't set key " + key + " in " + table + " for user " + userHandle);
- }
- }
-
- int deleteForUser(IContentProvider provider, int userHandle,
- final String table, final String key) {
- Uri targetUri;
- if ("system".equals(table)) targetUri = Settings.System.getUriFor(key);
- else if ("secure".equals(table)) targetUri = Settings.Secure.getUriFor(key);
- else if ("global".equals(table)) targetUri = Settings.Global.getUriFor(key);
- else {
- System.err.println("Invalid table; no delete performed");
- throw new IllegalArgumentException("Invalid table " + table);
- }
-
- int num = 0;
- try {
- num = provider.delete(resolveCallingPackage(), targetUri, null, null);
- } catch (RemoteException e) {
- System.err.println("Can't clear key " + key + " in " + table + " for user "
- + userHandle);
- }
- return num;
- }
-
- private static void printUsage() {
- System.err.println("usage: settings [--user <USER_ID> | current] get namespace key");
- System.err.println(" settings [--user <USER_ID> | current] put namespace key value");
- System.err.println(" settings [--user <USER_ID> | current] delete namespace key");
- System.err.println(" settings [--user <USER_ID> | current] list namespace");
- System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
- System.err.println("If '--user <USER_ID> | current' is not given, the operations are "
- + "performed on the system user.");
- }
-
- public static String resolveCallingPackage() {
- switch (android.os.Process.myUid()) {
- case Process.ROOT_UID: {
- return "root";
- }
-
- case Process.SHELL_UID: {
- return "com.android.shell";
- }
-
- default: {
- return null;
- }
- }
- }
-}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 15f6361..141c899 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,6 +752,15 @@
return true;
}
+ case MOVE_STACK_TO_DISPLAY_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int stackId = data.readInt();
+ int displayId = data.readInt();
+ moveStackToDisplay(stackId, displayId);
+ reply.writeNoException();
+ return true;
+ }
+
case MOVE_TASK_TO_FRONT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int task = data.readInt();
@@ -3885,6 +3894,19 @@
reply.recycle();
return list;
}
+ public void moveStackToDisplay(int stackId, int displayId) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(stackId);
+ data.writeInt(displayId);
+ mRemote.transact(MOVE_STACK_TO_DISPLAY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+ @Override
public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index c075ed6..cada1b8 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -448,13 +448,6 @@
mGuard.open("release");
}
- void attachToDisplay(int displayId) {
- try {
- mIActivityContainer.attachToDisplay(displayId);
- } catch (RemoteException e) {
- }
- }
-
void setSurface(Surface surface, int width, int height, int density)
throws RemoteException {
mIActivityContainer.setSurface(surface, width, height, density);
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index 170aff3..1ff3c87 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -25,7 +25,7 @@
/** @hide */
interface IActivityContainer {
- void attachToDisplay(int displayId);
+ void addToDisplay(int displayId);
void setSurface(in Surface surface, int width, int height, int density);
int startActivity(in Intent intent);
int startActivityIntentSender(in IIntentSender intentSender);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 0323651..7166789 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -143,6 +143,7 @@
public List<RunningServiceInfo> getServices(int maxNum, int flags) throws RemoteException;
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
+ public void moveStackToDisplay(int stackId, int displayId) throws RemoteException;
public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
public void moveTaskBackwards(int task) throws RemoteException;
@@ -1100,4 +1101,5 @@
int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
int UNREGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+402;
+ int MOVE_STACK_TO_DISPLAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
}
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index b7715da..d07b407 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -530,14 +530,13 @@
Bitmap bmp = null;
try {
bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
- } catch (Exception e) {
- } finally {
- if (bmp == null) {
- return -1;
+ if (bmp != null) {
+ mBundle.putParcelable(key, bmp);
+ return 0;
}
- mBundle.putParcelable(key, bmp);
- return 0;
+ } catch (Exception e) {
}
+ return -1;
}
int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 89c6eb2..1b905a0 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5161,18 +5161,10 @@
"accessibility_display_magnification_scale";
/**
- * Setting that specifies whether the display magnification should be
- * automatically updated. If this fearture is enabled the system will
- * exit magnification mode or pan the viewport when a context change
- * occurs. For example, on staring a new activity or rotating the screen,
- * the system may zoom out so the user can see the new context he is in.
- * Another example is on showing a window that is not visible in the
- * magnified viewport the system may pan the viewport to make the window
- * the has popped up so the user knows that the context has changed.
- * Whether a screen magnification is performed is controlled by
- * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+ * Unused mangnification setting
*
* @hide
+ * @deprecated
*/
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
"accessibility_display_magnification_auto_update";
@@ -6491,7 +6483,6 @@
ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
- ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
ACCESSIBILITY_SCRIPT_INJECTION,
ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
ENABLED_ACCESSIBILITY_SERVICES,
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index a8d70c4..8a9bb33 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -339,13 +339,6 @@
boolean isVisibleLw();
/**
- * Like {@link #isVisibleLw}, but also counts a window that is currently
- * "hidden" behind the keyguard as visible. This allows us to apply
- * things like window flags that impact the keyguard.
- */
- boolean isVisibleOrBehindKeyguardLw();
-
- /**
* Is this window currently visible to the user on-screen? It is
* displayed either if it is visible or it is currently running an
* animation before no longer being visible. Must be called with the
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 71e77c4..2829744 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -35,16 +35,16 @@
*/
interface IAccessibilityManager {
- int addClient(IAccessibilityManagerClient client, int userId);
+ oneway void interrupt(int userId);
- void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+ oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+
+ int addClient(IAccessibilityManagerClient client, int userId);
List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
- void interrupt(int userId);
-
int addAccessibilityInteractionConnection(IWindow windowToken,
in IAccessibilityInteractionConnection connection, int userId);
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index b971856..accbabd 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -49,8 +49,9 @@
}
void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
- //negative and positive Z order are drawn out of order
- if (MathUtils::isZero(mRenderNode->properties().getZ())) {
+ //negative and positive Z order are drawn out of order, if this render node drawable is in
+ //a reordering section
+ if ((!mInReorderingSection) || MathUtils::isZero(mRenderNode->properties().getZ())) {
this->forceDraw(canvas);
}
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 0762f37..a2ffc6c 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -56,10 +56,12 @@
* we should draw into the contents of the layer or compose the existing contents of the
* layer into the canvas.
*/
- explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true)
+ explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true,
+ bool inReorderingSection = false)
: mRenderNode(node)
, mRecordedTransform(canvas->getTotalMatrix())
- , mComposeLayer(composeLayer) {}
+ , mComposeLayer(composeLayer)
+ , mInReorderingSection(inReorderingSection) {}
/**
* Draws into the canvas this render node and its children. If the node is marked as a
@@ -138,6 +140,11 @@
std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
/*
+ * True if the render node is in a reordering section
+ */
+ bool mInReorderingSection;
+
+ /*
* Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
*/
void drawContent(SkCanvas* canvas) const;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 8a42983..ecc6d51 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -107,7 +107,7 @@
}
// record the child node
- mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas());
+ mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
drawDrawable(&mDisplayList->mChildNodes.back());
// use staging property, since recording on UI thread
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index c68ca4e..623d971 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -51,85 +51,56 @@
ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
}
-TEST(RenderNodeDrawable, drawContent) {
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
- SkCanvas& canvas = *surface->getCanvas();
- canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-
- //create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder
- auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
- [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
- recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
- });
- RenderNodeDrawable drawable(rootNode.get(), &canvas, false);
-
- //negative and positive Z order are drawn out of order
- rootNode->animatorProperties().setElevation(10.0f);
- canvas.drawDrawable(&drawable);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
- rootNode->animatorProperties().setElevation(-10.0f);
- canvas.drawDrawable(&drawable);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-
- //zero Z are drawn immediately
- rootNode->animatorProperties().setElevation(0.0f);
- canvas.drawDrawable(&drawable);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
+ SkPaint paint;
+ // order put in blue channel, transparent so overlapped content doesn't get rejected
+ paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
+ canvas->drawRect(0, 0, 100, 100, paint);
}
-//TODO: another test that verifies equal z values are drawn in order, and barriers prevent Z
-//intermixing (model after FrameBuilder zReorder)
-TEST(RenderNodeDrawable, drawAndReorder) {
- //this test exercises StartReorderBarrierDrawable, EndReorderBarrierDrawable and
- //SkiaRecordingCanvas
- auto surface = SkSurface::MakeRasterN32Premul(4, 4);
- SkCanvas& canvas = *surface->getCanvas();
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) {
+ auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [expectedDrawOrder, z](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedRect(&canvas, expectedDrawOrder);
+ props.setTranslationZ(z);
+ });
+ canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
- canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+TEST(RenderNodeDrawable, zReorder) {
+ class ZReorderCanvas : public SkCanvas {
+ public:
+ ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+ int getIndex() { return mIndex; }
+ protected:
+ int mIndex = 0;
+ };
- //-z draws to all 4 pixels (RED)
- auto redNode = TestUtils::createSkiaNode(0, 0, 4, 4,
- [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
- redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
- props.setElevation(-10.0f);
- }, "redNode");
+ auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
+ [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
+ drawOrderedRect(&canvas, 1);
+ canvas.insertReorderBarrier(true);
+ drawOrderedNode(&canvas, 6, 2.0f);
+ drawOrderedRect(&canvas, 3);
+ drawOrderedNode(&canvas, 4, 0.0f);
+ drawOrderedRect(&canvas, 5);
+ drawOrderedNode(&canvas, 2, -2.0f);
+ drawOrderedNode(&canvas, 7, 2.0f);
+ canvas.insertReorderBarrier(false);
+ drawOrderedRect(&canvas, 8);
+ drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+ });
- //0z draws to bottom 2 pixels (GREEN)
- auto bottomHalfGreenNode = TestUtils::createSkiaNode(0, 0, 4, 4,
- [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
- SkPaint greenPaint;
- greenPaint.setColor(SK_ColorGREEN);
- greenPaint.setStyle(SkPaint::kFill_Style);
- bottomHalfGreenCanvas.drawRect(0, 2, 4, 4, greenPaint);
- props.setElevation(0.0f);
- }, "bottomHalfGreenNode");
-
- //+z draws to right 2 pixels (BLUE)
- auto rightHalfBlueNode = TestUtils::createSkiaNode(0, 0, 4, 4,
- [](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) {
- SkPaint bluePaint;
- bluePaint.setColor(SK_ColorBLUE);
- bluePaint.setStyle(SkPaint::kFill_Style);
- rightHalfBlueCanvas.drawRect(2, 0, 4, 4, bluePaint);
- props.setElevation(10.0f);
- }, "rightHalfBlueNode");
-
- auto rootNode = TestUtils::createSkiaNode(0, 0, 4, 4,
- [&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) {
- rootRecorder.insertReorderBarrier(true);
- //draw in reverse Z order, so Z alters draw order
- rootRecorder.drawRenderNode(rightHalfBlueNode.get());
- rootRecorder.drawRenderNode(bottomHalfGreenNode.get());
- rootRecorder.drawRenderNode(redNode.get());
- }, "rootNode");
-
- RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
- canvas.drawDrawable(&drawable3);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
- ASSERT_EQ(TestUtils::getColor(surface, 0, 3), SK_ColorGREEN);
- ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorBLUE);
+ //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+ ZReorderCanvas canvas(100, 100);
+ RenderNodeDrawable drawable(parent.get(), &canvas, false);
+ canvas.drawDrawable(&drawable);
+ EXPECT_EQ(10, canvas.getIndex());
}
TEST(RenderNodeDrawable, composeOnLayer)
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index bd68fec..64576ec 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -236,18 +236,16 @@
*/
public static final int VOICE_CALL = 4;
- /** Microphone audio source with same orientation as camera if available, the main
- * device microphone otherwise */
+ /** Microphone audio source tuned for video recording, with the same orientation
+ * as the camera if available. */
public static final int CAMCORDER = 5;
- /** Microphone audio source tuned for voice recognition if available, behaves like
- * {@link #DEFAULT} otherwise. */
+ /** Microphone audio source tuned for voice recognition. */
public static final int VOICE_RECOGNITION = 6;
/** Microphone audio source tuned for voice communications such as VoIP. It
* will for instance take advantage of echo cancellation or automatic gain control
- * if available. It otherwise behaves like {@link #DEFAULT} if no voice processing
- * is applied.
+ * if available.
*/
public static final int VOICE_COMMUNICATION = 7;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index d55bb4f..dd543a3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1310,10 +1310,6 @@
loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
R.fraction.def_accessibility_display_magnification_scale, 1);
stmt.close();
- stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
- loadBooleanSetting(stmt,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
- R.bool.def_accessibility_display_magnification_auto_update);
db.setTransactionSuccessful();
} finally {
@@ -2508,10 +2504,6 @@
loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
R.fraction.def_accessibility_display_magnification_scale, 1);
- loadBooleanSetting(stmt,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
- R.bool.def_accessibility_display_magnification_auto_update);
-
loadBooleanSetting(stmt, Settings.Secure.USER_SETUP_COMPLETE,
R.bool.def_user_setup_complete);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f1d1b1f..88ee33c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2175,7 +2175,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 134;
+ private static final int SETTINGS_VERSION = 135;
private final int mUserId;
@@ -2545,6 +2545,14 @@
currentVersion = 134;
}
+ if (currentVersion == 134) {
+ // Remove setting that specifies if magnification values should be preserved.
+ // This setting defaulted to true and never has a UI.
+ getSecureSettingsLocked(userId).deleteSettingLocked(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE);
+ currentVersion = 135;
+ }
+
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b141454..01ffe01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -105,9 +105,7 @@
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- if (mDozeParameters.getAlwaysOn()) {
- mHandler.post(mPulseIn);
- }
+ mHandler.post(mPulseIn);
}
/**
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index ba0725e..48bc27e 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -689,6 +689,7 @@
ACTION_WIFI_ADD_NETWORK = 134;
// ACTION: Settings > Wi-Fi > [Long press network] > Connect to network
+ // SUBTYPE: true if connecting to a saved network, false if not
// CATEGORY: SETTINGS
// OS: 6.0
ACTION_WIFI_CONNECT = 135;
@@ -704,6 +705,7 @@
ACTION_WIFI_FORGET = 137;
// ACTION: Settings > Wi-Fi > Toggle off
+ // SUBTYPE: true if connected to network before toggle, false if not
// CATEGORY: SETTINGS
// OS: 6.0
ACTION_WIFI_OFF = 138;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c89f158..3ea03f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -829,9 +829,10 @@
* @param centerX the new screen-relative center X coordinate
* @param centerY the new screen-relative center Y coordinate
*/
- void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChanged(@NonNull Region region,
float scale, float centerX, float centerY) {
synchronized (mLock) {
+ notifyClearAccessibilityCacheLocked();
notifyMagnificationChangedLocked(region, scale, centerX, centerY);
}
}
@@ -901,10 +902,6 @@
mSecurityPolicy.onTouchInteractionEnd();
}
- void onMagnificationStateChanged() {
- notifyClearAccessibilityCacheLocked();
- }
-
private void switchUser(int userId) {
synchronized (mLock) {
if (mCurrentUserId == userId && mInitialized) {
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 7886b9e..f65046c 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -21,8 +21,6 @@
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -34,12 +32,10 @@
import android.graphics.Region;
import android.os.AsyncTask;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.MathUtils;
-import android.util.Property;
import android.util.Slog;
import android.view.MagnificationSpec;
import android.view.View;
@@ -53,27 +49,29 @@
* from the accessibility manager and related classes. It is responsible for
* holding the current state of magnification and animation, and it handles
* communication between the accessibility manager and window manager.
+ *
+ * Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the
+ * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
+ * constraints.
*/
-class MagnificationController {
+class MagnificationController implements Handler.Callback {
private static final String LOG_TAG = "MagnificationController";
- private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
+ public static final float MIN_SCALE = 1.0f;
+ public static final float MAX_SCALE = 5.0f;
- private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
+ private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private static final int INVALID_ID = -1;
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
- private static final float MIN_SCALE = 1.0f;
- private static final float MAX_SCALE = 5.0f;
-
- /**
- * The minimum scaling factor that can be persisted to secure settings.
- * This must be > 1.0 to ensure that magnification is actually set to an
- * enabled state when the scaling factor is restored from settings.
- */
- private static final float MIN_PERSISTED_SCALE = 2.0f;
+ // Messages
+ private static final int MSG_SEND_SPEC_TO_ANIMATION = 1;
+ private static final int MSG_SCREEN_TURNED_OFF = 2;
+ private static final int MSG_ON_MAGNIFIED_BOUNDS_CHANGED = 3;
+ private static final int MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED = 4;
+ private static final int MSG_ON_USER_CONTEXT_CHANGED = 5;
private final Object mLock;
@@ -90,46 +88,95 @@
private final Rect mTempRect1 = new Rect();
private final AccessibilityManagerService mAms;
- private final ContentResolver mContentResolver;
+
+ private final SettingsBridge mSettingsBridge;
private final ScreenStateObserver mScreenStateObserver;
- private final WindowStateObserver mWindowStateObserver;
private final SpecAnimationBridge mSpecAnimationBridge;
+ private final WindowManagerInternal.MagnificationCallbacks mWMCallbacks =
+ new WindowManagerInternal.MagnificationCallbacks () {
+ @Override
+ public void onMagnificationRegionChanged(Region region) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(region);
+ mHandler.obtainMessage(MSG_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = left;
+ args.argi2 = top;
+ args.argi3 = right;
+ args.argi4 = bottom;
+ mHandler.obtainMessage(MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED, args)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onRotationChanged(int rotation) {
+ // Treat as context change and reset
+ mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
+ }
+
+ @Override
+ public void onUserContextChanged() {
+ mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
+ }
+ };
+
private int mUserId;
+ private final long mMainThreadId;
+
+ private Handler mHandler;
+
private int mIdOfLastServiceToMagnify = INVALID_ID;
+ private final WindowManagerInternal mWindowManager;
+
// Flag indicating that we are registered with window manager.
private boolean mRegistered;
private boolean mUnregisterPending;
public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
+ this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
+ new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
+ mHandler = new Handler(context.getMainLooper(), this);
+ }
+
+ public MagnificationController(Context context, AccessibilityManagerService ams, Object lock,
+ Handler handler, WindowManagerInternal windowManagerInternal,
+ ValueAnimator valueAnimator, SettingsBridge settingsBridge) {
+ mHandler = handler;
+ mWindowManager = windowManagerInternal;
+ mMainThreadId = context.getMainLooper().getThread().getId();
mAms = ams;
- mContentResolver = context.getContentResolver();
mScreenStateObserver = new ScreenStateObserver(context, this);
- mWindowStateObserver = new WindowStateObserver(context, this);
mLock = lock;
- mSpecAnimationBridge = new SpecAnimationBridge(context, mLock);
+ mSpecAnimationBridge = new SpecAnimationBridge(
+ context, mLock, mWindowManager, valueAnimator);
+ mSettingsBridge = settingsBridge;
}
/**
* Start tracking the magnification region for services that control magnification and the
* magnification gesture handler.
*
- * This tracking imposes a cost on the system, so we avoid tracking this data
- * unless it's required.
+ * This tracking imposes a cost on the system, so we avoid tracking this data unless it's
+ * required.
*/
public void register() {
synchronized (mLock) {
if (!mRegistered) {
mScreenStateObserver.register();
- mWindowStateObserver.register();
+ mWindowManager.setMagnificationCallbacks(mWMCallbacks);
mSpecAnimationBridge.setEnabled(true);
// Obtain initial state.
- mWindowStateObserver.getMagnificationRegion(mMagnificationRegion);
+ mWindowManager.getMagnificationRegion(mMagnificationRegion);
mMagnificationRegion.getBounds(mMagnificationBounds);
mRegistered = true;
}
@@ -164,7 +211,7 @@
if (mRegistered) {
mSpecAnimationBridge.setEnabled(false);
mScreenStateObserver.unregister();
- mWindowStateObserver.unregister();
+ mWindowManager.setMagnificationCallbacks(null);
mMagnificationRegion.setEmpty();
mRegistered = false;
}
@@ -183,40 +230,22 @@
* Update our copy of the current magnification region
*
* @param magnified the magnified region
- * @param updateSpec {@code true} to update the scale and center based on
- * the region bounds, {@code false} to leave them as-is
*/
- private void onMagnificationRegionChanged(Region magnified, boolean updateSpec) {
+ private void onMagnificationRegionChanged(Region magnified) {
synchronized (mLock) {
if (!mRegistered) {
// Don't update if we've unregistered
return;
}
- boolean magnificationChanged = false;
- boolean boundsChanged = false;
-
if (!mMagnificationRegion.equals(magnified)) {
mMagnificationRegion.set(magnified);
mMagnificationRegion.getBounds(mMagnificationBounds);
- boundsChanged = true;
- }
- if (updateSpec) {
- final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
- final float scale = sentSpec.scale;
- final float offsetX = sentSpec.offsetX;
- final float offsetY = sentSpec.offsetY;
-
- // Compute the new center and update spec as needed.
- final float centerX = (mMagnificationBounds.width() / 2.0f
- + mMagnificationBounds.left - offsetX) / scale;
- final float centerY = (mMagnificationBounds.height() / 2.0f
- + mMagnificationBounds.top - offsetY) / scale;
- magnificationChanged = setScaleAndCenterLocked(
- scale, centerX, centerY, false, INVALID_ID);
- }
-
- // If magnification changed we already notified for the change.
- if (boundsChanged && updateSpec && !magnificationChanged) {
+ // It's possible that our magnification spec is invalid with the new bounds.
+ // Adjust the current spec's offsets if necessary.
+ if (updateCurrentSpecWithOffsetsLocked(
+ mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
+ sendSpecToAnimation(mCurrentMagnificationSpec, false);
+ }
onMagnificationChangedLocked();
}
}
@@ -328,7 +357,7 @@
*
* @return the scale currently used by the window manager
*/
- public float getSentScale() {
+ private float getSentScale() {
return mSpecAnimationBridge.mSentMagnificationSpec.scale;
}
@@ -339,7 +368,7 @@
*
* @return the X offset currently used by the window manager
*/
- public float getSentOffsetX() {
+ private float getSentOffsetX() {
return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
}
@@ -350,7 +379,7 @@
*
* @return the Y offset currently used by the window manager
*/
- public float getSentOffsetY() {
+ private float getSentOffsetY() {
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
@@ -380,7 +409,7 @@
onMagnificationChangedLocked();
}
mIdOfLastServiceToMagnify = INVALID_ID;
- mSpecAnimationBridge.updateSentSpec(spec, animate);
+ sendSpecToAnimation(spec, animate);
return changed;
}
@@ -475,7 +504,7 @@
private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
boolean animate, int id) {
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
- mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+ sendSpecToAnimation(mCurrentMagnificationSpec, animate);
if (isMagnifying() && (id != INVALID_ID)) {
mIdOfLastServiceToMagnify = id;
}
@@ -483,27 +512,28 @@
}
/**
- * Offsets the center of the magnified region.
+ * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
+ * opposite direction as the offsets passed in here.
*
- * @param offsetX the amount in pixels to offset the X center
- * @param offsetY the amount in pixels to offset the Y center
+ * @param offsetX the amount in pixels to offset the region in the X direction, in current
+ * screen pixels.
+ * @param offsetY the amount in pixels to offset the region in the Y direction, in current
+ * screen pixels.
* @param id the ID of the service requesting the change
*/
- public void offsetMagnifiedRegionCenter(float offsetX, float offsetY, int id) {
+ public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
synchronized (mLock) {
if (!mRegistered) {
return;
}
- final MagnificationSpec currSpec = mCurrentMagnificationSpec;
- final float nonNormOffsetX = currSpec.offsetX - offsetX;
- currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
- final float nonNormOffsetY = currSpec.offsetY - offsetY;
- currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
if (id != INVALID_ID) {
mIdOfLastServiceToMagnify = id;
}
- mSpecAnimationBridge.updateSentSpec(currSpec, false);
+ sendSpecToAnimation(mCurrentMagnificationSpec, false);
}
}
@@ -517,7 +547,6 @@
}
private void onMagnificationChangedLocked() {
- mAms.onMagnificationStateChanged();
mAms.notifyMagnificationChanged(mMagnificationRegion,
getScale(), getCenterX(), getCenterY());
if (mUnregisterPending && !isMagnifying()) {
@@ -535,8 +564,7 @@
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- Settings.Secure.putFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+ mSettingsBridge.putMagnificationScale(scale, userId);
return null;
}
}.execute();
@@ -550,9 +578,7 @@
* scale if none is available
*/
public float getPersistedScale() {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
- DEFAULT_MAGNIFICATION_SCALE, mUserId);
+ return mSettingsBridge.getMagnificationScale(mUserId);
}
/**
@@ -578,36 +604,20 @@
scale = getScale();
}
- // Ensure requested center is within the magnification region.
- if (!magnificationRegionContains(centerX, centerY)) {
- return false;
- }
-
// Compute changes.
- final MagnificationSpec currSpec = mCurrentMagnificationSpec;
boolean changed = false;
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
- if (Float.compare(currSpec.scale, normScale) != 0) {
- currSpec.scale = normScale;
+ if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
+ mCurrentMagnificationSpec.scale = normScale;
changed = true;
}
final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
- + mMagnificationBounds.left - centerX * scale;
- final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
- if (Float.compare(currSpec.offsetX, offsetX) != 0) {
- currSpec.offsetX = offsetX;
- changed = true;
- }
-
+ + mMagnificationBounds.left - centerX * normScale;
final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
- + mMagnificationBounds.top - centerY * scale;
- final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
- if (Float.compare(currSpec.offsetY, offsetY) != 0) {
- currSpec.offsetY = offsetY;
- changed = true;
- }
+ + mMagnificationBounds.top - centerY * normScale;
+ changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
if (changed) {
onMagnificationChangedLocked();
@@ -616,6 +626,21 @@
return changed;
}
+ private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
+ boolean changed = false;
+ final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+ if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
+ mCurrentMagnificationSpec.offsetX = offsetX;
+ changed = true;
+ }
+ final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) {
+ mCurrentMagnificationSpec.offsetY = offsetY;
+ changed = true;
+ }
+ return changed;
+ }
+
private float getMinOffsetXLocked() {
final float viewportWidth = mMagnificationBounds.width();
return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
@@ -643,12 +668,6 @@
}
}
- private boolean isScreenMagnificationAutoUpdateEnabled() {
- return (Settings.Secure.getInt(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
- DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
- }
-
/**
* Resets magnification if magnification and auto-update are both enabled.
*
@@ -658,7 +677,7 @@
*/
boolean resetIfNeeded(boolean animate) {
synchronized (mLock) {
- if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+ if (isMagnifying()) {
reset(animate);
return true;
}
@@ -715,18 +734,61 @@
}
final float scale = getScale();
- offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale, INVALID_ID);
+ offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID);
}
}
+ private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
+ } else {
+ mHandler.obtainMessage(MSG_SEND_SPEC_TO_ANIMATION,
+ animate ? 1 : 0, 0, spec).sendToTarget();
+ }
+ }
+
+ private void onScreenTurnedOff() {
+ mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF);
+ }
+
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_SPEC_TO_ANIMATION:
+ final boolean animate = msg.arg1 == 1;
+ final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+ mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
+ break;
+ case MSG_SCREEN_TURNED_OFF:
+ resetIfNeeded(false);
+ break;
+ case MSG_ON_MAGNIFIED_BOUNDS_CHANGED: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final Region magnifiedBounds = (Region) args.arg1;
+ onMagnificationRegionChanged(magnifiedBounds);
+ magnifiedBounds.recycle();
+ args.recycle();
+ } break;
+ case MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ requestRectangleOnScreen(left, top, right, bottom);
+ args.recycle();
+ } break;
+ case MSG_ON_USER_CONTEXT_CHANGED:
+ resetIfNeeded(true);
+ break;
+ }
+ return true;
+ }
+
/**
* Class responsible for animating spec on the main thread and sending spec
* updates to the window manager.
*/
- private static class SpecAnimationBridge {
- private static final int ACTION_UPDATE_SPEC = 1;
-
- private final Handler mHandler;
+ private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
private final WindowManagerInternal mWindowManager;
/**
@@ -735,34 +797,33 @@
*/
private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
- /**
- * The animator that updates the sent spec. This should only be accessed
- * and modified on the main (e.g. animation) thread.
- */
- private final ValueAnimator mTransformationAnimator;
+ private final MagnificationSpec mStartMagnificationSpec = MagnificationSpec.obtain();
- private final long mMainThreadId;
+ private final MagnificationSpec mEndMagnificationSpec = MagnificationSpec.obtain();
+
+ private final MagnificationSpec mTmpMagnificationSpec = MagnificationSpec.obtain();
+
+ /**
+ * The animator should only be accessed and modified on the main (e.g. animation) thread.
+ */
+ private final ValueAnimator mValueAnimator;
+
private final Object mLock;
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(Context context, Object lock) {
+ private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
+ ValueAnimator animator) {
mLock = lock;
- final Looper mainLooper = context.getMainLooper();
- mMainThreadId = mainLooper.getThread().getId();
-
- mHandler = new UpdateHandler(context);
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
- final MagnificationSpecProperty property = new MagnificationSpecProperty();
- final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+ mWindowManager = wm;
final long animationDuration = context.getResources().getInteger(
R.integer.config_longAnimTime);
- mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
- mSentMagnificationSpec);
- mTransformationAnimator.setDuration(animationDuration);
- mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ mValueAnimator = animator;
+ mValueAnimator.setDuration(animationDuration);
+ mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ mValueAnimator.setFloatValues(0.0f, 1.0f);
+ mValueAnimator.addUpdateListener(this);
}
/**
@@ -781,22 +842,9 @@
}
}
- public void updateSentSpec(MagnificationSpec spec, boolean animate) {
- if (Thread.currentThread().getId() == mMainThreadId) {
- // Already on the main thread, don't bother proxying.
- updateSentSpecInternal(spec, animate);
- } else {
- mHandler.obtainMessage(ACTION_UPDATE_SPEC,
- animate ? 1 : 0, 0, spec).sendToTarget();
- }
- }
-
- /**
- * Updates the sent spec.
- */
- private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
+ public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
+ if (mValueAnimator.isRunning()) {
+ mValueAnimator.cancel();
}
// If the current and sent specs don't match, update the sent spec.
@@ -812,11 +860,6 @@
}
}
- private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
- mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
- mTransformationAnimator.start();
- }
-
private void setMagnificationSpecLocked(MagnificationSpec spec) {
if (mEnabled) {
if (DEBUG_SET_MAGNIFICATION_SPEC) {
@@ -828,71 +871,40 @@
}
}
- private class UpdateHandler extends Handler {
- public UpdateHandler(Context context) {
- super(context.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ACTION_UPDATE_SPEC:
- final boolean animate = msg.arg1 == 1;
- final MagnificationSpec spec = (MagnificationSpec) msg.obj;
- updateSentSpecInternal(spec, animate);
- break;
- }
- }
+ private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
+ mEndMagnificationSpec.setTo(toSpec);
+ mStartMagnificationSpec.setTo(mSentMagnificationSpec);
+ mValueAnimator.start();
}
- private static class MagnificationSpecProperty
- extends Property<SpecAnimationBridge, MagnificationSpec> {
- public MagnificationSpecProperty() {
- super(MagnificationSpec.class, "spec");
- }
-
- @Override
- public MagnificationSpec get(SpecAnimationBridge object) {
- synchronized (object.mLock) {
- return object.mSentMagnificationSpec;
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ synchronized (mLock) {
+ if (mEnabled) {
+ float fract = animation.getAnimatedFraction();
+ mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale +
+ (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
+ mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX +
+ (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
+ * fract;
+ mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
+ (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
+ * fract;
+ synchronized (mLock) {
+ setMagnificationSpecLocked(mTmpMagnificationSpec);
+ }
}
}
-
- @Override
- public void set(SpecAnimationBridge object, MagnificationSpec value) {
- synchronized (object.mLock) {
- object.setMagnificationSpecLocked(value);
- }
- }
- }
-
- private static class MagnificationSpecEvaluator
- implements TypeEvaluator<MagnificationSpec> {
- private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
-
- @Override
- public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- final MagnificationSpec result = mTempSpec;
- result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
- result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
- result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
- return result;
- }
}
}
private static class ScreenStateObserver extends BroadcastReceiver {
- private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
private final Context mContext;
private final MagnificationController mController;
- private final Handler mHandler;
public ScreenStateObserver(Context context, MagnificationController controller) {
mContext = context;
mController = controller;
- mHandler = new StateChangeHandler(context);
}
public void register() {
@@ -905,151 +917,27 @@
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
- intent.getAction()).sendToTarget();
- }
-
- private void handleOnScreenStateChange() {
- mController.resetIfNeeded(false);
- }
-
- private class StateChangeHandler extends Handler {
- public StateChangeHandler(Context context) {
- super(context.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_SCREEN_STATE_CHANGE:
- handleOnScreenStateChange();
- break;
- }
- }
+ mController.onScreenTurnedOff();
}
}
- /**
- * This class handles the screen magnification when accessibility is enabled.
- */
- private static class WindowStateObserver
- implements WindowManagerInternal.MagnificationCallbacks {
- private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
- private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
- private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
- private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+ // Extra class to get settings so tests can mock it
+ public static class SettingsBridge {
+ private final ContentResolver mContentResolver;
- private final MagnificationController mController;
- private final WindowManagerInternal mWindowManager;
- private final Handler mHandler;
-
- private boolean mSpecIsDirty;
-
- public WindowStateObserver(Context context, MagnificationController controller) {
- mController = controller;
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
- mHandler = new CallbackHandler(context);
+ public SettingsBridge(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
}
- public void register() {
- mWindowManager.setMagnificationCallbacks(this);
+ public void putMagnificationScale(float value, int userId) {
+ Settings.Secure.putFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
}
- public void unregister() {
- mWindowManager.setMagnificationCallbacks(null);
- }
-
- @Override
- public void onMagnificationRegionChanged(Region magnificationRegion) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = Region.obtain(magnificationRegion);
- mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
- }
-
- private void handleOnMagnifiedBoundsChanged(Region magnificationRegion) {
- mController.onMagnificationRegionChanged(magnificationRegion, mSpecIsDirty);
- mSpecIsDirty = false;
- }
-
- @Override
- public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- final SomeArgs args = SomeArgs.obtain();
- args.argi1 = left;
- args.argi2 = top;
- args.argi3 = right;
- args.argi4 = bottom;
- mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
- }
-
- private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- mController.requestRectangleOnScreen(left, top, right, bottom);
- }
-
- @Override
- public void onRotationChanged(int rotation) {
- mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
- }
-
- private void handleOnRotationChanged() {
- // If there was a rotation and magnification is still enabled,
- // we'll need to rewrite the spec to reflect the new screen
- // configuration. Conveniently, we'll receive a callback from
- // the window manager with updated bounds for the magnified
- // region.
- mSpecIsDirty = !mController.resetIfNeeded(true);
- }
-
- @Override
- public void onUserContextChanged() {
- mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
- }
-
- private void handleOnUserContextChanged() {
- mController.resetIfNeeded(true);
- }
-
- /**
- * This method is used to get the magnification region in the tiny time slice between
- * registering the callbacks and handling the message.
- * TODO: Elimiante this extra path, perhaps by processing the message immediately
- *
- * @param outMagnificationRegion
- */
- public void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
- mWindowManager.getMagnificationRegion(outMagnificationRegion);
- }
-
- private class CallbackHandler extends Handler {
- public CallbackHandler(Context context) {
- super(context.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
- final SomeArgs args = (SomeArgs) message.obj;
- final Region magnifiedBounds = (Region) args.arg1;
- handleOnMagnifiedBoundsChanged(magnifiedBounds);
- magnifiedBounds.recycle();
- } break;
- case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
- final SomeArgs args = (SomeArgs) message.obj;
- final int left = args.argi1;
- final int top = args.argi2;
- final int right = args.argi3;
- final int bottom = args.argi4;
- handleOnRectangleOnScreenRequested(left, top, right, bottom);
- args.recycle();
- } break;
- case MESSAGE_ON_USER_CONTEXT_CHANGED: {
- handleOnUserContextChanged();
- } break;
- case MESSAGE_ON_ROTATION_CHANGED: {
- handleOnRotationChanged();
- } break;
- }
- }
+ public float getMagnificationScale(int userId) {
+ return Settings.Secure.getFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ DEFAULT_MAGNIFICATION_SCALE, userId);
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 39bc809..f6e5340 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -381,7 +381,7 @@
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY,
+ mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return true;
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 6e871a8..2c4cfb9 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -16,7 +16,9 @@
package com.android.server;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import com.android.internal.content.PackageMonitor;
@@ -113,6 +115,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerInternal;
import android.view.inputmethod.EditorInfo;
@@ -468,6 +471,7 @@
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
+ private IBinder mSwitchingDialogToken = new Binder();
private View mSwitchingDialogTitleView;
private Toast mSubtypeSwitchedByShortCutToast;
private InputMethodInfo[] mIms;
@@ -3288,11 +3292,16 @@
mSwitchingDialog = mDialogBuilder.create();
mSwitchingDialog.setCanceledOnTouchOutside(true);
- mSwitchingDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
- mSwitchingDialog.getWindow().getAttributes().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
- mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
+ final Window w = mSwitchingDialog.getWindow();
+ final WindowManager.LayoutParams attrs = w.getAttributes();
+ w.setType(TYPE_INPUT_METHOD_DIALOG);
+ // Use an alternate token for the dialog for that window manager can group the token
+ // with other IME windows based on type vs. grouping based on whichever token happens
+ // to get selected by the system later on.
+ attrs.token = mSwitchingDialogToken;
+ attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ attrs.setTitle("Select input method");
+ w.setAttributes(attrs);
updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
mSwitchingDialog.show();
}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 0cad814..a2207b2 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -585,6 +585,20 @@
Slog.e(TAG, "Unable to remove tied profile key", e);
}
}
+
+ boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+ // and device management the lockscreen must be re-enabled now for users that upgrade.
+ if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
+ setString("migrated_wear_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
+ }
} catch (RemoteException re) {
Slog.e(TAG, "Unable to migrate old data", re);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5d41d36..5aee770 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9513,6 +9513,22 @@
}
@Override
+ public void moveStackToDisplay(int stackId, int displayId) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveStackToDisplay()");
+
+ synchronized (this) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveStackToDisplay: moving stackId=" + stackId
+ + " to displayId=" + displayId);
+ mStackSupervisor.moveStackToDisplayLocked(stackId, displayId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
public boolean removeTask(int taskId) {
enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
synchronized (this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8234f35..3efd300 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -215,6 +215,8 @@
return runGetInactive(pw);
case "send-trim-memory":
return runSendTrimMemory(pw);
+ case "display":
+ return runDisplay(pw);
case "stack":
return runStack(pw);
case "task":
@@ -1631,12 +1633,23 @@
return 0;
}
+ int runDisplay(PrintWriter pw) throws RemoteException {
+ String op = getNextArgRequired();
+ switch (op) {
+ case "move-stack":
+ return runDisplayMoveStack(pw);
+ default:
+ getErrPrintWriter().println("Error: unknown command '" + op + "'");
+ return -1;
+ }
+ }
+
int runStack(PrintWriter pw) throws RemoteException {
String op = getNextArgRequired();
switch (op) {
case "start":
return runStackStart(pw);
- case "movetask":
+ case "move-task":
return runStackMoveTask(pw);
case "resize":
return runStackResize(pw);
@@ -1691,6 +1704,15 @@
return new Rect(left, top, right, bottom);
}
+ int runDisplayMoveStack(PrintWriter pw) throws RemoteException {
+ String stackIdStr = getNextArgRequired();
+ int stackId = Integer.parseInt(stackIdStr);
+ String displayIdStr = getNextArgRequired();
+ int displayId = Integer.parseInt(displayIdStr);
+ mInterface.moveStackToDisplay(stackId, displayId);
+ return 0;
+ }
+
int runStackStart(PrintWriter pw) throws RemoteException {
String displayIdStr = getNextArgRequired();
int displayId = Integer.parseInt(displayIdStr);
@@ -2450,10 +2472,13 @@
pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>");
pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
pw.println(" Send a memory trim event to a <PROCESS>.");
+ pw.println(" display [COMMAND] [...]: sub-commands for operating on displays.");
+ pw.println(" move-stack <STACK_ID> <DISPLAY_ID>");
+ pw.println(" Move <STACK_ID> from its current display to <DISPLAY_ID>.");
pw.println(" stack [COMMAND] [...]: sub-commands for operating on activity stacks.");
pw.println(" start <DISPLAY_ID> <INTENT>");
pw.println(" Start a new activity on <DISPLAY_ID> using <INTENT>");
- pw.println(" movetask <TASK_ID> <STACK_ID> [true|false]");
+ pw.println(" move-task <TASK_ID> <STACK_ID> [true|false]");
pw.println(" Move <TASK_ID> from its current stack to the top (true) or");
pw.println(" bottom (false) of <STACK_ID>.");
pw.println(" resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1c3a8851..0d79980 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -449,10 +449,23 @@
? new LaunchingTaskPositioner() : null;
}
- void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+ /** Adds the stack to specified display and calls WindowManager to do the same. */
+ void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+ final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
+ onTop);
+ postAddToDisplay(activityDisplay, bounds);
+ }
+
+ /**
+ * Updates internal state after adding to new display.
+ * @param activityDisplay New display to which this stack was attached.
+ * @param bounds Updated bounds.
+ */
+ private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
+ Rect bounds) {
mDisplayId = activityDisplay.mDisplayId;
mStacks = activityDisplay.mStacks;
- mBounds = mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
+ mBounds = bounds;
mFullscreen = mBounds == null;
if (mTaskPositioner != null) {
mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -468,7 +481,21 @@
}
}
- void remove() {
+ /**
+ * Moves the stack to specified display.
+ * @param activityDisplay Target display to move the stack to.
+ */
+ void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
+ removeFromDisplay();
+ final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
+ postAddToDisplay(activityDisplay, bounds);
+ }
+
+ /**
+ * Updates the inner state of the stack to remove it from its current parent, so it can be
+ * either destroyed completely or re-parented.
+ */
+ private void removeFromDisplay() {
mDisplayId = Display.INVALID_DISPLAY;
mStacks = null;
if (mTaskPositioner != null) {
@@ -480,6 +507,11 @@
mStackSupervisor.resizeDockedStackLocked(
null, null, null, null, null, PRESERVE_WINDOWS);
}
+ }
+
+ /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
+ void remove() {
+ removeFromDisplay();
mStackSupervisor.deleteActivityContainerRecord(mStackId);
mWindowManager.removeStack(mStackId);
onParentChanged();
@@ -696,11 +728,7 @@
}
mStacks.add(addIndex, this);
-
- // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
- if (isOnHomeDisplay()) {
- mStackSupervisor.setFocusStackUnchecked(reason, this);
- }
+ mStackSupervisor.setFocusStackUnchecked(reason, this);
if (task != null) {
insertTaskAtTop(task, null);
return;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index eed8dd7..6167671 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2300,7 +2300,7 @@
ActivityContainer activityContainer = new ActivityContainer(stackId);
mActivityContainers.put(stackId, activityContainer);
- activityContainer.attachToDisplayLocked(activityDisplay, onTop);
+ activityContainer.addToDisplayLocked(activityDisplay, onTop);
return activityContainer.mStack;
}
@@ -2364,6 +2364,40 @@
}
/**
+ * Move stack with all its existing content to specified display.
+ * @param stackId Id of stack to move.
+ * @param displayId Id of display to move stack to.
+ */
+ void moveStackToDisplayLocked(int stackId, int displayId) {
+ final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ if (activityDisplay == null) {
+ throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
+ + displayId);
+ }
+ final ActivityContainer activityContainer = mActivityContainers.get(stackId);
+ if (activityContainer != null) {
+ if (activityContainer.isAttachedLocked()) {
+ if (activityContainer.getDisplayId() == displayId) {
+ throw new IllegalArgumentException("Trying to move stackId=" + stackId
+ + " to its current displayId=" + displayId);
+ }
+
+ activityContainer.moveToDisplayLocked(activityDisplay);
+ } else {
+ throw new IllegalStateException("moveStackToDisplayLocked: Stack with stackId="
+ + stackId + " is not attached to any display.");
+ }
+ } else {
+ throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
+ + stackId);
+ }
+
+ ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
+ !PRESERVE_WINDOWS);
+ // TODO(multi-display): resize stacks properly if moved from split-screen.
+ }
+
+ /**
* Moves the specified task record to the input stack id.
* WARNING: This method performs an unchecked/raw move of the task and
* can leave the system in an unstable state if used incorrectly.
@@ -4032,22 +4066,33 @@
}
}
- void attachToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
- if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
+ /**
+ * Adds the stack to specified display. Also calls WindowManager to do the same from
+ * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+ * @param activityDisplay The display to add the stack to.
+ * @param onTop If true the stack will be place at the top of the display, else at the
+ * bottom.
+ */
+ void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
+ " to display=" + activityDisplay + " onTop=" + onTop);
+ if (mActivityDisplay != null) {
+ throw new IllegalStateException("ActivityContainer is already attached, " +
+ "displayId=" + mActivityDisplay.mDisplayId);
+ }
mActivityDisplay = activityDisplay;
- mStack.attachDisplay(activityDisplay, onTop);
+ mStack.addToDisplay(activityDisplay, onTop);
activityDisplay.attachActivities(mStack, onTop);
}
@Override
- public void attachToDisplay(int displayId) {
+ public void addToDisplay(int displayId) {
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
return;
}
- attachToDisplayLocked(activityDisplay, true);
+ addToDisplayLocked(activityDisplay, true);
}
}
@@ -4103,16 +4148,44 @@
}
}
+ /** Remove the stack completely. */
void removeLocked() {
if (DEBUG_STACK) Slog.d(TAG_STACK, "removeLocked: " + this + " from display="
+ mActivityDisplay + " Callers=" + Debug.getCallers(2));
if (mActivityDisplay != null) {
- mActivityDisplay.detachActivitiesLocked(mStack);
- mActivityDisplay = null;
+ removeFromDisplayLocked();
}
mStack.remove();
}
+ /**
+ * Remove the stack from its current {@link ActivityDisplay}, so it can be either destroyed
+ * completely or re-parented.
+ */
+ private void removeFromDisplayLocked() {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
+ + " current displayId=" + mActivityDisplay.mDisplayId);
+
+ mActivityDisplay.detachActivitiesLocked(mStack);
+ mActivityDisplay = null;
+ }
+
+ /**
+ * Move the stack to specified display.
+ * @param activityDisplay Target display to move the stack to.
+ */
+ void moveToDisplayLocked(ActivityDisplay activityDisplay) {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveToDisplayLocked: " + this + " from display="
+ + mActivityDisplay + " to display=" + activityDisplay
+ + " Callers=" + Debug.getCallers(2));
+
+ removeFromDisplayLocked();
+
+ mActivityDisplay = activityDisplay;
+ mStack.moveToDisplay(activityDisplay);
+ activityDisplay.attachActivities(mStack, ON_TOP);
+ }
+
@Override
public final int startActivity(Intent intent) {
return mService.startActivity(intent, this);
@@ -4232,7 +4305,7 @@
new VirtualActivityDisplay(width, height, density);
mActivityDisplay = virtualActivityDisplay;
mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
- attachToDisplayLocked(virtualActivityDisplay, true);
+ addToDisplayLocked(virtualActivityDisplay, true);
}
if (mSurface != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f351f6..87f1cf8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3109,14 +3109,28 @@
boolean success =
handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
handleDeviceConnection(connected, inDevice, address, btDeviceName);
- if (success) {
- synchronized (mScoClients) {
- if (connected) {
- mBluetoothHeadsetDevice = btDevice;
- } else {
- mBluetoothHeadsetDevice = null;
- resetBluetoothSco();
- }
+
+ if (!success) {
+ return;
+ }
+
+ /* When one BT headset is disconnected while another BT headset
+ * is connected, don't mess with the headset device.
+ */
+ if ((state == BluetoothProfile.STATE_DISCONNECTED ||
+ state == BluetoothProfile.STATE_DISCONNECTING) &&
+ mBluetoothHeadset != null &&
+ mBluetoothHeadset.getAudioState(btDevice) == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ Log.w(TAG, "SCO connected through another device, returning");
+ return;
+ }
+
+ synchronized (mScoClients) {
+ if (connected) {
+ mBluetoothHeadsetDevice = btDevice;
+ } else {
+ mBluetoothHeadsetDevice = null;
+ resetBluetoothSco();
}
}
}
@@ -5264,7 +5278,6 @@
state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
BluetoothProfile.STATE_DISCONNECTED);
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
setBtScoDeviceConnectionState(btDevice, state);
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
boolean broadcast = false;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2ca0db4..b2a5966 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2549,11 +2549,11 @@
return 2;
}
switch (type) {
- case TYPE_PRESENTATION:
- case TYPE_PRIVATE_PRESENTATION:
- return 2;
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the window manager may move it.
+ return 1;
+ case TYPE_PRESENTATION:
+ case TYPE_PRIVATE_PRESENTATION:
return 2;
case TYPE_DOCK_DIVIDER:
return 2;
@@ -5089,12 +5089,12 @@
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
- if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw()
- && win.isDisplayedLw() && !win.getGivenInsetsPendingLw()) {
+ if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
+ && !win.getGivenInsetsPendingLw()) {
setLastInputMethodWindowLw(null, null);
offsetInputMethodWindowLw(win);
}
- if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
+ if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
&& !win.getGivenInsetsPendingLw()) {
offsetVoiceInputWindowLw(win);
}
@@ -5168,12 +5168,11 @@
@Override
public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached, WindowState imeTarget) {
- if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw="
- + win.isVisibleOrBehindKeyguardLw());
+ final boolean visible = win.isVisibleLw();
+ if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisible=" + visible);
applyKeyguardPolicyLw(win, imeTarget);
final int fl = PolicyControl.getWindowFlags(win, attrs);
- if (mTopFullscreenOpaqueWindowState == null
- && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) {
+ if (mTopFullscreenOpaqueWindowState == null && visible && attrs.type == TYPE_INPUT_METHOD) {
mForcingShowNavBar = true;
mForcingShowNavBarLayer = win.getSurfaceLayer();
}
@@ -5189,8 +5188,7 @@
boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type < FIRST_SYSTEM_WINDOW;
final int stackId = win.getStackId();
- if (mTopFullscreenOpaqueWindowState == null &&
- win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) {
+ if (mTopFullscreenOpaqueWindowState == null && visible) {
if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
mForceStatusBar = true;
}
@@ -5221,10 +5219,8 @@
}
}
- final boolean reallyVisible = win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw();
-
// Voice interaction overrides both top fullscreen and top docked.
- if (reallyVisible && win.getAttrs().type == TYPE_VOICE_INTERACTION) {
+ if (visible && win.getAttrs().type == TYPE_VOICE_INTERACTION) {
if (mTopFullscreenOpaqueWindowState == null) {
mTopFullscreenOpaqueWindowState = win;
if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
@@ -5240,7 +5236,7 @@
}
// Keep track of the window if it's dimming but not necessarily fullscreen.
- if (mTopFullscreenOpaqueOrDimmingWindowState == null && reallyVisible
+ if (mTopFullscreenOpaqueOrDimmingWindowState == null && visible
&& win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
mTopFullscreenOpaqueOrDimmingWindowState = win;
}
@@ -5248,7 +5244,7 @@
// We need to keep track of the top "fullscreen" opaque window for the docked stack
// separately, because both the "real fullscreen" opaque window and the one for the docked
// stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
- if (mTopDockedOpaqueWindowState == null && reallyVisible && appWindow && attached == null
+ if (mTopDockedOpaqueWindowState == null && visible && appWindow && attached == null
&& isFullscreen(attrs) && stackId == DOCKED_STACK_ID) {
mTopDockedOpaqueWindowState = win;
if (mTopDockedOpaqueOrDimmingWindowState == null) {
@@ -5258,7 +5254,7 @@
// Also keep track of any windows that are dimming but not necessarily fullscreen in the
// docked stack.
- if (mTopDockedOpaqueOrDimmingWindowState == null && reallyVisible && win.isDimming()
+ if (mTopDockedOpaqueOrDimmingWindowState == null && visible && win.isDimming()
&& stackId == DOCKED_STACK_ID) {
mTopDockedOpaqueOrDimmingWindowState = win;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 353d663..62f5468 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1014,6 +1014,9 @@
synchronized (mDeviceLockedForUser) {
mDeviceLockedForUser.delete(userId);
}
+ synchronized (mTrustUsuallyManagedForUser) {
+ mTrustUsuallyManagedForUser.delete(userId);
+ }
refreshAgentList(userId);
refreshDeviceLockedForUser(userId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 243c4a5..ec4cdf2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,6 +53,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@@ -74,6 +76,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -137,9 +140,6 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -154,14 +154,24 @@
/** Unique identifier of this stack. */
private final int mDisplayId;
- // The display only has 2 child window containers. mTaskStackContainers which contains all
- // window containers that are related to apps (Activities) and mNonAppWindowContainers which
- // contains all window containers not related to apps (e.g. Status bar).
+ /** The containers below are the only child containers the display can have. */
+ // Contains all window containers that are related to apps (Activities)
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
- private final NonAppWindowContainers mNonAppWindowContainers = new NonAppWindowContainers();
+ // Contains all non-app window containers that should be displayed above the app containers
+ // (e.g. Status bar)
+ private final NonAppWindowContainers mAboveAppWindowsContainers =
+ new NonAppWindowContainers("mAboveAppWindowsContainers");
+ // Contains all non-app window containers that should be displayed below the app containers
+ // (e.g. Wallpaper).
+ private final NonAppWindowContainers mBelowAppWindowsContainers =
+ new NonAppWindowContainers("mBelowAppWindowsContainers");
+ // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
+ // on the IME target. We mainly have this container grouping so we can keep track of all the IME
+ // window containers together and move them in-sync if/when needed.
+ private final NonAppWindowContainers mImeWindowsContainers =
+ new NonAppWindowContainers("mImeWindowsContainers");
- /** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element
- * from mDisplayWindows; */
+ // Z-ordered (bottom-most first) list of all Window objects.
private final WindowList mWindows = new WindowList();
// Mapping from a token IBinder to a WindowToken object on this display.
@@ -184,6 +194,7 @@
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
+ // TODO(multi-display): remove some of the usages.
final boolean isDefaultDisplay;
/** Window tokens that are in the process of exiting, but still on screen for animations. */
@@ -266,8 +277,10 @@
mDimLayerController = new DimLayerController(this);
// These are the only direct children we should ever have and they are permanent.
+ super.addChild(mBelowAppWindowsContainers, null);
super.addChild(mTaskStackContainers, null);
- super.addChild(mNonAppWindowContainers, null);
+ super.addChild(mAboveAppWindowsContainers, null);
+ super.addChild(mImeWindowsContainers, null);
}
int getDisplayId() {
@@ -301,14 +314,36 @@
if (token.asAppWindowToken() == null) {
// Add non-app token to container hierarchy on the display. App tokens are added through
// the parent container managing them (e.g. Tasks).
- mNonAppWindowContainers.addChild(token, null);
+ switch (token.windowType) {
+ case TYPE_WALLPAPER:
+ mBelowAppWindowsContainers.addChild(token);
+ break;
+ case TYPE_INPUT_METHOD:
+ case TYPE_INPUT_METHOD_DIALOG:
+ mImeWindowsContainers.addChild(token);
+ break;
+ default:
+ mAboveAppWindowsContainers.addChild(token);
+ break;
+ }
}
}
WindowToken removeWindowToken(IBinder binder) {
final WindowToken token = mTokenMap.remove(binder);
if (token != null && token.asAppWindowToken() == null) {
- mNonAppWindowContainers.removeChild(token);
+ switch (token.windowType) {
+ case TYPE_WALLPAPER:
+ mBelowAppWindowsContainers.removeChild(token);
+ break;
+ case TYPE_INPUT_METHOD:
+ case TYPE_INPUT_METHOD_DIALOG:
+ mImeWindowsContainers.removeChild(token);
+ break;
+ default:
+ mAboveAppWindowsContainers.removeChild(token);
+ break;
+ }
}
return token;
}
@@ -535,9 +570,72 @@
out.set(mContentRect);
}
- /** Refer to {@link WindowManagerService#attachStack(int, int, boolean)} */
- void attachStack(TaskStack stack, boolean onTop) {
- mTaskStackContainers.attachStack(stack, onTop);
+ /**
+ * Adds the stack to this display.
+ * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+ */
+ Rect addStackToDisplay(int stackId, boolean onTop) {
+ boolean attachedToDisplay = false;
+ TaskStack stack = mService.mStackIdToStack.get(stackId);
+ if (stack == null) {
+ if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+ + mDisplayId);
+
+ stack = getStackById(stackId);
+ if (stack != null) {
+ // It's already attached to the display...clear mDeferRemoval and move stack to
+ // appropriate z-order on display as needed.
+ stack.mDeferRemoval = false;
+ moveStack(stack, onTop);
+ attachedToDisplay = true;
+ } else {
+ stack = new TaskStack(mService, stackId);
+ }
+
+ mService.mStackIdToStack.put(stackId, stack);
+ if (stackId == DOCKED_STACK_ID) {
+ mDividerControllerLocked.notifyDockedStackExistsChanged(true);
+ }
+ } else {
+ final DisplayContent currentDC = stack.getDisplayContent();
+ if (currentDC != null) {
+ throw new IllegalStateException("Trying to add stackId=" + stackId
+ + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
+ + currentDC.getDisplayId());
+ }
+ }
+
+ if (!attachedToDisplay) {
+ mTaskStackContainers.addStackToDisplay(stack, onTop);
+ }
+
+ if (stack.getRawFullscreen()) {
+ return null;
+ }
+ final Rect bounds = new Rect();
+ stack.getRawBounds(bounds);
+ return bounds;
+ }
+
+ /** Removes the stack from the display and prepares for changing the parent. */
+ private void removeStackFromDisplay(TaskStack stack) {
+ mTaskStackContainers.removeStackFromDisplay(stack);
+ }
+
+ /** Moves the stack to this display and returns the updated bounds. */
+ Rect moveStackToDisplay(TaskStack stack) {
+ final DisplayContent currentDisplayContent = stack.getDisplayContent();
+ if (currentDisplayContent == null) {
+ throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
+ + " which is not currently attached to any display");
+ }
+ if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+ throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
+ + " to its current displayId=" + mDisplayId);
+ }
+
+ currentDisplayContent.removeStackFromDisplay(stack);
+ return addStackToDisplay(stack.mStackId, true /* onTop */);
}
void moveStack(TaskStack stack, boolean toTop) {
@@ -2394,7 +2492,11 @@
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
pendingLayoutChanges);
- if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+ // TODO(multi-display): For now adjusting wallpaper only on primary display to avoid
+ // the wallpaper window jumping across displays.
+ // Remove check for default display when there will be support for multiple wallpaper
+ // targets (on different displays).
+ if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
adjustWallpaperWindows();
}
@@ -3076,9 +3178,13 @@
* Window container class that contains all containers on this display relating to Apps.
* I.e Activities.
*/
- private class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+ private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
- void attachStack(TaskStack stack, boolean onTop) {
+ /**
+ * Adds the stack to this container.
+ * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+ */
+ void addStackToDisplay(TaskStack stack, boolean onTop) {
if (stack.mStackId == HOME_STACK_ID) {
if (mHomeStack != null) {
throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
@@ -3089,6 +3195,21 @@
stack.onDisplayChanged(DisplayContent.this);
}
+ /** Removes the stack from its container and prepare for changing the parent. */
+ void removeStackFromDisplay(TaskStack stack) {
+ removeChild(stack);
+ stack.onRemovedFromDisplay();
+ // TODO: remove when window list will be gone.
+ // Manually remove records from window list and tap excluded windows list.
+ for (int i = mWindows.size() - 1; i >= 0; --i) {
+ final WindowState windowState = mWindows.get(i);
+ if (stack == windowState.getStack()) {
+ mWindows.remove(i);
+ mTapExcludedWindows.remove(windowState);
+ }
+ }
+ }
+
void moveStack(TaskStack stack, boolean toTop) {
if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
// This stack is always-on-top silly...
@@ -3158,7 +3279,28 @@
* Window container class that contains all containers on this display that are not related to
* Apps. E.g. status bar.
*/
- private static class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+ private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+ /**
+ * Compares two child window tokens returns -1 if the first is lesser than the second in
+ * terms of z-order and 1 otherwise.
+ */
+ final Comparator<WindowToken> mWindowComparator = (token1, token2) ->
+ // Tokens with higher base layer are z-ordered on-top.
+ mService.mPolicy.windowTypeToLayerLw(token1.windowType)
+ < mService.mPolicy.windowTypeToLayerLw(token2.windowType) ? -1 : 1;
+ private final String mName;
+ NonAppWindowContainers(String name) {
+ mName = name;
+ }
+
+ void addChild(WindowToken token) {
+ addChild(token, mWindowComparator);
+ }
+
+ @Override
+ String getName() {
+ return mName;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6325cda..f038135 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -215,49 +215,6 @@
return dc;
}
- /** Adds the input stack id to the input display id and returns the bounds of the added stack.*/
- Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
- final DisplayContent dc = getDisplayContent(displayId);
- if (dc == null) {
- Slog.w(TAG_WM, "addStackToDisplay: Trying to add stackId=" + stackId
- + " to unknown displayId=" + displayId + " callers=" + Debug.getCallers(6));
- return null;
- }
-
- boolean attachedToDisplay = false;
- TaskStack stack = mService.mStackIdToStack.get(stackId);
- if (stack == null) {
- if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);
-
- stack = dc.getStackById(stackId);
- if (stack != null) {
- // It's already attached to the display...clear mDeferRemoval and move stack to
- // appropriate z-order on display as needed.
- stack.mDeferRemoval = false;
- dc.moveStack(stack, onTop);
- attachedToDisplay = true;
- } else {
- stack = new TaskStack(mService, stackId);
- }
-
- mService.mStackIdToStack.put(stackId, stack);
- if (stackId == DOCKED_STACK_ID) {
- dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
- }
- }
-
- if (!attachedToDisplay) {
- dc.attachStack(stack, onTop);
- }
-
- if (stack.getRawFullscreen()) {
- return null;
- }
- final Rect bounds = new Rect();
- stack.getRawBounds(bounds);
- return bounds;
- }
-
boolean isLayoutNeeded() {
final int numDisplays = mChildren.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 60ba025..a0270c6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -793,6 +793,14 @@
void removeImmediately() {
super.removeImmediately();
+ onRemovedFromDisplay();
+ }
+
+ /**
+ * Removes the stack it from its current parent, so it can be either destroyed completely or
+ * re-parented.
+ */
+ void onRemovedFromDisplay() {
mDisplayContent.mDimLayerController.removeDimLayerUser(this);
EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
@@ -800,15 +808,13 @@
mAnimationBackgroundSurface.destroySurface();
mAnimationBackgroundSurface = null;
}
- final DockedStackDividerController dividerController =
- mDisplayContent.mDividerControllerLocked;
- mDisplayContent = null;
-
- mService.mWindowPlacerLocked.requestTraversal();
if (mStackId == DOCKED_STACK_ID) {
- dividerController.notifyDockedStackExistsChanged(false);
+ mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
}
+
+ mDisplayContent = null;
+ mService.mWindowPlacerLocked.requestTraversal();
}
void resetAnimationBackgroundAnimator() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0abcd9f..7723752 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1219,7 +1219,7 @@
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
- token = new WindowToken(this, attrs.token, -1, false, displayContent);
+ token = new WindowToken(this, attrs.token, type, false, displayContent);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
if (atoken == null) {
@@ -1287,7 +1287,7 @@
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
- token = new WindowToken(this, null, -1, false, displayContent);
+ token = new WindowToken(this, null, type, false, displayContent);
}
WindowState win = new WindowState(this, session, client, token, parentWindow,
@@ -2721,11 +2721,11 @@
}
void setFocusTaskRegionLocked() {
- if (mFocusedApp != null) {
- final Task task = mFocusedApp.mTask;
- final DisplayContent displayContent = task.getDisplayContent();
+ final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
+ if (focusedTask != null) {
+ final DisplayContent displayContent = focusedTask.getDisplayContent();
if (displayContent != null) {
- displayContent.setTouchExcludeRegion(task);
+ displayContent.setTouchExcludeRegion(focusedTask);
}
}
}
@@ -3425,24 +3425,63 @@
}
/**
- * Create a new TaskStack and place it on a DisplayContent.
+ * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
+ * specified stackId.
* @param stackId The unique identifier of the new stack.
* @param displayId The unique identifier of the DisplayContent.
* @param onTop If true the stack will be place at the top of the display,
- * else at the bottom
- * @return The initial bounds the stack was created with. null means fullscreen.
+ * else at the bottom.
+ * @return The bounds that the stack has after adding. null means fullscreen.
*/
- public Rect attachStack(int stackId, int displayId, boolean onTop) {
+ public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
- return mRoot.addStackToDisplay(stackId, displayId, onTop);
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ throw new IllegalArgumentException("Trying to add stackId=" + stackId
+ + " to unknown displayId=" + displayId);
+ }
+
+ return dc.addStackToDisplay(stackId, onTop);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ /**
+ * Move a TaskStack from current DisplayContent to specified one.
+ * @param stackId The unique identifier of the new stack.
+ * @param displayId The unique identifier of the new display.
+ */
+ public Rect moveStackToDisplay(int stackId, int displayId) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mWindowMap) {
+ TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
+ + " to displayId=" + displayId);
+ }
+
+ final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
+ if (targetDisplayContent == null) {
+ throw new IllegalArgumentException("Trying to move stackId=" + stackId
+ + " to unknown displayId=" + displayId);
+ }
+
+ return targetDisplayContent.moveStackToDisplay(stack);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ /**
+ * Remove a TaskStack completely.
+ * @param stackId The unique identifier of the stack.
+ */
public void removeStack(int stackId) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 534a3d2..972c359 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1353,25 +1353,6 @@
}
/**
- * Like {@link #isVisibleLw}, but also counts a window that is currently "hidden" behind the
- * keyguard as visible. This allows us to apply things like window flags that impact the
- * keyguard. XXX I am starting to think we need to have ANOTHER visibility flag for this
- * "hidden behind keyguard" state rather than overloading mPolicyVisibility. Ungh.
- */
- @Override
- public boolean isVisibleOrBehindKeyguardLw() {
- if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
- return false;
- }
- final AppWindowToken atoken = mAppToken;
- final boolean animating = atoken != null && atoken.mAppAnimator.animation != null;
- return mHasSurface && !mDestroying && !mAnimatingExit
- && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
- && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
- || mWinAnimator.mAnimation != null || animating);
- }
-
- /**
* Is this window visible, ignoring its app token? It is not visible if there is no surface,
* or we are in the process of running an exit animation that will remove the surface.
*/
@@ -1418,14 +1399,6 @@
* being visible.
*/
boolean isOnScreen() {
- return mPolicyVisibility && isOnScreenIgnoringKeyguard();
- }
-
- /**
- * Like isOnScreen(), but ignores any force hiding of the window due
- * to the keyguard.
- */
- private boolean isOnScreenIgnoringKeyguard() {
if (!mHasSurface || mDestroying) {
return false;
}
@@ -1449,7 +1422,7 @@
boolean mightAffectAllDrawn(boolean visibleOnly) {
final boolean isViewVisible = (mAppToken == null || !mAppToken.clientHidden)
&& (mViewVisibility == View.VISIBLE) && !mWindowRemovalAllowed;
- return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible)
+ return (isOnScreen() && (!visibleOnly || isViewVisible)
|| mWinAnimator.mAttrType == TYPE_BASE_APPLICATION
|| mWinAnimator.mAttrType == TYPE_DRAWN_APPLICATION)
&& !mAnimatingExit && !mDestroying;
@@ -1479,28 +1452,6 @@
}
/**
- * Like isReadyForDisplay(), but ignores any force hiding of the window due
- * to the keyguard.
- */
- boolean isReadyForDisplayIgnoringKeyguard() {
- if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
- return false;
- }
- final AppWindowToken atoken = mAppToken;
- if (atoken == null && !mPolicyVisibility) {
- // If this is not an app window, and the policy has asked to force
- // hide, then we really do want to hide.
- return false;
- }
- return mHasSurface && !mDestroying
- && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
- || mWinAnimator.mAnimation != null
- || ((atoken != null) && (atoken.mAppAnimator.animation != null)
- && !mWinAnimator.isDummyAnimation())
- || isAnimatingWithSavedSurface());
- }
-
- /**
* Like isOnScreen, but returns false if the surface hasn't yet
* been drawn.
*/
@@ -3771,7 +3722,7 @@
logPerformShow("performShow on ");
- if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplayIgnoringKeyguard()) {
+ if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
return false;
}
@@ -3821,7 +3772,7 @@
|| (DEBUG_STARTING_WINDOW && mAttrs.type == TYPE_APPLICATION_STARTING)) {
Slog.v(TAG, prefix + this
+ ": mDrawState=" + mWinAnimator.drawStateToString()
- + " readyForDisplay=" + isReadyForDisplayIgnoringKeyguard()
+ + " readyForDisplay=" + isReadyForDisplay()
+ " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING)
+ " during animation: policyVis=" + mPolicyVisibility
+ " parentHidden=" + isParentWindowHidden()
diff --git a/services/tests/runtests.py b/services/tests/runtests.py
index 35fec90f..7980dc2 100755
--- a/services/tests/runtests.py
+++ b/services/tests/runtests.py
@@ -61,8 +61,8 @@
print 'Running tests...'
if len(sys.argv) != 1:
- run('adb shell am instrument -w "%s" %s' %
- (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:])))
+ run('adb shell am instrument -w %s "%s"' %
+ (' '.join(sys.argv[1:]), INSTRUMENTED_PACKAGE_RUNNER))
return 0
# It would be nice if the activity manager accepted a list of packages, but
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
new file mode 100644
index 0000000..cb5e8bb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2016 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.server.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MagnificationSpec;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerInternal.MagnificationCallbacks;
+
+import com.android.internal.R;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class MagnificationControllerTest {
+ static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 100, 200);
+ static final PointF INITIAL_MAGNIFICATION_BOUNDS_CENTER = new PointF(
+ INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY());
+ static final PointF INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER = new PointF(25, 50);
+ static final PointF INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(75, 150);
+ static final Rect OTHER_MAGNIFICATION_BOUNDS = new Rect(100, 200, 500, 600);
+ static final PointF OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(400, 500);
+ static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS);
+ static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
+ static final int SERVICE_ID_1 = 1;
+ static final int SERVICE_ID_2 = 2;
+
+ final Context mMockContext = mock(Context.class);
+ final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
+ final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+ final MessageCapturingHandler mMessageCapturingHandler =
+ new MessageCapturingHandler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ return mMagnificationController.handleMessage(msg);
+ }
+ });
+ final ArgumentCaptor<MagnificationSpec> mMagnificationSpecCaptor =
+ ArgumentCaptor.forClass(MagnificationSpec.class);
+ final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
+ MagnificationController.SettingsBridge mMockSettingsBridge;
+
+
+ MagnificationController mMagnificationController;
+ ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
+ Resources mockResources = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn(mockResources);
+ when(mockResources.getInteger(R.integer.config_longAnimTime))
+ .thenReturn(1000);
+ mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
+ mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
+ mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
+ mMockSettingsBridge);
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Region regionArg = (Region) args[0];
+ regionArg.set(INITIAL_MAGNIFICATION_REGION);
+ return null;
+ }
+ }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
+
+ ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+ verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+ mTargetAnimationListener = listenerArgumentCaptor.getValue();
+ Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ }
+
+ @Test
+ public void testRegister_WindowManagerAndContextRegisterListeners() {
+ mMagnificationController.register();
+ verify(mMockContext).registerReceiver(
+ (BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
+ assertTrue(mMagnificationController.isRegisteredLocked());
+ }
+
+ @Test
+ public void testRegister_WindowManagerAndContextUnregisterListeners() {
+ mMagnificationController.register();
+ mMagnificationController.unregister();
+
+ verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(null);
+ assertFalse(mMagnificationController.isRegisteredLocked());
+ }
+
+ @Test
+ public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
+ mMagnificationController.register();
+ MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
+ Region initialMagRegion = new Region();
+ Rect initialBounds = new Rect();
+
+ assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
+ mMagnificationController.getMagnificationRegion(initialMagRegion);
+ mMagnificationController.getMagnificationBounds(initialBounds);
+ assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
+ assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
+ assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
+ mMagnificationController.getCenterX(), 0.0f);
+ assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
+ mMagnificationController.getCenterY(), 0.0f);
+ }
+
+ @Test
+ public void testNotRegistered_publicMethodsShouldBeBenign() {
+ assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
+ assertFalse(mMagnificationController.reset(true));
+ assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
+ assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
+ assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
+ assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+
+ mMagnificationController.getMagnificationRegion(new Region());
+ mMagnificationController.getMagnificationBounds(new Rect());
+ mMagnificationController.getScale();
+ mMagnificationController.getOffsetX();
+ mMagnificationController.getOffsetY();
+ mMagnificationController.getCenterX();
+ mMagnificationController.getCenterY();
+ mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
+ mMagnificationController.unregister();
+ }
+
+ @Test
+ public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
+ mMagnificationController.register();
+ final float scale = 2.0f;
+ final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
+ assertTrue(mMagnificationController
+ .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+
+ final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+ assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
+ assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
+ assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockValueAnimator, times(0)).start();
+ }
+
+ @Test
+ public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
+ mMagnificationController.register();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ float scale = 2.0f;
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ assertTrue(mMagnificationController
+ .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+
+ // New center should be halfway between original center and pivot
+ PointF newCenter = new PointF((pivotPoint.x + INITIAL_MAGNIFICATION_BOUNDS.centerX()) / 2,
+ (pivotPoint.y + INITIAL_MAGNIFICATION_BOUNDS.centerY()) / 2);
+ PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
+
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+ assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+ verify(mMockValueAnimator).start();
+
+ // Initial value
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(startSpec);
+
+ // Intermediate point
+ Mockito.reset(mMockWindowManager);
+ float fraction = 0.5f;
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(
+ argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
+
+ // Final value
+ Mockito.reset(mMockWindowManager);
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ }
+
+ @Test
+ public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
+ mMagnificationController.register();
+ // First zoom in
+ float scale = 2.0f;
+ assertTrue(mMagnificationController.setScale(scale,
+ INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
+ false, SERVICE_ID_1));
+ Mockito.reset(mMockWindowManager);
+
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ assertTrue(mMagnificationController
+ .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
+
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockValueAnimator, times(0)).start();
+ }
+
+ @Test
+ public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
+ mMagnificationController.register();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ float scale = 2.5f;
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
+
+ assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
+ true, SERVICE_ID_1));
+
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+ assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+ verify(mMockAms).notifyMagnificationChanged(
+ INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
+ verify(mMockValueAnimator).start();
+
+ // Initial value
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(startSpec);
+
+ // Intermediate point
+ Mockito.reset(mMockWindowManager);
+ float fraction = 0.33f;
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(
+ argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
+
+ // Final value
+ Mockito.reset(mMockWindowManager);
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ }
+
+ @Test
+ public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
+ mMagnificationController.register();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
+ MagnificationController.MAX_SCALE);
+ MagnificationSpec endSpec = getMagnificationSpec(
+ MagnificationController.MAX_SCALE, offsets);
+
+ assertTrue(mMagnificationController.setScaleAndCenter(
+ MagnificationController.MAX_SCALE + 1.0f,
+ newCenter.x, newCenter.y, false, SERVICE_ID_1));
+
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ Mockito.reset(mMockWindowManager);
+
+ // Verify that we can't zoom below 1x
+ assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+ INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
+ false, SERVICE_ID_1));
+
+ assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
+ mMagnificationController.getCenterX(), 0.5);
+ assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
+ mMagnificationController.getCenterY(), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ }
+
+ @Test
+ public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
+ mMagnificationController.register();
+ float scale = 2.0f;
+
+ // Off the edge to the top and left
+ assertTrue(mMagnificationController.setScaleAndCenter(
+ scale, -100f, -200f, false, SERVICE_ID_1));
+
+ PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(
+ argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
+ Mockito.reset(mMockWindowManager);
+
+ // Off the edge to the bottom and right
+ assertTrue(mMagnificationController.setScaleAndCenter(scale,
+ INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
+ false, SERVICE_ID_1));
+ newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(
+ argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
+ }
+
+ @Test
+ public void testMagnificationRegionChanged_serviceNotified() {
+ mMagnificationController.register();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ callbacks.onMagnificationRegionChanged(OTHER_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+ OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
+ }
+
+ @Test
+ public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
+ mMagnificationController.register();
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
+ // First zoom in
+ assertTrue(mMagnificationController
+ .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+ Mockito.reset(mMockWindowManager);
+
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ mMagnificationController.offsetMagnifiedRegion(
+ startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+
+ MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockValueAnimator, times(0)).start();
+ }
+
+ @Test
+ public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
+ mMagnificationController.register();
+ Mockito.reset(mMockWindowManager);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
+ public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
+ mMagnificationController.register();
+ float scale = 2.0f;
+
+ // Upper left edges
+ PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+ assertTrue(mMagnificationController
+ .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+ Mockito.reset(mMockWindowManager);
+ MagnificationSpec ulSpec = getCurrentMagnificationSpec();
+ mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+
+ // Lower right edges
+ PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ assertTrue(mMagnificationController
+ .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+ Mockito.reset(mMockWindowManager);
+ MagnificationSpec lrSpec = getCurrentMagnificationSpec();
+ mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
+ public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
+ mMagnificationController.register();
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ assertTrue(mMagnificationController
+ .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+ assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+ assertTrue(mMagnificationController
+ .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
+ assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+ }
+
+ @Test
+ public void testSetUserId_resetsOnlyIfIdChanges() {
+ final int userId1 = 1;
+ final int userId2 = 2;
+
+ mMagnificationController.register();
+ mMagnificationController.setUserId(userId1);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+
+ mMagnificationController.setUserId(userId1);
+ assertTrue(mMagnificationController.isMagnifying());
+ mMagnificationController.setUserId(userId2);
+ assertFalse(mMagnificationController.isMagnifying());
+ }
+
+ @Test
+ public void testResetIfNeeded_doesWhatItSays() {
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ assertTrue(mMagnificationController.resetIfNeeded(false));
+ verify(mMockAms).notifyMagnificationChanged(
+ eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
+ assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.resetIfNeeded(false));
+ }
+
+ @Test
+ public void testTurnScreenOff_resetsMagnification() {
+ mMagnificationController.register();
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext).registerReceiver(
+ broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
+ BroadcastReceiver br = broadcastReceiverCaptor.getValue();
+ zoomIn2xToMiddle();
+ br.onReceive(mMockContext, null);
+ mMessageCapturingHandler.sendAllMessages();
+ assertFalse(mMagnificationController.isMagnifying());
+ }
+
+ @Test
+ public void testUserContextChange_resetsMagnification() {
+ mMagnificationController.register();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ zoomIn2xToMiddle();
+ callbacks.onUserContextChanged();
+ mMessageCapturingHandler.sendAllMessages();
+ assertFalse(mMagnificationController.isMagnifying());
+ }
+
+ @Test
+ public void testRotation_resetsMagnification() {
+ mMagnificationController.register();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ zoomIn2xToMiddle();
+ mMessageCapturingHandler.sendAllMessages();
+ assertTrue(mMagnificationController.isMagnifying());
+ callbacks.onRotationChanged(0);
+ mMessageCapturingHandler.sendAllMessages();
+ assertFalse(mMagnificationController.isMagnifying());
+ }
+
+ @Test
+ public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+ // Going from a small region to a large one leads to no issues
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ Mockito.reset(mMockWindowManager);
+ callbacks.onMagnificationRegionChanged(OTHER_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
+ public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
+ mMagnificationController.register();
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ Mockito.reset(mMockWindowManager);
+ callbacks.onMagnificationRegionChanged(OTHER_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
+ public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+ // In a large region, pan to the farthest point possible
+ mMagnificationController.register();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ callbacks.onMagnificationRegionChanged(OTHER_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ float scale = 2.0f;
+ mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ Mockito.reset(mMockWindowManager);
+
+ callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+
+ MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
+ PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
+ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
+ assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ }
+
+ @Test
+ public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
+ mMagnificationController.register();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ callbacks.onMagnificationRegionChanged(OTHER_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ float scale = 2.0f;
+ mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ when (mMockValueAnimator.isRunning()).thenReturn(true);
+
+ callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
+ mMessageCapturingHandler.sendAllMessages();
+ verify(mMockValueAnimator).cancel();
+
+ MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
+ PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
+ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
+ assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ }
+
+ @Test
+ public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ Mockito.reset(mMockWindowManager);
+ int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
+ int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
+ callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
+ mMessageCapturingHandler.sendAllMessages();
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
+ public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ Mockito.reset(mMockWindowManager);
+ callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
+ mMessageCapturingHandler.sendAllMessages();
+ MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
+ assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ }
+
+ @Test
+ public void testRequestRectOnScreen_garbageInput_doesNothing() {
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ Mockito.reset(mMockWindowManager);
+ callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
+ mMessageCapturingHandler.sendAllMessages();
+ assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+
+ @Test
+ public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+ Locale.setDefault(new Locale("en", "us"));
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ Mockito.reset(mMockWindowManager);
+ Rect wideRect = new Rect(0, 50, 100, 51);
+ callbacks.onRectangleOnScreenRequested(
+ wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
+ mMessageCapturingHandler.sendAllMessages();
+ MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
+ assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ Mockito.reset(mMockWindowManager);
+
+ // Repeat with RTL
+ Locale.setDefault(new Locale("he", "il"));
+ callbacks.onRectangleOnScreenRequested(
+ wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
+ mMessageCapturingHandler.sendAllMessages();
+ expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
+ assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ }
+
+ @Test
+ public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
+ mMagnificationController.register();
+ zoomIn2xToMiddle();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ Mockito.reset(mMockWindowManager);
+ Rect tallRect = new Rect(50, 0, 51, 100);
+ callbacks.onRectangleOnScreenRequested(
+ tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
+ mMessageCapturingHandler.sendAllMessages();
+ MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
+ assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ }
+
+ @Test
+ public void testChangeMagnification_duringAnimation_animatesToNewValue() {
+ mMagnificationController.register();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ float scale = 2.5f;
+ PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ MagnificationSpec firstEndSpec = getMagnificationSpec(
+ scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
+
+ assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
+ true, SERVICE_ID_1));
+
+ assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
+ assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
+ assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+ verify(mMockValueAnimator, times(1)).start();
+
+ // Initial value
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockAms).notifyMagnificationChanged(
+ INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
+ Mockito.reset(mMockWindowManager);
+
+ // Intermediate point
+ float fraction = 0.33f;
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ MagnificationSpec intermediateSpec1 =
+ getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ Mockito.reset(mMockWindowManager);
+
+ PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+ MagnificationSpec newEndSpec = getMagnificationSpec(
+ scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
+ assertTrue(mMagnificationController.setCenter(
+ newCenter.x, newCenter.y, true, SERVICE_ID_1));
+
+ // Animation should have been restarted
+ verify(mMockValueAnimator, times(2)).start();
+ verify(mMockAms).notifyMagnificationChanged(
+ INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
+
+ // New starting point should be where we left off
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ Mockito.reset(mMockWindowManager);
+
+ // Second intermediate point
+ fraction = 0.5f;
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(
+ argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
+ Mockito.reset(mMockWindowManager);
+
+ // Final value should be the new center
+ Mockito.reset(mMockWindowManager);
+ when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+ mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+ verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+ }
+
+ private void zoomIn2xToMiddle() {
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ assertTrue(mMagnificationController.isMagnifying());
+ }
+
+ private MagnificationCallbacks getMagnificationCallbacks() {
+ ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
+ ArgumentCaptor.forClass(MagnificationCallbacks.class);
+ verify(mMockWindowManager)
+ .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+ return magnificationCallbacksCaptor.getValue();
+ }
+
+ private PointF computeOffsets(Rect magnifiedBounds, PointF center, float scale) {
+ return new PointF(
+ magnifiedBounds.centerX() - scale * center.x,
+ magnifiedBounds.centerY() - scale * center.y);
+ }
+
+ private MagnificationSpec getInterpolatedMagSpec(MagnificationSpec start, MagnificationSpec end,
+ float fraction) {
+ MagnificationSpec interpolatedSpec = MagnificationSpec.obtain();
+ interpolatedSpec.scale = start.scale + fraction * (end.scale - start.scale);
+ interpolatedSpec.offsetX = start.offsetX + fraction * (end.offsetX - start.offsetX);
+ interpolatedSpec.offsetY = start.offsetY + fraction * (end.offsetY - start.offsetY);
+ return interpolatedSpec;
+ }
+
+ private MagnificationSpec getMagnificationSpec(float scale, PointF offsets) {
+ return getMagnificationSpec(scale, offsets.x, offsets.y);
+ }
+
+ private MagnificationSpec getMagnificationSpec(float scale, float offsetX, float offsetY) {
+ MagnificationSpec spec = MagnificationSpec.obtain();
+ spec.scale = scale;
+ spec.offsetX = offsetX;
+ spec.offsetY = offsetY;
+ return spec;
+ }
+
+ private MagnificationSpec getCurrentMagnificationSpec() {
+ return getMagnificationSpec(mMagnificationController.getScale(),
+ mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+ }
+
+ private MagSpecMatcher closeTo(MagnificationSpec spec) {
+ return new MagSpecMatcher(spec, 0.01f, 0.5f);
+ }
+
+ private class MagSpecMatcher extends TypeSafeMatcher<MagnificationSpec> {
+ final MagnificationSpec mMagSpec;
+ final float mScaleTolerance;
+ final float mOffsetTolerance;
+
+ MagSpecMatcher(MagnificationSpec spec, float scaleTolerance, float offsetTolerance) {
+ mMagSpec = spec;
+ mScaleTolerance = scaleTolerance;
+ mOffsetTolerance = offsetTolerance;
+ }
+
+ @Override
+ protected boolean matchesSafely(MagnificationSpec magnificationSpec) {
+ if (Math.abs(mMagSpec.scale - magnificationSpec.scale) > mScaleTolerance) {
+ return false;
+ }
+ if (Math.abs(mMagSpec.offsetX - magnificationSpec.offsetX) > mOffsetTolerance) {
+ return false;
+ }
+ if (Math.abs(mMagSpec.offsetY - magnificationSpec.offsetY) > mOffsetTolerance) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Match spec: " + mMagSpec);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
new file mode 100644
index 0000000..003f7ab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.server.accessibility;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to capture messages dispatched through a handler and control when they arrive
+ * at their target.
+ */
+public class MessageCapturingHandler extends Handler {
+ List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+
+ Handler.Callback mCallback;
+
+ public MessageCapturingHandler(Handler.Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message message, long uptimeMillis) {
+ timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
+ return super.sendMessageAtTime(message, uptimeMillis);
+ }
+
+ public void sendOneMessage() {
+ Message message = timedMessages.remove(0).first;
+ removeMessages(message.what, message.obj);
+ mCallback.handleMessage(message);
+ removeStaleMessages();
+ }
+
+ public void sendAllMessages() {
+ while (!timedMessages.isEmpty()) {
+ sendOneMessage();
+ }
+ }
+
+ public void sendLastMessage() {
+ Message message = timedMessages.remove(timedMessages.size() - 1).first;
+ removeMessages(message.what, message.obj);
+ mCallback.handleMessage(message);
+ removeStaleMessages();
+ }
+
+ public boolean hasMessages() {
+ removeStaleMessages();
+ return !timedMessages.isEmpty();
+ }
+
+ private void removeStaleMessages() {
+ for (int i = 0; i < timedMessages.size(); i++) {
+ Message message = timedMessages.get(i).first;
+ if (!hasMessages(message.what, message.obj)) {
+ timedMessages.remove(i--);
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 5920fef..d5305d9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -92,7 +92,12 @@
@Before
public void setUp() {
- mMessageCapturingHandler = new MessageCapturingHandler();
+ mMessageCapturingHandler = new MessageCapturingHandler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ return mMotionEventInjector.handleMessage(msg);
+ }
+ });
mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
mClickList.add(
MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
@@ -501,48 +506,4 @@
return false;
}
}
-
- private class MessageCapturingHandler extends Handler {
- List<Pair<Message, Long>> timedMessages = new ArrayList<>();
-
- @Override
- public boolean sendMessageAtTime(Message message, long uptimeMillis) {
- timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
- return super.sendMessageAtTime(message, uptimeMillis);
- }
-
- void sendOneMessage() {
- Message message = timedMessages.remove(0).first;
- removeMessages(message.what, message.obj);
- mMotionEventInjector.handleMessage(message);
- removeStaleMessages();
- }
-
- void sendAllMessages() {
- while (!timedMessages.isEmpty()) {
- sendOneMessage();
- }
- }
-
- void sendLastMessage() {
- Message message = timedMessages.remove(timedMessages.size() - 1).first;
- removeMessages(message.what, message.obj);
- mMotionEventInjector.handleMessage(message);
- removeStaleMessages();
- }
-
- boolean hasMessages() {
- removeStaleMessages();
- return !timedMessages.isEmpty();
- }
-
- private void removeStaleMessages() {
- for (int i = 0; i < timedMessages.size(); i++) {
- Message message = timedMessages.get(i).first;
- if (!hasMessages(message.what, message.obj)) {
- timedMessages.remove(i--);
- }
- }
- }
- }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7506b10..60a27bd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -498,6 +498,13 @@
"disable_severe_when_extreme_disabled_bool";
/**
+ * The message expiration time in milliseconds for duplicate detection purposes.
+ * @hide
+ */
+ public static final String KEY_MESSAGE_EXPIRATION_TIME_LONG =
+ "message_expiration_time_long";
+
+ /**
* The data call retry configuration for different types of APN.
* @hide
*/
@@ -1165,6 +1172,7 @@
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
+ sDefaults.putLong(KEY_MESSAGE_EXPIRATION_TIME_LONG, 86400000L);
sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
"default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ "320000:5000,640000:5000,1280000:5000,1800000:5000",