summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java11
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.cpp49
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.h11
-rw-r--r--cmds/hid/src/com/android/commands/hid/Device.java88
-rw-r--r--cmds/hid/src/com/android/commands/hid/Event.java30
-rw-r--r--cmds/hid/src/com/android/commands/hid/Hid.java4
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/ddm/OWNERS1
-rw-r--r--core/java/android/hardware/BatteryState.java6
-rw-r--r--core/java/android/hardware/biometrics/BiometricFingerprintConstants.java10
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java3
-rw-r--r--core/java/android/view/InputDevice.java6
-rw-r--r--core/java/android/view/View.java22
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java3
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/ddm/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java172
-rw-r--r--media/jni/audioeffect/Android.bp3
-rw-r--r--media/jni/audioeffect/Visualizer.cpp5
-rw-r--r--media/jni/audioeffect/Visualizer.h3
-rw-r--r--media/jni/audioeffect/android_media_AudioEffect.cpp10
-rw-r--r--media/jni/audioeffect/android_media_Visualizer.cpp6
-rw-r--r--packages/SystemUI/OWNERS1
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt110
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt31
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt423
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt77
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt6
-rw-r--r--packages/SystemUI/res/values/bools.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt68
-rw-r--r--services/core/java/com/android/server/am/UserController.java64
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java150
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java14
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java10
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/utils/Slogf.java46
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java16
-rw-r--r--services/core/java/com/android/server/wm/Session.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java43
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java13
80 files changed, 2000 insertions, 293 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 4ba69570f7e0..b79cc5e4bb60 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -55,12 +55,13 @@ public abstract class EconomicPolicy {
static final int TYPE_ACTION = 1 << SHIFT_TYPE;
static final int TYPE_REWARD = 2 << SHIFT_TYPE;
- private static final int SHIFT_POLICY = 29;
- static final int MASK_POLICY = 0b1 << SHIFT_POLICY;
- static final int POLICY_AM = 0 << SHIFT_POLICY;
- static final int POLICY_JS = 1 << SHIFT_POLICY;
+ private static final int SHIFT_POLICY = 28;
+ static final int MASK_POLICY = 0b11 << SHIFT_POLICY;
+ // Reserve 0 for the base/common policy.
+ static final int POLICY_AM = 1 << SHIFT_POLICY;
+ static final int POLICY_JS = 2 << SHIFT_POLICY;
- static final int MASK_EVENT = ~0 - (0b111 << SHIFT_POLICY);
+ static final int MASK_EVENT = -1 ^ (MASK_TYPE | MASK_POLICY);
static final int REGULATION_BASIC_INCOME = TYPE_REGULATION | 0;
static final int REGULATION_BIRTHRIGHT = TYPE_REGULATION | 1;
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 2cda57dd67e9..8b8d361edbd4 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -18,24 +18,22 @@
#include "com_android_commands_hid_Device.h"
-#include <linux/uhid.h>
-
+#include <android-base/stringprintf.h>
+#include <android/looper.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <unistd.h>
-#include <cstdio>
-#include <cstring>
-#include <memory>
-
-#include <android/looper.h>
#include <jni.h>
+#include <linux/uhid.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <unistd.h>
-#include <android-base/stringprintf.h>
+#include <cstdio>
+#include <cstring>
+#include <memory>
// Log debug messages about the output.
static constexpr bool DEBUG_OUTPUT = false;
@@ -109,15 +107,15 @@ void DeviceCallback::onDeviceOpen() {
void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) {
JNIEnv* env = getJNIEnv();
- env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport,
- requestId, reportId);
+ env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport, requestId,
+ reportId);
checkAndClearException(env, "onDeviceGetReport");
}
-void DeviceCallback::onDeviceSetReport(uint8_t rType,
- const std::vector<uint8_t>& data) {
+void DeviceCallback::onDeviceSetReport(uint32_t id, uint8_t rType,
+ const std::vector<uint8_t>& data) {
JNIEnv* env = getJNIEnv();
- env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceSetReport, rType,
+ env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceSetReport, id, rType,
toJbyteArray(env, data).get());
checkAndClearException(env, "onDeviceSetReport");
}
@@ -236,6 +234,14 @@ void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>&
writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY");
}
+void Device::sendSetReportReply(uint32_t id, bool success) const {
+ struct uhid_event ev = {};
+ ev.type = UHID_SET_REPORT_REPLY;
+ ev.u.set_report_reply.id = id;
+ ev.u.set_report_reply.err = success ? 0 : EIO;
+ writeEvent(mFd, ev, "UHID_SET_REPORT_REPLY");
+}
+
int Device::handleEvents(int events) {
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
ALOGE("uhid node was closed or an error occurred. events=0x%x", events);
@@ -249,7 +255,6 @@ int Device::handleEvents(int events) {
mDeviceCallback->onDeviceError();
return 0;
}
-
switch (ev.type) {
case UHID_OPEN: {
mDeviceCallback->onDeviceOpen();
@@ -271,7 +276,7 @@ int Device::handleEvents(int events) {
ALOGD("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
set_report.rnum, toString(data).c_str());
}
- mDeviceCallback->onDeviceSetReport(set_report.rtype, data);
+ mDeviceCallback->onDeviceSetReport(set_report.id, set_report.rtype, data);
break;
}
case UHID_OUTPUT: {
@@ -347,6 +352,15 @@ static void sendGetFeatureReportReply(JNIEnv* env, jclass /* clazz */, jlong ptr
}
}
+static void sendSetReportReply(JNIEnv*, jclass /* clazz */, jlong ptr, jint id, jboolean success) {
+ uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
+ if (d) {
+ d->sendSetReportReply(id, success);
+ } else {
+ ALOGE("Could not send set report reply, Device* is null!");
+ }
+}
+
static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
if (d) {
@@ -362,6 +376,7 @@ static JNINativeMethod sMethods[] = {
{"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
{"nativeSendGetFeatureReportReply", "(JI[B)V",
reinterpret_cast<void*>(sendGetFeatureReportReply)},
+ {"nativeSendSetReportReply", "(JIZ)V", reinterpret_cast<void*>(sendSetReportReply)},
{"nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice)},
};
@@ -376,7 +391,7 @@ int register_com_android_commands_hid_Device(JNIEnv* env) {
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
uhid::gDeviceCallbackClassInfo.onDeviceSetReport =
- env->GetMethodID(clazz, "onDeviceSetReport", "(B[B)V");
+ env->GetMethodID(clazz, "onDeviceSetReport", "(IB[B)V");
uhid::gDeviceCallbackClassInfo.onDeviceOutput =
env->GetMethodID(clazz, "onDeviceOutput", "(B[B)V");
uhid::gDeviceCallbackClassInfo.onDeviceError =
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index d10a9aa3680c..9c6060d837e0 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-#include <memory>
-#include <vector>
-
+#include <android-base/unique_fd.h>
#include <jni.h>
-#include <android-base/unique_fd.h>
+#include <memory>
+#include <vector>
namespace android {
namespace uhid {
@@ -31,7 +30,7 @@ public:
void onDeviceOpen();
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
- void onDeviceSetReport(uint8_t rType, const std::vector<uint8_t>& data);
+ void onDeviceSetReport(uint32_t id, uint8_t rType, const std::vector<uint8_t>& data);
void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data);
void onDeviceError();
@@ -50,9 +49,9 @@ public:
~Device();
void sendReport(const std::vector<uint8_t>& report) const;
+ void sendSetReportReply(uint32_t id, bool success) const;
void sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const;
void close();
-
int handleEvents(int events);
private:
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 95b1e9a7b57f..0415037cfc9f 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -42,7 +42,8 @@ public class Device {
private static final int MSG_OPEN_DEVICE = 1;
private static final int MSG_SEND_REPORT = 2;
private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3;
- private static final int MSG_CLOSE_DEVICE = 4;
+ private static final int MSG_SEND_SET_REPORT_REPLY = 4;
+ private static final int MSG_CLOSE_DEVICE = 5;
// Sync with linux uhid_event_type::UHID_OUTPUT
private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
@@ -56,21 +57,45 @@ public class Device {
private final Map<ByteBuffer, byte[]> mOutputs;
private final OutputStream mOutputStream;
private long mTimeToSend;
-
private final Object mCond = new Object();
+ /**
+ * The report id of the report received in UHID_EVENT_TYPE_SET_REPORT.
+ * Used for SET_REPORT_REPLY.
+ * This field gets overridden each time SET_REPORT is received.
+ */
+ private int mResponseId;
static {
System.loadLibrary("hidcommand_jni");
}
- private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus,
- byte[] descriptor, DeviceCallback callback);
+ private static native long nativeOpenDevice(
+ String name,
+ int id,
+ int vid,
+ int pid,
+ int bus,
+ byte[] descriptor,
+ DeviceCallback callback);
+
private static native void nativeSendReport(long ptr, byte[] data);
+
private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data);
+
+ private static native void nativeSendSetReportReply(long ptr, int id, boolean success);
+
private static native void nativeCloseDevice(long ptr);
- public Device(int id, String name, int vid, int pid, int bus, byte[] descriptor,
- byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) {
+ public Device(
+ int id,
+ String name,
+ int vid,
+ int pid,
+ int bus,
+ byte[] descriptor,
+ byte[] report,
+ SparseArray<byte[]> featureReports,
+ Map<ByteBuffer, byte[]> outputs) {
mId = id;
mThread = new HandlerThread("HidDeviceHandler");
mThread.start();
@@ -100,6 +125,17 @@ public class Device {
mHandler.sendMessageAtTime(msg, mTimeToSend);
}
+ public void setGetReportResponse(byte[] report) {
+ mFeatureReports.put(report[0], report);
+ }
+
+ public void sendSetReportReply(boolean success) {
+ Message msg =
+ mHandler.obtainMessage(MSG_SEND_SET_REPORT_REPLY, mResponseId, success ? 1 : 0);
+
+ mHandler.sendMessageAtTime(msg, mTimeToSend);
+ }
+
public void addDelay(int delay) {
mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
}
@@ -111,7 +147,8 @@ public class Device {
synchronized (mCond) {
mCond.wait();
}
- } catch (InterruptedException ignore) {}
+ } catch (InterruptedException ignore) {
+ }
}
private class DeviceHandler extends Handler {
@@ -127,8 +164,15 @@ public class Device {
switch (msg.what) {
case MSG_OPEN_DEVICE:
SomeArgs args = (SomeArgs) msg.obj;
- mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3,
- args.argi4, (byte[]) args.arg2, new DeviceCallback());
+ mPtr =
+ nativeOpenDevice(
+ (String) args.arg1,
+ args.argi1,
+ args.argi2,
+ args.argi3,
+ args.argi4,
+ (byte[]) args.arg2,
+ new DeviceCallback());
pauseEvents();
break;
case MSG_SEND_REPORT:
@@ -145,6 +189,14 @@ public class Device {
Log.e(TAG, "Tried to send feature report reply to closed device.");
}
break;
+ case MSG_SEND_SET_REPORT_REPLY:
+ if (mPtr != 0) {
+ final boolean success = msg.arg2 == 1;
+ nativeSendSetReportReply(mPtr, msg.arg1, success);
+ } else {
+ Log.e(TAG, "Tried to send set report reply to closed device.");
+ }
+ break;
case MSG_CLOSE_DEVICE:
if (mPtr != 0) {
nativeCloseDevice(mPtr);
@@ -173,14 +225,18 @@ public class Device {
}
private class DeviceCallback {
+
public void onDeviceOpen() {
mHandler.resumeEvents();
}
public void onDeviceGetReport(int requestId, int reportId) {
if (mFeatureReports == null) {
- Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId
- + ", but 'feature_reports' section is not found");
+ Log.e(
+ TAG,
+ "Received GET_REPORT request for reportId="
+ + reportId
+ + ", but 'feature_reports' section is not found");
return;
}
byte[] report = mFeatureReports.get(reportId);
@@ -220,14 +276,15 @@ public class Device {
} catch (IOException e) {
throw new RuntimeException(e);
}
-
}
// native callback
- public void onDeviceSetReport(byte rtype, byte[] data) {
+ public void onDeviceSetReport(int id, byte rType, byte[] data) {
+ // Used by sendSetReportReply()
+ mResponseId = id;
// We don't need to reply for the SET_REPORT but just send it to HID output for test
// verification.
- sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rtype, data);
+ sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rType, data);
}
// native callback
@@ -239,7 +296,8 @@ public class Device {
}
byte[] response = mOutputs.get(ByteBuffer.wrap(data));
if (response == null) {
- Log.i(TAG,
+ Log.i(
+ TAG,
"Requested response for output " + Arrays.toString(data) + " is not found");
return;
}
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index d4bf1d820a70..3efb79766b94 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -35,6 +35,8 @@ public class Event {
public static final String COMMAND_REGISTER = "register";
public static final String COMMAND_DELAY = "delay";
public static final String COMMAND_REPORT = "report";
+ public static final String COMMAND_SET_GET_REPORT_RESPONSE = "set_get_report_response";
+ public static final String COMMAND_SEND_SET_REPORT_REPLY = "send_set_report_reply";
// These constants come from "include/uapi/linux/input.h" in the kernel
enum Bus {
@@ -62,6 +64,7 @@ public class Event {
private SparseArray<byte[]> mFeatureReports;
private Map<ByteBuffer, byte[]> mOutputs;
private int mDuration;
+ private Boolean mReply;
public int getId() {
return mId;
@@ -107,6 +110,10 @@ public class Event {
return mDuration;
}
+ public Boolean getReply() {
+ return mReply;
+ }
+
public String toString() {
return "Event{id=" + mId
+ ", command=" + String.valueOf(mCommand)
@@ -119,6 +126,7 @@ public class Event {
+ ", feature_reports=" + mFeatureReports.toString()
+ ", outputs=" + mOutputs.toString()
+ ", duration=" + mDuration
+ + ", success=" + mReply.toString()
+ "}";
}
@@ -173,6 +181,10 @@ public class Event {
mEvent.mDuration = duration;
}
+ public void setReply(boolean success) {
+ mEvent.mReply = success;
+ }
+
public Event build() {
if (mEvent.mId == -1) {
throw new IllegalStateException("No event id");
@@ -183,6 +195,16 @@ public class Event {
if (mEvent.mDescriptor == null) {
throw new IllegalStateException("Device registration is missing descriptor");
}
+ }
+ if (COMMAND_SET_GET_REPORT_RESPONSE.equals(mEvent.mCommand)) {
+ if (mEvent.mReport == null) {
+ throw new IllegalStateException("Report command is missing response data");
+ }
+ }
+ if (COMMAND_SEND_SET_REPORT_REPLY.equals(mEvent.mCommand)) {
+ if (mEvent.mReply == null) {
+ throw new IllegalStateException("Reply command is missing reply");
+ }
} else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
if (mEvent.mDuration <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
@@ -246,6 +268,9 @@ public class Event {
case "duration":
eb.setDuration(readInt());
break;
+ case "success":
+ eb.setReply(readBool());
+ break;
default:
mReader.skipValue();
}
@@ -292,6 +317,11 @@ public class Event {
return Integer.decode(val);
}
+ private boolean readBool() throws IOException {
+ String val = mReader.nextString();
+ return Boolean.parseBoolean(val);
+ }
+
private Bus readBus() throws IOException {
String val = mReader.nextString();
return Bus.valueOf(val.toUpperCase());
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index fac0ab2ef125..2db791fe90bd 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -93,6 +93,10 @@ public class Hid {
d.addDelay(e.getDuration());
} else if (Event.COMMAND_REPORT.equals(e.getCommand())) {
d.sendReport(e.getReport());
+ } else if (Event.COMMAND_SET_GET_REPORT_RESPONSE.equals(e.getCommand())) {
+ d.setGetReportResponse(e.getReport());
+ } else if (Event.COMMAND_SEND_SET_REPORT_REPLY.equals(e.getCommand())) {
+ d.sendSetReportReply(e.getReply());
} else {
if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
error("Device id=" + e.getId() + " is already registered. Ignoring event.");
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ee5d2b7ba327..cf5f10ba109e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7838,7 +7838,7 @@ public final class ActivityThread extends ClientTransactionHandler
Files.move(new File(oldPath).toPath(), new File(newPath).toPath(),
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e2) {
- Log.e(TAG, "Rename recovery failed ", e);
+ Log.e(TAG, "Rename recovery failed ", e2);
throw e;
}
} else {
diff --git a/core/java/android/ddm/OWNERS b/core/java/android/ddm/OWNERS
new file mode 100644
index 000000000000..369025bd21f4
--- /dev/null
+++ b/core/java/android/ddm/OWNERS
@@ -0,0 +1 @@
+per-file DdmHandleViewDebug.java = michschn@google.com
diff --git a/core/java/android/hardware/BatteryState.java b/core/java/android/hardware/BatteryState.java
index aa7535982452..956fefca1f41 100644
--- a/core/java/android/hardware/BatteryState.java
+++ b/core/java/android/hardware/BatteryState.java
@@ -62,7 +62,8 @@ public abstract class BatteryState {
*
* @return the battery status.
*/
- public abstract @BatteryStatus int getStatus();
+ @BatteryStatus
+ public abstract int getStatus();
/**
* Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity
@@ -70,5 +71,6 @@ public abstract class BatteryState {
*
* @return the battery capacity.
*/
- public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity();
+ @FloatRange(from = -1.0f, to = 1.0f)
+ public abstract float getCapacity();
}
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 98f571b80949..c59d7571ee6d 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -217,7 +217,8 @@ public interface BiometricFingerprintConstants {
FINGERPRINT_ACQUIRED_START,
FINGERPRINT_ACQUIRED_UNKNOWN,
FINGERPRINT_ACQUIRED_IMMOBILE,
- FINGERPRINT_ACQUIRED_TOO_BRIGHT})
+ FINGERPRINT_ACQUIRED_TOO_BRIGHT,
+ FINGERPRINT_ACQUIRED_POWER_PRESSED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintAcquired {}
@@ -302,6 +303,13 @@ public interface BiometricFingerprintConstants {
int FINGERPRINT_ACQUIRED_TOO_BRIGHT = 10;
/**
+ * For sensors that have the power button co-located with their sensor, this event will
+ * be sent during enrollment.
+ * @hide
+ */
+ int FINGERPRINT_ACQUIRED_POWER_PRESSED = 11;
+
+ /**
* @hide
*/
int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 72d812269374..0fd164de8ffb 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1538,6 +1538,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case FINGERPRINT_ACQUIRED_TOO_BRIGHT:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_too_bright);
+ case FINGERPRINT_ACQUIRED_POWER_PRESSED:
+ return context.getString(
+ com.android.internal.R.string.fingerprint_acquired_power_press);
case FINGERPRINT_ACQUIRED_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_acquired_vendor);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 38911e07eb64..42c37fd97213 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1072,7 +1072,8 @@ public final class InputDevice implements Parcelable {
*
* @return The lights manager associated with the device, never null.
*/
- public @NonNull LightsManager getLightsManager() {
+ @NonNull
+ public LightsManager getLightsManager() {
if (mLightsManager == null) {
mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
}
@@ -1090,7 +1091,8 @@ public final class InputDevice implements Parcelable {
*
* @return The sensor manager service associated with the device, never null.
*/
- public @NonNull SensorManager getSensorManager() {
+ @NonNull
+ public SensorManager getSensorManager() {
synchronized (mMotionRanges) {
if (mSensorManager == null) {
mSensorManager = InputManager.getInstance().getInputDeviceSensorManager(mId);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8fee4db458b3..94ea2060a770 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10995,8 +10995,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* description. An image of a floppy disk that is used to save a file may
* use "Save".
*
+ * <p>
+ * This should omit role or state. Role refers to the kind of user-interface element the View
+ * is, such as a Button or Checkbox. State refers to a frequently changing property of the View,
+ * such as an On/Off state of a button or the audio level of a volume slider.
+ *
+ * <p>
+ * Content description updates are not frequent, and are used when the semantic content - not
+ * the state - of the element changes. For example, a Play button might change to a Pause
+ * button during music playback.
+ *
* @param contentDescription The content description.
* @see #getContentDescription()
+ * @see #setStateDescription(CharSequence)} for state changes.
* @attr ref android.R.styleable#View_contentDescription
*/
@RemotableViewMethod
@@ -13897,6 +13908,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
+ * <p>
+ * <b>Note:</b> Avoid setting accessibility focus. This is intended to be controlled by screen
+ * readers. Apps changing focus can confuse screen readers, so the resulting behavior can vary
+ * by device and screen reader version.
+ *
* @return Whether this view actually took accessibility focus.
*
* @hide
@@ -14735,6 +14751,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if
* {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.</p>
*
+ * <p>
+ * <b>Note:</b> Avoid setting accessibility focus with
+ * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}. This is intended to be controlled
+ * by screen readers. Apps changing focus can confuse screen readers, so the resulting behavior
+ * can vary by device and screen reader version.
+ *
* @param action The action to perform.
* @param arguments Optional action arguments.
* @return Whether the action was performed.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1f33806e8dd7..d07a7977b2ab 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -4829,6 +4829,9 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Action that gives accessibility focus to the node.
+ * <p>
+ * This is intended to be used by screen readers. Apps changing focus can confuse screen
+ * readers, so the resulting behavior can vary by device and screen reader version.
*/
public static final AccessibilityAction ACTION_ACCESSIBILITY_FOCUS =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 63eb83eb8db0..9214f438826f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1701,6 +1701,8 @@
<string name="fingerprint_acquired_already_enrolled">Try another fingerprint</string>
<!-- Message shown during fingerprint acquisition when fingerprint sensor detected too much light.[CHAR LIMIT=50] -->
<string name="fingerprint_acquired_too_bright">Too bright</string>
+ <!-- Message shown during fingerprint acquisition when a Power press has been detected.[CHAR LIMIT=50] -->
+ <string name="fingerprint_acquired_power_press">Power press detected</string>
<!-- Message shown during fingerprint acquisition when a fingerprint must be adjusted.[CHAR LIMIT=50] -->
<string name="fingerprint_acquired_try_adjusting">Try adjusting</string>
<!-- Message shown during fingerprint acquisition when a fingeprint area has already been captured during enrollment [CHAR LIMIT=100] -->
@@ -3574,11 +3576,11 @@
<!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_title">Tap to turn off screen</string>
+ <string name="fp_power_button_enrollment_title">To end setup, turn off screen</string>
<!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
power button is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_button_text">Turn off screen</string>
+ <string name="fp_power_button_enrollment_button_text">Turn off</string>
<!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
is pressed during biometric prompt when a side fingerprint sensor is present. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 66bfbb61ff15..a535c5097868 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2611,6 +2611,7 @@
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
<java-symbol type="string" name="fingerprint_acquired_too_slow" />
<java-symbol type="string" name="fingerprint_acquired_too_fast" />
+ <java-symbol type="string" name="fingerprint_acquired_power_press" />
<java-symbol type="string" name="fingerprint_acquired_too_bright" />
<java-symbol type="array" name="fingerprint_acquired_vendor" />
<java-symbol type="string" name="fingerprint_error_canceled" />
diff --git a/core/tests/coretests/src/android/ddm/OWNERS b/core/tests/coretests/src/android/ddm/OWNERS
new file mode 100644
index 000000000000..c8be1919fb93
--- /dev/null
+++ b/core/tests/coretests/src/android/ddm/OWNERS
@@ -0,0 +1 @@
+michschn@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
new file mode 100644
index 000000000000..acc0caf95e35
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.phone;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Rect;
+
+import com.android.wm.shell.pip.PipBoundsState;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Static utilities to get appropriate {@link PipDoubleTapHelper.PipSizeSpec} on a double tap.
+ */
+public class PipDoubleTapHelper {
+
+ /**
+ * Should not be instantiated as a stateless class.
+ */
+ private PipDoubleTapHelper() {}
+
+ /**
+ * A constant that represents a pip screen size.
+ *
+ * <p>CUSTOM - user resized screen size (by pinching in/out)</p>
+ * <p>DEFAULT - normal screen size used as default when entering pip mode</p>
+ * <p>MAX - maximum allowed screen size</p>
+ */
+ @IntDef(value = {
+ SIZE_SPEC_CUSTOM,
+ SIZE_SPEC_DEFAULT,
+ SIZE_SPEC_MAX
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PipSizeSpec {}
+
+ static final int SIZE_SPEC_CUSTOM = 2;
+ static final int SIZE_SPEC_DEFAULT = 0;
+ static final int SIZE_SPEC_MAX = 1;
+
+ /**
+ * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
+ *
+ * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
+ * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
+ * the latter two sizes is determined based on the current state of the pip screen.</p>
+ *
+ * @param mPipBoundsState current state of the pip screen
+ */
+ @PipSizeSpec
+ private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
+ // determine the average pip screen width
+ int averageWidth = (mPipBoundsState.getMaxSize().x
+ + mPipBoundsState.getMinSize().x) / 2;
+
+ // If pip screen width is above average, DEFAULT is the size spec we need to
+ // toggle to. Otherwise, we choose MAX.
+ return (mPipBoundsState.getBounds().width() > averageWidth)
+ ? SIZE_SPEC_DEFAULT
+ : SIZE_SPEC_MAX;
+ }
+
+ /**
+ * Determines the {@link PipSizeSpec} to toggle to on double tap.
+ *
+ * @param mPipBoundsState current state of the pip screen
+ * @param userResizeBounds latest user resized bounds (by pinching in/out)
+ * @return pip screen size to switch to
+ */
+ @PipSizeSpec
+ static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ @NonNull Rect userResizeBounds) {
+ // is pip screen at its maximum
+ boolean isScreenMax = mPipBoundsState.getBounds().width()
+ == mPipBoundsState.getMaxSize().x;
+
+ // is pip screen at its normal default size
+ boolean isScreenDefault = (mPipBoundsState.getBounds().width()
+ == mPipBoundsState.getNormalBounds().width())
+ && (mPipBoundsState.getBounds().height()
+ == mPipBoundsState.getNormalBounds().height());
+
+ // edge case 1
+ // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
+ // or if user has resized exactly to DEFAULT, then we just want to maximize
+ if (isScreenDefault
+ && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
+ return SIZE_SPEC_MAX;
+ }
+
+ // edge case 2
+ // if user has maximized, then we want to toggle to DEFAULT
+ if (isScreenMax
+ && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
+ return SIZE_SPEC_DEFAULT;
+ }
+
+ // otherwise in general we want to toggle back to user's CUSTOM size
+ if (isScreenDefault || isScreenMax) {
+ return SIZE_SPEC_CUSTOM;
+ }
+
+ // if we are currently in user resized CUSTOM size state
+ // then we toggle either to MAX or DEFAULT depending on the current pip screen state
+ return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a2fa058e97b7..84d9217e6fb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -929,9 +929,18 @@ public class PipTouchHandler {
if (mMenuController.isMenuVisible()) {
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
- if (toExpand) {
+
+ // the size to toggle to after a double tap
+ int nextSize = PipDoubleTapHelper
+ .nextSizeSpec(mPipBoundsState, getUserResizeBounds());
+
+ // actually toggle to the size chosen
+ if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
animateToMaximizedState(null);
+ } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToNormalSize(null);
} else {
animateToUnexpandedState(getUserResizeBounds());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
new file mode 100644
index 000000000000..8ce3ca4bdc00
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.phone;
+
+import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipBoundsState;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit test against {@link PipDoubleTapHelper}.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipDoubleTapHelperTest extends ShellTestCase {
+ // represents the current pip window state and has information on current
+ // max, min, and normal sizes
+ @Mock private PipBoundsState mBoundStateMock;
+ // tied to boundsStateMock.getBounds() in setUp()
+ @Mock private Rect mBoundsMock;
+
+ // represents the most recent manually resized bounds
+ // i.e. dimensions from the most recent pinch in/out
+ @Mock private Rect mUserResizeBoundsMock;
+
+ // actual dimensions of the pip screen bounds
+ private static final int MAX_WIDTH = 100;
+ private static final int DEFAULT_WIDTH = 40;
+ private static final int MIN_WIDTH = 10;
+
+ private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2;
+
+ /**
+ * Initializes mocks and assigns values for different pip screen bounds.
+ */
+ @Before
+ public void setUp() {
+ // define pip bounds
+ when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20));
+ when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2));
+
+ Rect rectMock = mock(Rect.class);
+ when(rectMock.width()).thenReturn(DEFAULT_WIDTH);
+ when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock);
+
+ when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
+ when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock);
+ }
+
+ /**
+ * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
+ *
+ * <p>when the user resizes the screen to a larger than the average but not the maximum width,
+ * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT}
+ */
+ @Test
+ public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() {
+ // make the user resize width in between MAX and average
+ when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
+ // make current bounds same as resized bound since no double tap yet
+ when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to DEFAULT state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_DEFAULT);
+
+ // once we toggle to DEFAULT our screen size gets updated
+ // but not the user resize bounds
+ when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to CUSTOM state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_CUSTOM);
+ }
+
+ /**
+ * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
+ *
+ * <p>when the user resizes the screen to a smaller than the average but not the default width,
+ * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX}
+ */
+ @Test
+ public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() {
+ // make the user resize width in between MIN and average
+ when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
+ // make current bounds same as resized bound since no double tap yet
+ when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to MAX state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_MAX);
+
+ // once we toggle to MAX our screen size gets updated
+ // but not the user resize bounds
+ when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to CUSTOM state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_CUSTOM);
+ }
+
+ /**
+ * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
+ *
+ * <p>when the user resizes the screen to exactly the maximum width
+ * then we toggle to {@code PipSizeSpec.DEFAULT}
+ */
+ @Test
+ public void testNextScreenSize_resizedToMax_returnDefault() {
+ // the resized width is the same as MAX_WIDTH
+ when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH);
+ // the current bounds are also at MAX_WIDTH
+ when(mBoundsMock.width()).thenReturn(MAX_WIDTH);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to DEFAULT state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_DEFAULT);
+ }
+
+ /**
+ * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
+ *
+ * <p>when the user resizes the screen to exactly the default width
+ * then we toggle to {@code PipSizeSpec.MAX}
+ */
+ @Test
+ public void testNextScreenSize_resizedToDefault_returnMax() {
+ // the resized width is the same as DEFAULT_WIDTH
+ when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
+ // the current bounds are also at DEFAULT_WIDTH
+ when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
+
+ // then nextScreenSize() i.e. double tapping should
+ // toggle to MAX state
+ Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
+ SIZE_SPEC_MAX);
+ }
+}
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 2ddfacf3884a..8b5b726fd2db 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -28,7 +28,7 @@ cc_library_shared {
"libaudioclient",
"libaudioutils",
"libaudiofoundation",
- "libbinder"
+ "libbinder",
],
export_shared_lib_headers: [
@@ -42,6 +42,7 @@ cc_library_shared {
"-Werror",
"-Wunused",
"-Wunreachable-code",
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
// Workaround Clang LTO crash.
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index d0f1ec6064c1..09c45ea97e9d 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -142,7 +142,8 @@ status_t Visualizer::setCaptureCallBack(capture_cbk_t cbk, void* user, uint32_t
mCaptureRate = rate;
if (cbk != NULL) {
- mCaptureThread = new CaptureThread(this, rate, ((flags & CAPTURE_CALL_JAVA) != 0));
+ mCaptureThread = sp<CaptureThread>::make(
+ sp<Visualizer>::fromExisting(this), rate, ((flags & CAPTURE_CALL_JAVA) != 0));
}
ALOGV("setCaptureCallBack() rate: %d thread %p flags 0x%08x",
rate, mCaptureThread.get(), mCaptureFlags);
@@ -439,7 +440,7 @@ void Visualizer::controlStatusChanged(bool controlGranted) {
//-------------------------------------------------------------------------
-Visualizer::CaptureThread::CaptureThread(Visualizer* receiver, uint32_t captureRate,
+Visualizer::CaptureThread::CaptureThread(const sp<Visualizer>& receiver, uint32_t captureRate,
bool bCanCallJava)
: Thread(bCanCallJava), mReceiver(receiver)
{
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index 3d5d74a99d0e..b38c01f62cf1 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -157,7 +157,8 @@ private:
class CaptureThread : public Thread
{
public:
- CaptureThread(Visualizer* visualizer, uint32_t captureRate, bool bCanCallJava = false);
+ CaptureThread(const sp<Visualizer>& visualizer,
+ uint32_t captureRate, bool bCanCallJava = false);
private:
friend class Visualizer;
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 2fb85a7f4b92..63e48aa622d0 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -205,15 +205,15 @@ static sp<AudioEffect> getAudioEffect(JNIEnv* env, jobject thiz)
Mutex::Autolock l(sLock);
AudioEffect* const ae =
(AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect);
- return sp<AudioEffect>(ae);
+ return sp<AudioEffect>::fromExisting(ae);
}
static sp<AudioEffect> setAudioEffect(JNIEnv* env, jobject thiz,
const sp<AudioEffect>& ae)
{
Mutex::Autolock l(sLock);
- sp<AudioEffect> old =
- (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect);
+ sp<AudioEffect> old = sp<AudioEffect>::fromExisting(
+ (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect));
if (ae.get()) {
ae->incStrong((void*)setAudioEffect);
}
@@ -347,8 +347,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
// create the native AudioEffect object
parcel = parcelForJavaObject(env, jAttributionSource);
attributionSource.readFromParcel(parcel);
- lpAudioEffect = new AudioEffect(attributionSource);
- if (lpAudioEffect == 0) {
+ lpAudioEffect = sp<AudioEffect>::make(attributionSource);
+ if (lpAudioEffect == 0) { // FIXME: I don't think this is actually possible.
ALOGE("Error creating AudioEffect");
goto setup_failure;
}
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index fac32e02cccd..940712232641 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -251,15 +251,15 @@ static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz)
Mutex::Autolock l(sLock);
Visualizer* const v =
(Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer);
- return sp<Visualizer>(v);
+ return sp<Visualizer>::fromExisting(v);
}
static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz,
const sp<Visualizer>& v)
{
Mutex::Autolock l(sLock);
- sp<Visualizer> old =
- (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer);
+ sp<Visualizer> old = sp<Visualizer>::fromExisting(
+ (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer));
if (v.get()) {
v->incStrong((void*)setVisualizer);
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index e9cec703c679..3325eec0c451 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -7,6 +7,7 @@ dsandler@android.com
aaliomer@google.com
adamcohen@google.com
alexflo@google.com
+arteiro@google.com
asc@google.com
awickham@google.com
beverlyt@google.com
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt
new file mode 100644
index 000000000000..496f4b3460f7
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiButtons.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.compose
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ButtonColors
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+
+@Composable
+fun SysUiButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ content: @Composable RowScope.() -> Unit,
+) {
+ androidx.compose.material3.Button(
+ modifier = modifier.padding(vertical = 6.dp).height(36.dp),
+ colors = filledButtonColors(),
+ contentPadding = ButtonPaddings,
+ onClick = onClick,
+ enabled = enabled,
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun SysUiOutlinedButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ content: @Composable RowScope.() -> Unit,
+) {
+ androidx.compose.material3.OutlinedButton(
+ modifier = modifier.padding(vertical = 6.dp).height(36.dp),
+ enabled = enabled,
+ colors = outlineButtonColors(),
+ border = outlineButtonBorder(),
+ contentPadding = ButtonPaddings,
+ onClick = onClick,
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun SysUiTextButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ content: @Composable RowScope.() -> Unit,
+) {
+ androidx.compose.material3.TextButton(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ content = content,
+ )
+}
+
+private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
+
+@Composable
+private fun filledButtonColors(): ButtonColors {
+ val colors = LocalAndroidColorScheme.current
+ return ButtonDefaults.buttonColors(
+ containerColor = colors.colorAccentPrimary,
+ contentColor = colors.textColorOnAccent,
+ )
+}
+
+@Composable
+private fun outlineButtonColors(): ButtonColors {
+ val colors = LocalAndroidColorScheme.current
+ return ButtonDefaults.outlinedButtonColors(
+ contentColor = colors.textColorPrimary,
+ )
+}
+
+@Composable
+private fun outlineButtonBorder(): BorderStroke {
+ val colors = LocalAndroidColorScheme.current
+ return BorderStroke(
+ width = 1.dp,
+ color = colors.colorAccentPrimaryVariant,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
new file mode 100644
index 000000000000..e1f73e304b9e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.systemui.common.shared.model.Text
+
+/** Returns the loaded [String] or `null` if there isn't one. */
+@Composable
+fun Text.load(): String? {
+ return when (this) {
+ is Text.Loaded -> text
+ is Text.Resource -> stringResource(res)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
new file mode 100644
index 000000000000..3175dcfa092b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.compose
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.compose.SysUiOutlinedButton
+import com.android.systemui.compose.SysUiTextButton
+import com.android.systemui.compose.features.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.user.ui.viewmodel.UserActionViewModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import com.android.systemui.user.ui.viewmodel.UserViewModel
+import java.lang.Integer.min
+import kotlin.math.ceil
+
+@Composable
+fun UserSwitcherScreen(
+ viewModel: UserSwitcherViewModel,
+ onFinished: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
+ val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
+ val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
+ val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
+ val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
+ val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
+
+ UserSwitcherScreenStateless(
+ isFinishRequested = isFinishRequested,
+ users = users,
+ maxUserColumns = maxUserColumns,
+ menuActions = menuActions,
+ isOpenMenuButtonVisible = isOpenMenuButtonVisible,
+ isMenuVisible = isMenuVisible,
+ onMenuClosed = viewModel::onMenuClosed,
+ onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
+ onCancelButtonClicked = viewModel::onCancelButtonClicked,
+ onFinished = {
+ onFinished()
+ viewModel.onFinished()
+ },
+ modifier = modifier,
+ )
+}
+
+@Composable
+private fun UserSwitcherScreenStateless(
+ isFinishRequested: Boolean,
+ users: List<UserViewModel>,
+ maxUserColumns: Int,
+ menuActions: List<UserActionViewModel>,
+ isOpenMenuButtonVisible: Boolean,
+ isMenuVisible: Boolean,
+ onMenuClosed: () -> Unit,
+ onOpenMenuButtonClicked: () -> Unit,
+ onCancelButtonClicked: () -> Unit,
+ onFinished: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ LaunchedEffect(isFinishRequested) {
+ if (isFinishRequested) {
+ onFinished()
+ }
+ }
+
+ Box(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .padding(
+ horizontal = 60.dp,
+ vertical = 40.dp,
+ ),
+ ) {
+ UserGrid(
+ users = users,
+ maxUserColumns = maxUserColumns,
+ modifier = Modifier.align(Alignment.Center),
+ )
+
+ Buttons(
+ menuActions = menuActions,
+ isOpenMenuButtonVisible = isOpenMenuButtonVisible,
+ isMenuVisible = isMenuVisible,
+ onMenuClosed = onMenuClosed,
+ onOpenMenuButtonClicked = onOpenMenuButtonClicked,
+ onCancelButtonClicked = onCancelButtonClicked,
+ modifier = Modifier.align(Alignment.BottomEnd),
+ )
+ }
+}
+
+@Composable
+private fun UserGrid(
+ users: List<UserViewModel>,
+ maxUserColumns: Int,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(44.dp),
+ modifier = modifier,
+ ) {
+ val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
+ (0 until rowCount).forEach { rowIndex ->
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(64.dp),
+ modifier = modifier,
+ ) {
+ val fromIndex = rowIndex * maxUserColumns
+ val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
+ users.subList(fromIndex, toIndex).forEach { user ->
+ UserItem(
+ viewModel = user,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun UserItem(
+ viewModel: UserViewModel,
+) {
+ val onClicked = viewModel.onClicked
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier =
+ if (onClicked != null) {
+ Modifier.clickable { onClicked() }
+ } else {
+ Modifier
+ }
+ .alpha(viewModel.alpha),
+ ) {
+ Box {
+ UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
+
+ UserItemIcon(
+ image = viewModel.image,
+ isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
+ modifier = Modifier.align(Alignment.Center).size(222.dp)
+ )
+ }
+
+ // User name
+ val text = viewModel.name.load()
+ if (text != null) {
+ // We use the box to center-align the text vertically as that is not possible with Text
+ // alone.
+ Box(
+ modifier = Modifier.size(width = 222.dp, height = 48.dp),
+ ) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.titleLarge,
+ color = colorResource(com.android.internal.R.color.system_neutral1_50),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun UserItemBackground(
+ modifier: Modifier = Modifier,
+) {
+ Image(
+ painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
+ contentDescription = null,
+ modifier = modifier.clip(CircleShape),
+ )
+}
+
+@Composable
+private fun UserItemIcon(
+ image: Drawable,
+ isSelectionMarkerVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ Image(
+ bitmap = image.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ modifier =
+ if (isSelectionMarkerVisible) {
+ // Draws a ring
+ modifier.border(
+ width = 8.dp,
+ color = LocalAndroidColorScheme.current.colorAccentPrimary,
+ shape = CircleShape,
+ )
+ } else {
+ modifier
+ }
+ .padding(16.dp)
+ .clip(CircleShape)
+ )
+}
+
+@Composable
+private fun Buttons(
+ menuActions: List<UserActionViewModel>,
+ isOpenMenuButtonVisible: Boolean,
+ isMenuVisible: Boolean,
+ onMenuClosed: () -> Unit,
+ onOpenMenuButtonClicked: () -> Unit,
+ onCancelButtonClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier,
+ ) {
+ // Cancel button.
+ SysUiTextButton(
+ onClick = onCancelButtonClicked,
+ ) {
+ Text(stringResource(R.string.cancel))
+ }
+
+ // "Open menu" button.
+ if (isOpenMenuButtonVisible) {
+ Spacer(modifier = Modifier.width(8.dp))
+ // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
+ // and the menu itself in a Box.
+ Box {
+ SysUiOutlinedButton(
+ onClick = onOpenMenuButtonClicked,
+ ) {
+ Text(stringResource(R.string.add))
+ }
+ Menu(
+ viewModel = menuActions,
+ isMenuVisible = isMenuVisible,
+ onMenuClosed = onMenuClosed,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun Menu(
+ viewModel: List<UserActionViewModel>,
+ isMenuVisible: Boolean,
+ onMenuClosed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
+ DropdownMenu(
+ expanded = isMenuVisible,
+ onDismissRequest = onMenuClosed,
+ modifier =
+ modifier.background(
+ color = MaterialTheme.colorScheme.inverseOnSurface,
+ ),
+ ) {
+ viewModel.forEachIndexed { index, action ->
+ MenuItem(
+ viewModel = action,
+ onClicked = { action.onClicked() },
+ topPadding =
+ if (index == 0) {
+ 16.dp
+ } else {
+ 0.dp
+ },
+ bottomPadding =
+ if (index == viewModel.size - 1) {
+ 16.dp
+ } else {
+ 0.dp
+ },
+ modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
+ )
+ }
+ }
+}
+
+@Composable
+private fun MenuItem(
+ viewModel: UserActionViewModel,
+ onClicked: () -> Unit,
+ topPadding: Dp,
+ bottomPadding: Dp,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+ val density = LocalDensity.current
+
+ val icon =
+ remember(viewModel.iconResourceId) {
+ val drawable =
+ checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+ drawable
+ .toBitmap(
+ size = with(density) { 20.dp.toPx() }.toInt(),
+ tintColor = Color.White,
+ )
+ .asImageBitmap()
+ }
+
+ DropdownMenuItem(
+ text = {
+ Text(
+ text = stringResource(viewModel.textResourceId),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ },
+ onClick = onClicked,
+ leadingIcon = {
+ Spacer(modifier = Modifier.width(10.dp))
+ Image(
+ bitmap = icon,
+ contentDescription = null,
+ )
+ },
+ modifier =
+ modifier
+ .heightIn(
+ min = 56.dp,
+ )
+ .padding(
+ start = 18.dp,
+ end = 65.dp,
+ top = topPadding,
+ bottom = bottomPadding,
+ ),
+ )
+}
+
+/**
+ * Converts the [Drawable] to a [Bitmap].
+ *
+ * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
+ * the `Drawable` onto it. Use sparingly and with care.
+ */
+private fun Drawable.toBitmap(
+ size: Int? = null,
+ tintColor: Color? = null,
+): Bitmap {
+ val bitmap =
+ if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ } else {
+ Bitmap.createBitmap(
+ size ?: intrinsicWidth,
+ size ?: intrinsicHeight,
+ Bitmap.Config.ARGB_8888
+ )
+ }
+ val canvas = Canvas(bitmap)
+ setBounds(0, 0, canvas.width, canvas.height)
+ if (tintColor != null) {
+ setTint(tintColor.toArgb())
+ }
+ draw(canvas)
+ return bitmap
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt
new file mode 100644
index 000000000000..881a1def113a
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ButtonsScreen.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.
+ *
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.systemui.compose.gallery
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.compose.SysUiButton
+import com.android.systemui.compose.SysUiOutlinedButton
+import com.android.systemui.compose.SysUiTextButton
+
+@Composable
+fun ButtonsScreen(
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier,
+ ) {
+ SysUiButton(
+ onClick = {},
+ ) {
+ Text("SysUiButton")
+ }
+
+ SysUiButton(
+ onClick = {},
+ enabled = false,
+ ) {
+ Text("SysUiButton - disabled")
+ }
+
+ SysUiOutlinedButton(
+ onClick = {},
+ ) {
+ Text("SysUiOutlinedButton")
+ }
+
+ SysUiOutlinedButton(
+ onClick = {},
+ enabled = false,
+ ) {
+ Text("SysUiOutlinedButton - disabled")
+ }
+
+ SysUiTextButton(
+ onClick = {},
+ ) {
+ Text("SysUiTextButton")
+ }
+
+ SysUiTextButton(
+ onClick = {},
+ enabled = false,
+ ) {
+ Text("SysUiTextButton - disabled")
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
index bb98fb350a2e..2e6456bcc4e1 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -31,6 +31,7 @@ object GalleryAppScreens {
val Typography = ChildScreen("typography") { TypographyScreen() }
val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() }
val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
+ val Buttons = ChildScreen("buttons") { ButtonsScreen() }
val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
val PeopleEmpty =
@@ -63,6 +64,7 @@ object GalleryAppScreens {
"Material colors" to MaterialColors,
"Android colors" to AndroidColors,
"Example feature" to ExampleFeature,
+ "Buttons" to Buttons,
"People" to People,
)
)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
index 8bf982d360a1..9c7fbe8842bc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
@@ -3,7 +3,9 @@ package com.android.systemui.plugins.qs
interface QSContainerController {
fun setCustomizerAnimating(animating: Boolean)
- fun setCustomizerShowing(showing: Boolean)
+ fun setCustomizerShowing(showing: Boolean) = setCustomizerShowing(showing, 0L)
+
+ fun setCustomizerShowing(showing: Boolean, animationDuration: Long)
fun setDetailShowing(showing: Boolean)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 499e24e6ecbd..c67ac8d34aa6 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -18,4 +18,6 @@
<resources>
<!-- Whether to show the user switcher in quick settings when only a single user is present. -->
<bool name="qs_show_user_switcher_for_single_user">false</bool>
+ <!-- Whether to show a custom biometric prompt size-->
+ <bool name="use_custom_bp_size">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9cb4cc4c8e63..08b1bdc4e8c7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -945,6 +945,8 @@
<dimen name="biometric_dialog_medium_to_large_translation_offset">100dp</dimen>
<!-- Y translation for credential contents when animating in -->
<dimen name="biometric_dialog_credential_translation_offset">60dp</dimen>
+ <dimen name="biometric_dialog_width">240dp</dimen>
+ <dimen name="biometric_dialog_height">240dp</dimen>
<!-- Biometric Auth Credential values -->
<dimen name="biometric_auth_icon_size">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e94b1f8122a6..0ac71c462e21 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -169,6 +169,10 @@ public abstract class AuthBiometricView extends LinearLayout {
private Animator.AnimatorListener mJankListener;
+ private final boolean mUseCustomBpSize;
+ private final int mCustomBpWidth;
+ private final int mCustomBpHeight;
+
private final OnClickListener mBackgroundClickListener = (view) -> {
if (mState == STATE_AUTHENTICATED) {
Log.w(TAG, "Ignoring background click after authenticated");
@@ -209,6 +213,10 @@ public abstract class AuthBiometricView extends LinearLayout {
handleResetAfterHelp();
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
};
+
+ mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size);
+ mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width);
+ mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height);
}
/** Delay after authentication is confirmed, before the dialog should be animated away. */
@@ -834,14 +842,17 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int width = MeasureSpec.getSize(widthMeasureSpec);
- final int height = MeasureSpec.getSize(heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
- final int newWidth = Math.min(width, height);
+ if (mUseCustomBpSize) {
+ width = mCustomBpWidth;
+ height = mCustomBpHeight;
+ } else {
+ width = Math.min(width, height);
+ }
- // Use "newWidth" instead, so the landscape dialog width is the same as the portrait
- // width.
- mLayoutParams = onMeasureInternal(newWidth, height);
+ mLayoutParams = onMeasureInternal(width, height);
setMeasuredDimension(mLayoutParams.mMediumWidth, mLayoutParams.mMediumHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index b02efba93161..de11d567d858 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -39,8 +39,15 @@ public class QSDetailClipper {
mBackground = (TransitionDrawable) detail.getBackground();
}
- public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
- updateCircularClip(true /* animate */, x, y, in, listener);
+ /**
+ * @param x x position where animation should originate
+ * @param y y position where animation should originate
+ * @param in whether animating in or out
+ * @param listener Animation listener. Called whether or not {@code animate} is true.
+ * @return the duration of the circular animator
+ */
+ public long animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
+ return updateCircularClip(true /* animate */, x, y, in, listener);
}
/**
@@ -50,8 +57,9 @@ public class QSDetailClipper {
* @param y y position where animation should originate
* @param in whether animating in or out
* @param listener Animation listener. Called whether or not {@code animate} is true.
+ * @return the duration of the circular animator
*/
- public void updateCircularClip(boolean animate, int x, int y, boolean in,
+ public long updateCircularClip(boolean animate, int x, int y, boolean in,
AnimatorListener listener) {
if (mAnimator != null) {
mAnimator.cancel();
@@ -87,6 +95,7 @@ public class QSDetailClipper {
mAnimator.addListener(mGoneOnEnd);
}
mAnimator.start();
+ return mAnimator.getDuration();
}
private final Runnable mReverseBackground = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7155626a1aa1..448e1807f7af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -49,7 +49,6 @@ import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
/** View that represents the quick settings tile panel (when expanded/pulled down). **/
public class QSPanel extends LinearLayout implements Tunable {
@@ -291,7 +290,16 @@ public class QSPanel extends LinearLayout implements Tunable {
} else {
topOffset = tileHeightOffset;
}
- int top = Objects.requireNonNull(mChildrenLayoutTop.get(child));
+ // Animation can occur before the layout pass, meaning setSquishinessFraction() gets
+ // called before onLayout(). So, a child view could be null because it has not
+ // been added to mChildrenLayoutTop yet (which happens in onLayout()).
+ // We use a continue statement here to catch this NPE because, on the layout pass,
+ // this code will be called again from onLayout() with the populated children views.
+ Integer childLayoutTop = mChildrenLayoutTop.get(child);
+ if (childLayoutTop == null) {
+ continue;
+ }
+ int top = childLayoutTop;
child.setLeftTopRightBottom(child.getLeft(), top + topOffset,
child.getRight(), top + topOffset + child.getHeight());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 8ad011904d3d..cf10c7940871 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -125,9 +125,10 @@ public class QSCustomizer extends LinearLayout {
isShown = true;
mOpening = true;
setVisibility(View.VISIBLE);
- mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
+ long duration = mClipper.animateCircularClip(
+ mX, mY, true, new ExpandAnimatorListener(tileAdapter));
mQsContainerController.setCustomizerAnimating(true);
- mQsContainerController.setCustomizerShowing(true);
+ mQsContainerController.setCustomizerShowing(true, duration);
}
}
@@ -153,13 +154,14 @@ public class QSCustomizer extends LinearLayout {
// Make sure we're not opening (because we're closing). Nobody can think we are
// customizing after the next two lines.
mOpening = false;
+ long duration = 0;
if (animate) {
- mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
+ duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
} else {
setVisibility(View.GONE);
}
mQsContainerController.setCustomizerAnimating(animate);
- mQsContainerController.setCustomizerShowing(false);
+ mQsContainerController.setCustomizerShowing(false, duration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 6b32daff0ab1..fe40d4cbe23a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -30,6 +30,7 @@ import androidx.constraintlayout.motion.widget.MotionLayout
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -310,6 +311,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
updateVisibility()
}
+ fun startCustomizingAnimation(show: Boolean, duration: Long) {
+ header.animate()
+ .setDuration(duration)
+ .alpha(if (show) 0f else 1f)
+ .setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
+ .start()
+ }
+
private fun loadConstraints() {
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 156b123cee8d..3429b9d0e503 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -443,6 +443,8 @@ public final class NotificationPanelViewController extends PanelViewController {
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
+ /** Indicates drag starting height when swiping down or up on heads-up notifications */
+ private int mHeadsUpStartHeight;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mListenForHeadsUp;
private int mNavigationBarBottomHeight;
@@ -645,6 +647,8 @@ public final class NotificationPanelViewController extends PanelViewController {
/** The drag distance required to fully expand the split shade. */
private int mSplitShadeFullTransitionDistance;
+ /** The drag distance required to fully transition scrims. */
+ private int mSplitShadeScrimTransitionDistance;
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@@ -1052,6 +1056,8 @@ public final class NotificationPanelViewController extends PanelViewController {
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+ mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize(
+ R.dimen.split_shade_scrim_transition_distance);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1335,7 +1341,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mQsSizeChangeAnimator.addUpdateListener(animation -> {
requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
mQs.setHeightOverride(height);
});
@@ -2114,7 +2120,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
setQsExpandImmediate(true);
setShowShelfOnly(true);
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
// Normally, we start listening when the panel is expanded, but here we need to start
// earlier so the state is already up to date when dragging down.
@@ -2326,7 +2332,7 @@ public final class NotificationPanelViewController extends PanelViewController {
// Reset scroll position and apply that position to the expanded height.
float height = mQsExpansionHeight;
setQsExpansion(height);
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
// When expanding QS, let's authenticate the user if possible,
@@ -2342,7 +2348,7 @@ public final class NotificationPanelViewController extends PanelViewController {
if (changed) {
mQsExpanded = expanded;
updateQsState();
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
mFalsingCollector.setQsExpanded(expanded);
mCentralSurfaces.setQsExpanded(expanded);
mNotificationsQSContainerController.setQsExpanded(expanded);
@@ -3066,16 +3072,7 @@ public final class NotificationPanelViewController extends PanelViewController {
int maxHeight;
if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
|| mPulsing || mSplitShadeEnabled) {
- if (mSplitShadeEnabled && mBarState == SHADE) {
- // Max panel height is used to calculate the fraction of the shade expansion.
- // Traditionally the value is based on the number of notifications.
- // On split-shade, we want the required distance to be a specific and constant
- // value, to make sure the expansion motion has the expected speed.
- // We also only want this on non-lockscreen for now.
- maxHeight = mSplitShadeFullTransitionDistance;
- } else {
- maxHeight = calculatePanelHeightQsExpanded();
- }
+ maxHeight = calculatePanelHeightQsExpanded();
} else {
maxHeight = calculatePanelHeightShade();
}
@@ -3434,6 +3431,31 @@ public final class NotificationPanelViewController extends PanelViewController {
}
@Override
+ public int getMaxPanelTransitionDistance() {
+ // Traditionally the value is based on the number of notifications. On split-shade, we want
+ // the required distance to be a specific and constant value, to make sure the expansion
+ // motion has the expected speed. We also only want this on non-lockscreen for now.
+ if (mSplitShadeEnabled && mBarState == SHADE) {
+ boolean transitionFromHeadsUp =
+ mHeadsUpManager.isTrackingHeadsUp() || mExpandingFromHeadsUp;
+ // heads-up starting height is too close to mSplitShadeFullTransitionDistance and
+ // when dragging HUN transition is already 90% complete. It makes shade become
+ // immediately visible when starting to drag. We want to set distance so that
+ // nothing is immediately visible when dragging (important for HUN swipe up motion) -
+ // 0.4 expansion fraction is a good starting point.
+ if (transitionFromHeadsUp) {
+ double maxDistance = Math.max(mSplitShadeFullTransitionDistance,
+ mHeadsUpStartHeight * 2.5);
+ return (int) Math.min(getMaxPanelHeight(), maxDistance);
+ } else {
+ return mSplitShadeFullTransitionDistance;
+ }
+ } else {
+ return getMaxPanelHeight();
+ }
+ }
+
+ @Override
protected boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3632,10 +3654,35 @@ public final class NotificationPanelViewController extends PanelViewController {
}
/**
+ * Called when heads-up notification is being dragged up or down to indicate what's the starting
+ * height for shade motion
+ */
+ public void setHeadsUpDraggingStartingHeight(int startHeight) {
+ mHeadsUpStartHeight = startHeight;
+ float scrimMinFraction;
+ if (mSplitShadeEnabled) {
+ boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance;
+ // if HUN height is higher than 40% of predefined transition distance, it means HUN
+ // is too high for regular transition. In that case we need to calculate transition
+ // distance - here we take scrim transition distance as equal to shade transition
+ // distance. It doesn't result in perfect motion - usually scrim transition distance
+ // should be longer - but it's good enough for HUN case.
+ float transitionDistance =
+ highHun ? getMaxPanelTransitionDistance() : mSplitShadeFullTransitionDistance;
+ scrimMinFraction = mHeadsUpStartHeight / transitionDistance;
+ } else {
+ int transitionDistance = getMaxPanelHeight();
+ scrimMinFraction = transitionDistance > 0f
+ ? (float) mHeadsUpStartHeight / transitionDistance : 0f;
+ }
+ setPanelScrimMinFraction(scrimMinFraction);
+ }
+
+ /**
* Sets the minimum fraction for the panel expansion offset. This may be non-zero in certain
* cases, such as if there's a heads-up notification.
*/
- public void setPanelScrimMinFraction(float minFraction) {
+ private void setPanelScrimMinFraction(float minFraction) {
mMinFraction = minFraction;
mDepthController.setPanelPullDownMinFraction(mMinFraction);
mScrimController.setPanelScrimMinFraction(mMinFraction);
@@ -4368,7 +4415,7 @@ public final class NotificationPanelViewController extends PanelViewController {
if (mKeyguardShowing) {
updateMaxDisplayedNotifications(true);
}
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
}
@Override
@@ -4501,7 +4548,7 @@ public final class NotificationPanelViewController extends PanelViewController {
if (mQsExpanded && mQsFullyExpanded) {
mQsExpansionHeight = mQsMaxExpansionHeight;
requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
}
if (mAccessibilityManager.isEnabled()) {
mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
@@ -4770,7 +4817,7 @@ public final class NotificationPanelViewController extends PanelViewController {
if (mQsExpanded && mQsFullyExpanded) {
mQsExpansionHeight = mQsMaxExpansionHeight;
requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
// Size has changed, start an animation.
if (mQsMaxExpansionHeight != oldMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 2a467763951c..d6f0de83ecc1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -35,6 +35,7 @@ class NotificationsQSContainerController @Inject constructor(
view: NotificationsQuickSettingsContainer,
private val navigationModeController: NavigationModeController,
private val overviewProxyService: OverviewProxyService,
+ private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
private val featureFlags: FeatureFlags,
@Main private val delayableExecutor: DelayableExecutor
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
@@ -156,9 +157,12 @@ class NotificationsQSContainerController @Inject constructor(
}
}
- override fun setCustomizerShowing(showing: Boolean) {
- isQSCustomizing = showing
- updateBottomSpacing()
+ override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
+ if (showing != isQSCustomizing) {
+ isQSCustomizing = showing
+ largeScreenShadeHeaderController.startCustomizingAnimation(showing, animationDuration)
+ updateBottomSpacing()
+ }
}
override fun setDetailShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index a00769374938..c3f1e571ab87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -248,7 +248,7 @@ public abstract class PanelViewController {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
}
});
mAmbientState = ambientState;
@@ -730,7 +730,7 @@ public abstract class PanelViewController {
setExpandedHeightInternal(height);
}
- protected void requestPanelHeightUpdate() {
+ void updateExpandedHeightToMaxHeight() {
float currentMaxPanelHeight = getMaxPanelHeight();
if (isFullyCollapsed()) {
@@ -753,6 +753,13 @@ public abstract class PanelViewController {
setExpandedHeight(currentMaxPanelHeight);
}
+ /**
+ * Returns drag down distance after which panel should be fully expanded. Usually it's the
+ * same as max panel height but for large screen devices (especially split shade) we might
+ * want to return different value to shorten drag distance
+ */
+ public abstract int getMaxPanelTransitionDistance();
+
public void setExpandedHeightInternal(float h) {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
@@ -763,18 +770,15 @@ public abstract class PanelViewController {
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
mExpandLatencyTracking = false;
}
- float maxPanelHeight = getMaxPanelHeight();
+ float maxPanelHeight = getMaxPanelTransitionDistance();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
if (mTracking && !mInSplitShade) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
- mExpandedHeight = Math.min(h, maxPanelHeight);
- } else {
- mExpandedHeight = h;
}
-
+ mExpandedHeight = Math.min(h, maxPanelHeight);
// If we are closing the panel and we are almost there due to a slow decelerating
// interpolator, abort the animation.
if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
@@ -832,7 +836,7 @@ public abstract class PanelViewController {
protected abstract int getMaxPanelHeight();
public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelHeight() * frac);
+ setExpandedHeight(getMaxPanelTransitionDistance() * frac);
}
public float getExpandedHeight() {
@@ -1029,7 +1033,7 @@ public abstract class PanelViewController {
mHeightAnimator = animator;
if (animator == null && mPanelUpdateWhenAnimatorEnds) {
mPanelUpdateWhenAnimatorEnds = false;
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
}
}
@@ -1421,7 +1425,7 @@ public abstract class PanelViewController {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
- requestPanelHeightUpdate();
+ updateExpandedHeightToMaxHeight();
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
abortAnimations();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index afd57daca10b..618c8924cd21 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -14,6 +14,7 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelState
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.LargeScreenUtils
import java.io.PrintWriter
import javax.inject.Inject
@@ -28,6 +29,7 @@ constructor(
private val scrimController: ScrimController,
@Main private val resources: Resources,
private val statusBarStateController: SysuiStatusBarStateController,
+ private val headsUpManager: HeadsUpManager
) {
private var inSplitShade = false
@@ -84,7 +86,11 @@ constructor(
}
private fun canUseCustomFraction(panelState: Int?) =
- inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING
+ inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING &&
+ // in case of HUN we can't always use predefined distances to manage scrim
+ // transition because dragDownPxAmount can start from value bigger than
+ // splitShadeScrimTransitionDistance
+ !headsUpManager.isTrackingHeadsUp
private fun isScreenUnlocked() =
statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 6bfb0dad28f7..90d0b697337a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -115,9 +115,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
mInitialTouchY = y;
int startHeight = (int) (mPickedChild.getActualHeight()
+ mPickedChild.getTranslationY());
- float maxPanelHeight = mPanel.getMaxPanelHeight();
- mPanel.setPanelScrimMinFraction(maxPanelHeight > 0f
- ? (float) startHeight / maxPanelHeight : 0f);
+ mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight);
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
index a6160aaf7756..6b7c42e3884a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
@@ -114,8 +114,8 @@ class PanelExpansionStateManager @Inject constructor() {
"end state=${state.panelStateToString()} " +
"f=$fraction " +
"expanded=$expanded " +
- "tracking=$tracking" +
- "drawDownPxAmount=$dragDownPxAmount " +
+ "tracking=$tracking " +
+ "dragDownPxAmount=$dragDownPxAmount " +
"${if (fullyOpened) " fullyOpened" else ""} " +
if (fullyClosed) " fullyClosed" else ""
)
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 8a51cd6c7e94..d43f739f9e71 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -36,6 +36,8 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.constraintlayout.helper.widget.Flow
import androidx.lifecycle.ViewModelProvider
@@ -67,7 +69,7 @@ private const val USER_VIEW = "user_view"
/**
* Support a fullscreen user switcher
*/
-class UserSwitcherActivity @Inject constructor(
+open class UserSwitcherActivity @Inject constructor(
private val userSwitcherController: UserSwitcherController,
private val broadcastDispatcher: BroadcastDispatcher,
private val falsingCollector: FalsingCollector,
@@ -83,6 +85,7 @@ class UserSwitcherActivity @Inject constructor(
private var popupMenu: UserSwitcherPopupMenu? = null
private lateinit var addButton: View
private var addUserRecords = mutableListOf<UserRecord>()
+ private val onBackCallback = OnBackInvokedCallback { finish() }
private val userSwitchedCallback: UserTracker.Callback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
finish()
@@ -105,7 +108,11 @@ class UserSwitcherActivity @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ createActivity()
+ }
+ @VisibleForTesting
+ fun createActivity() {
setContentView(R.layout.user_switcher_fullscreen)
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -148,6 +155,9 @@ class UserSwitcherActivity @Inject constructor(
}
}
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackCallback)
+
userSwitcherController.init(parent)
initBroadcastReceiver()
@@ -278,7 +288,12 @@ class UserSwitcherActivity @Inject constructor(
if (isUsingModernArchitecture()) {
return
}
+ destroyActivity()
+ }
+ @VisibleForTesting
+ fun destroyActivity() {
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback)
broadcastDispatcher.unregisterReceiver(broadcastReceiver)
userTracker.removeCallback(userSwitchedCallback)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index b98be75a51c7..2db58be15665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -158,6 +158,13 @@ class QSPanelTest : SysuiTestCase() {
assertThat(qsPanel.paddingBottom).isEqualTo(padding)
}
+ @Test
+ fun testSetSquishinessFraction_noCrash() {
+ qsPanel.addView(qsPanel.mTileLayout as View, 0)
+ qsPanel.addView(FrameLayout(context))
+ qsPanel.setSquishinessFraction(0.5f)
+ }
+
private infix fun View.isLeftOf(other: View): Boolean {
val rect = Rect()
getBoundsOnScreen(rect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index e85ffb68de54..c4485389d646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.DisplayCutout
import android.view.View
+import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.widget.TextView
import androidx.constraintlayout.motion.widget.MotionLayout
@@ -30,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -64,6 +66,7 @@ import org.mockito.Answers
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyInt
@@ -614,6 +617,34 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
)
}
+ @Test
+ fun animateOutOnStartCustomizing() {
+ val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ val duration = 1000L
+ whenever(view.animate()).thenReturn(animator)
+
+ controller.startCustomizingAnimation(show = true, duration)
+
+ verify(animator).setDuration(duration)
+ verify(animator).alpha(0f)
+ verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
+ verify(animator).start()
+ }
+
+ @Test
+ fun animateInOnEndCustomizing() {
+ val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ val duration = 1000L
+ whenever(view.animate()).thenReturn(animator)
+
+ controller.startCustomizingAnimation(show = false, duration)
+
+ verify(animator).setDuration(duration)
+ verify(animator).alpha(1f)
+ verify(animator).setInterpolator(Interpolators.ALPHA_IN)
+ verify(animator).start()
+ }
+
private fun createWindowInsets(
topCutout: Rect? = Rect()
): WindowInsets {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 8511443705e4..5ecfc8eb3649 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -4,10 +4,12 @@ import android.app.StatusBarManager
import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.View
+import android.view.ViewPropertyAnimator
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -29,8 +31,10 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Answers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
@@ -198,4 +202,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
)
}
+
+ @Test
+ fun animateOutOnStartCustomizing() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ val duration = 1000L
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
+
+ verify(animator).setDuration(duration)
+ verify(animator).alpha(0f)
+ verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
+ verify(animator).start()
+ }
+
+ @Test
+ fun animateInOnEndCustomizing() {
+ val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+ val duration = 1000L
+ whenever(view.animate()).thenReturn(animator)
+
+ mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
+
+ verify(animator).setDuration(duration)
+ verify(animator).alpha(1f)
+ verify(animator).setInterpolator(Interpolators.ALPHA_IN)
+ verify(animator).start()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index f1c54ef8d6f7..e73071335c9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -183,6 +183,7 @@ import java.util.Optional;
@TestableLooper.RunWithLooper
public class NotificationPanelViewControllerTest extends SysuiTestCase {
+ private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
private static final int PANEL_WIDTH = 500; // Random value just for the test.
@@ -321,6 +322,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal))
.thenReturn(10);
+ when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance))
+ .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
when(mView.getContext()).thenReturn(getContext());
when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
@@ -666,8 +669,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testSetPanelScrimMinFraction() {
- mNotificationPanelViewController.setPanelScrimMinFraction(0.5f);
+ public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
+ mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
+ mNotificationPanelViewController.getMaxPanelHeight() / 2);
verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
}
@@ -1363,40 +1367,47 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void getMaxPanelHeight_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
- int splitShadeFullTransitionDistance = 123456;
+ public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
+ enableSplitShade(true);
+ mNotificationPanelViewController.expandWithQs();
+
+ int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+
+ assertThat(maxDistance).isEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
+ }
+
+ @Test
+ public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
enableSplitShade(true);
- setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
mNotificationPanelViewController.expandWithQs();
+ when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
+ mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
+ SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
- int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+ int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
- assertThat(maxPanelHeight).isEqualTo(splitShadeFullTransitionDistance);
+ assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
}
@Test
- public void getMaxPanelHeight_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
+ public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
mStatusBarStateController.setState(KEYGUARD);
- int splitShadeFullTransitionDistance = 123456;
enableSplitShade(true);
- setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
mNotificationPanelViewController.expandWithQs();
- int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+ int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
- assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance);
+ assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
}
@Test
- public void getMaxPanelHeight_expanding_notSplitShade_returnsNonSplitShadeValue() {
- int splitShadeFullTransitionDistance = 123456;
+ public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() {
enableSplitShade(false);
- setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
mNotificationPanelViewController.expandWithQs();
- int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+ int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
- assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance);
+ assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
}
@Test
@@ -1543,12 +1554,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mTouchHandler.onTouch(mView, ev);
}
- private void setSplitShadeFullTransitionDistance(int splitShadeFullTransitionDistance) {
- when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance))
- .thenReturn(splitShadeFullTransitionDistance);
- mNotificationPanelViewController.updateResources();
- }
-
private void setDozing(boolean dozing, boolean dozingAlwaysOn) {
when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
mNotificationPanelViewController.setDozing(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index 0c6a6a98052f..12ef036d89d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -20,6 +20,7 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,10 +34,10 @@ import org.mockito.Mockito.doNothing
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -63,6 +64,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Mock
private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
@Mock
+ private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
+ @Mock
private lateinit var featureFlags: FeatureFlags
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@@ -92,6 +95,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
notificationsQSContainer,
navigationModeController,
overviewProxyService,
+ largeScreenShadeHeaderController,
featureFlags,
delayableExecutor
)
@@ -371,8 +375,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
container.removeAllViews()
container.addView(newViewWithId(1))
container.addView(newViewWithId(View.NO_ID))
- val controller = NotificationsQSContainerController(container, navigationModeController,
- overviewProxyService, featureFlags, delayableExecutor)
+ val controller = NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ largeScreenShadeHeaderController,
+ featureFlags,
+ delayableExecutor
+ )
controller.updateConstraints()
assertThat(container.getChildAt(0).id).isEqualTo(1)
@@ -397,6 +407,21 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
}
+ @Test
+ fun testStartCustomizingWithDuration() {
+ controller.setCustomizerShowing(true, 100L)
+ verify(largeScreenShadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ controller.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(largeScreenShadeHeaderController)
+
+ controller.setCustomizerShowing(false, 100L)
+ verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
private fun disableSplitShade() {
setSplitShadeEnabled(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index baaa447d53a3..6be76a6ac969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -13,6 +13,7 @@ import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.HeadsUpManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,6 +29,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var headsUpManager: HeadsUpManager
private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -42,7 +44,8 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
dumpManager,
scrimController,
context.resources,
- statusBarStateController)
+ statusBarStateController,
+ headsUpManager)
controller.onPanelStateChanged(STATE_OPENING)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
index 439beaab6c7e..3968bb798bb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
@@ -16,11 +16,17 @@
package com.android.systemui.user
+import android.app.Application
import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
+import android.view.View
+import android.view.Window
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingCollector
@@ -29,12 +35,25 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -60,11 +79,22 @@ class UserSwitcherActivityTest : SysuiTestCase() {
private lateinit var flags: FeatureFlags
@Mock
private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory>
+ @Mock
+ private lateinit var onBackDispatcher: OnBackInvokedDispatcher
+ @Mock
+ private lateinit var decorView: View
+ @Mock
+ private lateinit var window: Window
+ @Mock
+ private lateinit var userSwitcherRootView: UserSwitcherRootView
+ @Captor
+ private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback>
+ var isFinished = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- activity = UserSwitcherActivity(
+ activity = spy(object : UserSwitcherActivity(
userSwitcherController,
broadcastDispatcher,
falsingCollector,
@@ -73,7 +103,21 @@ class UserSwitcherActivityTest : SysuiTestCase() {
userTracker,
flags,
viewModelFactoryLazy,
- )
+ ) {
+ override fun getOnBackInvokedDispatcher() = onBackDispatcher
+ override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock())
+ override fun finish() {
+ isFinished = true
+ }
+ })
+ `when`(activity.window).thenReturn(window)
+ `when`(window.decorView).thenReturn(decorView)
+ `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root))
+ .thenReturn(userSwitcherRootView)
+ `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java))
+ `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java))
+ `when`(activity.application).thenReturn(mock(Application::class.java))
+ doNothing().`when`(activity).setContentView(anyInt())
}
@Test
@@ -85,4 +129,24 @@ class UserSwitcherActivityTest : SysuiTestCase() {
assertThat(activity.getMaxColumns(7)).isEqualTo(4)
assertThat(activity.getMaxColumns(9)).isEqualTo(5)
}
+
+ @Test
+ fun onCreate_callbackRegistration() {
+ activity.createActivity()
+ verify(onBackDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
+
+ activity.destroyActivity()
+ verify(onBackDispatcher).unregisterOnBackInvokedCallback(any())
+ }
+
+ @Test
+ fun onBackInvokedCallback_finishesActivity() {
+ activity.createActivity()
+ verify(onBackDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture())
+
+ onBackInvokedCallback.value.onBackInvoked()
+ assertThat(isFinished).isTrue()
+ }
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 1954f76825cd..6775c9952ddd 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -376,6 +376,11 @@ class UserController implements Handler.Callback {
private boolean mDelayUserDataLocking;
/**
+ * Users are only allowed to be unlocked after boot complete.
+ */
+ private volatile boolean mAllowUserUnlocking;
+
+ /**
* Keep track of last active users for mDelayUserDataLocking.
* The latest stopped user is placed in front while the least recently stopped user in back.
*/
@@ -434,6 +439,11 @@ class UserController implements Handler.Callback {
mUserLru.add(UserHandle.USER_SYSTEM);
mLockPatternUtils = mInjector.getLockPatternUtils();
updateStartedUserArrayLU();
+
+ // TODO(b/232452368): currently mAllowUserUnlocking is only used on devices with HSUM
+ // (Headless System User Mode), but on master it will be used by all devices (and hence this
+ // initial assignment should be removed).
+ mAllowUserUnlocking = !UserManager.isHeadlessSystemUserMode();
}
void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
@@ -1801,6 +1811,22 @@ class UserController implements Handler.Callback {
private boolean unlockUserCleared(final @UserIdInt int userId, byte[] secret,
IProgressListener listener) {
+ // Delay user unlocking for headless system user mode until the system boot
+ // completes. When the system boot completes, the {@link #onBootCompleted()}
+ // method unlocks all started users for headless system user mode. This is done
+ // to prevent unlocking the users too early during the system boot up.
+ // Otherwise, emulated volumes are mounted too early during the system
+ // boot up. When vold is reset on boot complete, vold kills all apps/services
+ // (that use these emulated volumes) before unmounting the volumes(b/241929666).
+ // In the past, these killings have caused the system to become too unstable on
+ // some occasions.
+ // Any unlocks that get delayed by this will be done by onBootComplete() instead.
+ if (!mAllowUserUnlocking) {
+ Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId);
+ notifyFinished(userId, listener);
+ return false;
+ }
+
UserState uss;
if (!StorageManager.isUserKeyUnlocked(userId)) {
final UserInfo userInfo = getUserInfo(userId);
@@ -2397,7 +2423,44 @@ class UserController implements Handler.Callback {
}
}
+ @VisibleForTesting
+ void setAllowUserUnlocking(boolean allowed) {
+ mAllowUserUnlocking = allowed;
+ if (DEBUG_MU) {
+ // TODO(b/245335748): use Slogf.d instead
+ // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
+ android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception());
+ }
+ }
+
+ /**
+ * @deprecated TODO(b/232452368): this logic will be merged into sendBootCompleted
+ */
+ @Deprecated
+ private void onBootCompletedOnHeadlessSystemUserModeDevices() {
+ setAllowUserUnlocking(true);
+
+ // Get a copy of mStartedUsers to use outside of lock.
+ SparseArray<UserState> startedUsers;
+ synchronized (mLock) {
+ startedUsers = mStartedUsers.clone();
+ }
+ // USER_SYSTEM must be processed first. It will be first in the array, as its ID is lowest.
+ Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
+ for (int i = 0; i < startedUsers.size(); i++) {
+ UserState uss = startedUsers.valueAt(i);
+ int userId = uss.mHandle.getIdentifier();
+ Slogf.i(TAG, "Attempting to unlock user %d on boot complete", userId);
+ maybeUnlockUser(userId);
+ }
+ }
+
void sendBootCompleted(IIntentReceiver resultTo) {
+ if (UserManager.isHeadlessSystemUserMode()) {
+ // Unlocking users is delayed until boot complete for headless system user mode.
+ onBootCompletedOnHeadlessSystemUserModeDevices();
+ }
+
// Get a copy of mStartedUsers to use outside of lock
SparseArray<UserState> startedUsers;
synchronized (mLock) {
@@ -2848,6 +2911,7 @@ class UserController implements Handler.Callback {
pw.println(" mTargetUserId:" + mTargetUserId);
pw.println(" mLastActiveUsers:" + mLastActiveUsers);
pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking);
+ pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking);
pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch);
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e97d9c22620e..736914ace215 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -17,9 +17,12 @@ package com.android.server.audio;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -118,8 +121,39 @@ import java.util.concurrent.atomic.AtomicBoolean;
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
/*package*/ final Object mSetModeLock = new Object();
- /** PID of current audio mode owner communicated by AudioService */
- private int mModeOwnerPid = 0;
+ /** AudioModeInfo contains information on current audio mode owner
+ * communicated by AudioService */
+ /* package */ static final class AudioModeInfo {
+ /** Current audio mode */
+ final int mMode;
+ /** PID of current audio mode owner */
+ final int mPid;
+ /** UID of current audio mode owner */
+ final int mUid;
+
+ AudioModeInfo(int mode, int pid, int uid) {
+ mMode = mode;
+ mPid = pid;
+ mUid = uid;
+ }
+
+ @Override
+ public String toString() {
+ return "AudioModeInfo: mMode=" + AudioSystem.modeToString(mMode)
+ + ", mPid=" + mPid
+ + ", mUid=" + mUid;
+ }
+ };
+
+ private AudioModeInfo mAudioModeOwner = new AudioModeInfo(AudioSystem.MODE_NORMAL, 0, 0);
+
+ /**
+ * Indicates that default communication device is chosen by routing rules in audio policy
+ * manager and not forced by AudioDeviceBroker.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
+ public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
//-------------------------------------------------------------------
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
@@ -187,7 +221,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/*package*/ void onSystemReady() {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mModeOwnerPid = mAudioService.getModeOwnerPid();
+ mAudioModeOwner = mAudioService.getAudioModeOwner();
mBtHelper.onSystemReady();
}
}
@@ -368,11 +402,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
@GuardedBy("mDeviceStateLock")
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (crc.getPid() == mModeOwnerPid) {
+ if (crc.getPid() == mAudioModeOwner.mPid) {
return crc;
}
}
- if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
+ if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
return mCommunicationRouteClients.get(0);
}
return null;
@@ -390,7 +424,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "requestedCommunicationDevice, device: "
- + device + " mode owner pid: " + mModeOwnerPid);
+ + device + "mAudioModeOwner: " + mAudioModeOwner.toString());
}
return device;
}
@@ -774,8 +808,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
}
- /*package*/ void postSetModeOwnerPid(int pid, int mode) {
- sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
+ /*package*/ void postSetModeOwner(int mode, int pid, int uid) {
+ sendLMsgNoDelay(MSG_I_SET_MODE_OWNER, SENDMSG_REPLACE,
+ new AudioModeInfo(mode, pid, uid));
}
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -1162,7 +1197,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
pw.println(prefix + "mAccessibilityStrategyId: "
+ mAccessibilityStrategyId);
- pw.println("\n" + prefix + "mModeOwnerPid: " + mModeOwnerPid);
+ pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner);
mBtHelper.dump(pw, prefix);
}
@@ -1288,12 +1323,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
break;
case MSG_L_SET_BT_ACTIVE_DEVICE:
- synchronized (mDeviceStateLock) {
- BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- mDeviceInventory.onSetBtActiveDevice(btInfo,
- (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
- ? mAudioService.getBluetoothContextualVolumeStream()
- : AudioSystem.STREAM_DEFAULT);
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ mDeviceInventory.onSetBtActiveDevice(btInfo,
+ (btInfo.mProfile
+ != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
+ if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
+ onUpdateCommunicationRouteClient("setBluetoothActiveDevice");
+ }
+ }
}
break;
case MSG_BT_HEADSET_CNCT_FAILED:
@@ -1338,11 +1380,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
}
break;
- case MSG_I_SET_MODE_OWNER_PID:
+ case MSG_I_SET_MODE_OWNER:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mModeOwnerPid = msg.arg1;
- if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
+ mAudioModeOwner = (AudioModeInfo) msg.obj;
+ if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
onUpdateCommunicationRouteClient("setNewModeOwner");
}
}
@@ -1504,7 +1546,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_REPORT_NEW_ROUTES = 13;
private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
- private static final int MSG_I_SET_MODE_OWNER_PID = 16;
+ private static final int MSG_I_SET_MODE_OWNER = 16;
private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
@@ -1826,8 +1868,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
AudioSystem.setParameters("BT_SCO=on");
}
if (preferredCommunicationDevice == null) {
- removePreferredDevicesForStrategySync(mCommunicationStrategyId);
- removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
+ AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
+ if (defaultDevice != null) {
+ setPreferredDevicesForStrategySync(
+ mCommunicationStrategyId, Arrays.asList(defaultDevice));
+ setPreferredDevicesForStrategySync(
+ mAccessibilityStrategyId, Arrays.asList(defaultDevice));
+ } else {
+ removePreferredDevicesForStrategySync(mCommunicationStrategyId);
+ removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
+ }
} else {
setPreferredDevicesForStrategySync(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
@@ -1856,26 +1906,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
+ // @GuardedBy("mSetModeLock")
+ @GuardedBy("mDeviceStateLock")
private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- boolean wasSpeakerphoneActive = isSpeakerphoneActive();
- mPreferredCommunicationDevice = device;
- updateActiveCommunicationDevice();
- if (wasSpeakerphoneActive != isSpeakerphoneActive()) {
- try {
- mContext.sendBroadcastAsUser(
- new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
- .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- UserHandle.ALL);
- } catch (Exception e) {
- Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
- }
- }
- mAudioService.postUpdateRingerModeServiceInt();
- dispatchCommunicationDevice();
+ boolean wasSpeakerphoneActive = isSpeakerphoneActive();
+ mPreferredCommunicationDevice = device;
+ updateActiveCommunicationDevice();
+ if (wasSpeakerphoneActive != isSpeakerphoneActive()) {
+ try {
+ mContext.sendBroadcastAsUser(
+ new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+ UserHandle.ALL);
+ } catch (Exception e) {
+ Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
}
}
+ mAudioService.postUpdateRingerModeServiceInt();
+ dispatchCommunicationDevice();
}
private CommunicationRouteClient removeCommunicationRouteClient(
@@ -1915,6 +1963,32 @@ import java.util.concurrent.atomic.AtomicBoolean;
return null;
}
+ @GuardedBy("mDeviceStateLock")
+ private boolean communnicationDeviceCompatOn() {
+ return mAudioModeOwner.mMode == AudioSystem.MODE_IN_COMMUNICATION
+ && !(CompatChanges.isChangeEnabled(
+ USE_SET_COMMUNICATION_DEVICE, mAudioModeOwner.mUid)
+ || mAudioModeOwner.mUid == android.os.Process.SYSTEM_UID);
+ }
+
+ @GuardedBy("mDeviceStateLock")
+ AudioDeviceAttributes getDefaultCommunicationDevice() {
+ // For system server (Telecom) and APKs targeting S and above, we let the audio
+ // policy routing rules select the default communication device.
+ // For older APKs, we force Hearing Aid or LE Audio headset when connected as
+ // those APKs cannot select a LE Audio or Hearing Aid device explicitly.
+ AudioDeviceAttributes device = null;
+ if (communnicationDeviceCompatOn()) {
+ // If both LE and Hearing Aid are active (thie should not happen),
+ // priority to Hearing Aid.
+ device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_HEARING_AID);
+ if (device == null) {
+ device = mDeviceInventory.getDeviceOfType(AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
+ }
+ return device;
+ }
+
@Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.getDeviceSensorUuid(device);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index c1f496905dba..e90bfe85fc89 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -294,6 +294,7 @@ public class AudioDeviceInventory {
}
}
+ // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
if (AudioService.DEBUG_DEVICES) {
@@ -1526,6 +1527,19 @@ public class AudioDeviceInventory {
return di.mSensorUuid;
}
}
+
+ /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
+ synchronized (mDevicesLock) {
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (di.mDeviceType == type) {
+ return new AudioDeviceAttributes(
+ di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+ }
+ }
+ }
+ return null;
+ }
+
//----------------------------------------------------------
// For tests only
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 31f86599b865..c8aecafb0b7f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5230,16 +5230,17 @@ public class AudioService extends IAudioService.Stub
}
/**
- * Return the pid of the current audio mode owner
+ * Return information on the current audio mode owner
* @return 0 if nobody owns the mode
*/
@GuardedBy("mDeviceBroker.mSetModeLock")
- /*package*/ int getModeOwnerPid() {
+ /*package*/ AudioDeviceBroker.AudioModeInfo getAudioModeOwner() {
SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
if (hdlr != null) {
- return hdlr.getPid();
+ return new AudioDeviceBroker.AudioModeInfo(
+ hdlr.getMode(), hdlr.getPid(), hdlr.getUid());
}
- return 0;
+ return new AudioDeviceBroker.AudioModeInfo(AudioSystem.MODE_NORMAL, 0 , 0);
}
/**
@@ -5425,7 +5426,7 @@ public class AudioService extends IAudioService.Stub
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
- mDeviceBroker.postSetModeOwnerPid(pid, mode);
+ mDeviceBroker.postSetModeOwner(mode, pid, uid);
} else {
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
}
@@ -5753,7 +5754,10 @@ public class AudioService extends IAudioService.Stub
}
return deviceIds.stream().mapToInt(Integer::intValue).toArray();
}
- /** @see AudioManager#setCommunicationDevice(int) */
+ /**
+ * @see AudioManager#setCommunicationDevice(int)
+ * @see AudioManager#clearCommunicationDevice()
+ */
public boolean setCommunicationDevice(IBinder cb, int portId) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -5768,7 +5772,8 @@ public class AudioService extends IAudioService.Stub
throw new IllegalArgumentException("invalid device type " + device.getType());
}
}
- final String eventSource = new StringBuilder("setCommunicationDevice(")
+ final String eventSource = new StringBuilder()
+ .append(device == null ? "clearCommunicationDevice(" : "setCommunicationDevice(")
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 46d863d7aaec..2e1a363bcc68 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -59,7 +59,7 @@ public class ClientMonitorCallbackConverter {
// The following apply to all clients
- void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
+ public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
if (mSensorReceiver != null) {
mSensorReceiver.onAcquired(sensorId, acquiredInfo, vendorCode);
} else if (mFaceServiceReceiver != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index e0393b54d2c5..612d90670888 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -50,12 +50,14 @@ import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.function.Supplier;
-class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps {
+class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
+ PowerPressHandler {
private static final String TAG = "FingerprintEnrollClient";
@@ -266,4 +268,10 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
Slog.e(TAG, "Unable to send UI ready", e);
}
}
+
+ @Override
+ public void onPowerPressed() {
+ onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
+ 0 /* vendorCode */);
+ }
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 6b568b74c405..63c0a88bf467 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -29,16 +29,13 @@ import android.view.InputEvent;
import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.List;
/**
* An interface for the native methods of InputManagerService. We use a public interface so that
* this can be mocked for testing by Mockito.
*/
-@VisibleForTesting
-public interface NativeInputManagerService {
+interface NativeInputManagerService {
void start();
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index bbc541438c82..e88ac63615ca 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,6 +162,21 @@ public final class Slogf {
}
/**
+ * Logs a {@link Log.VEBOSE} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
+ * is enabled for the given {@code tag}, but the compiler will still create an intermediate
+ * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
+ * you're calling this method in a critical path, make sure to explicitly do the check before
+ * calling it.
+ */
+ public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+ if (!isLoggable(tag, Log.VERBOSE)) return;
+
+ v(tag, getMessage(format, args), exception);
+ }
+
+ /**
* Logs a {@link Log.DEBUG} message.
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging is
@@ -177,6 +192,21 @@ public final class Slogf {
}
/**
+ * Logs a {@link Log.DEBUG} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
+ * is enabled for the given {@code tag}, but the compiler will still create an intermediate
+ * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
+ * you're calling this method in a critical path, make sure to explicitly do the check before
+ * calling it.
+ */
+ public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+ if (!isLoggable(tag, Log.DEBUG)) return;
+
+ d(tag, getMessage(format, args), exception);
+ }
+
+ /**
* Logs a {@link Log.INFO} message.
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging is
@@ -192,6 +222,21 @@ public final class Slogf {
}
/**
+ * Logs a {@link Log.INFO} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
+ * is enabled for the given {@code tag}, but the compiler will still create an intermediate
+ * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
+ * you're calling this method in a critical path, make sure to explicitly do the check before
+ * calling it.
+ */
+ public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+ if (!isLoggable(tag, Log.INFO)) return;
+
+ i(tag, getMessage(format, args), exception);
+ }
+
+ /**
* Logs a {@link Log.WARN} message.
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
@@ -220,6 +265,7 @@ public final class Slogf {
w(tag, getMessage(format, args), exception);
}
+
/**
* Logs a {@link Log.ERROR} message.
*
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7471993545a7..690c94afd04f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1152,6 +1152,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
+ setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
@@ -2667,16 +2668,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void setWindowingMode(int windowingMode) {
- // Intentionally call onRequestedOverrideConfigurationChanged() directly to change windowing
- // mode and display windowing mode atomically.
- mTmpConfiguration.setTo(getRequestedOverrideConfiguration());
- mTmpConfiguration.windowConfiguration.setWindowingMode(windowingMode);
- mTmpConfiguration.windowConfiguration.setDisplayWindowingMode(windowingMode);
- onRequestedOverrideConfigurationChanged(mTmpConfiguration);
- }
-
- @Override
void setDisplayWindowingMode(int windowingMode) {
setWindowingMode(windowingMode);
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 9462d4f7829d..e0644b61772a 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -150,7 +150,10 @@ class DisplayWindowSettings {
final SettingsProvider.SettingsEntry overrideSettings =
mSettingsProvider.getOverrideSettings(displayInfo);
overrideSettings.mWindowingMode = mode;
- dc.setWindowingMode(mode);
+ final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
+ if (defaultTda != null) {
+ defaultTda.setWindowingMode(mode);
+ }
mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
}
@@ -253,8 +256,10 @@ class DisplayWindowSettings {
// Setting windowing mode first, because it may override overscan values later.
final int windowingMode = getWindowingModeLocked(settings, dc);
- dc.setWindowingMode(windowingMode);
-
+ final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
+ if (defaultTda != null) {
+ defaultTda.setWindowingMode(windowingMode);
+ }
final int userRotationMode = settings.mUserRotationMode != null
? settings.mUserRotationMode : WindowManagerPolicy.USER_ROTATION_FREE;
final int userRotation = settings.mUserRotation != null
@@ -311,10 +316,11 @@ class DisplayWindowSettings {
* changed.
*/
boolean updateSettingsForDisplay(DisplayContent dc) {
- if (dc.getWindowingMode() != getWindowingModeLocked(dc)) {
+ final TaskDisplayArea defaultTda = dc.getDefaultTaskDisplayArea();
+ if (defaultTda != null && defaultTda.getWindowingMode() != getWindowingModeLocked(dc)) {
// For the time being the only thing that may change is windowing mode, so just update
// that.
- dc.setWindowingMode(getWindowingModeLocked(dc));
+ defaultTda.setWindowingMode(getWindowingModeLocked(dc));
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index fb68fe666c0b..b9739f03bec5 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,7 +324,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final int callingPid = Binder.getCallingPid();
// Validate and resolve ClipDescription data before clearing the calling identity
validateAndResolveDragMimeTypeExtras(data, callingUid, callingPid, mPackageName);
- validateDragFlags(flags, callingUid);
+ validateDragFlags(flags);
final long ident = Binder.clearCallingIdentity();
try {
return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
@@ -349,11 +349,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
* Validates the given drag flags.
*/
@VisibleForTesting
- void validateDragFlags(int flags, int callingUid) {
- if (callingUid == Process.SYSTEM_UID) {
- throw new IllegalStateException("Need to validate before calling identify is cleared");
- }
-
+ void validateDragFlags(int flags) {
if ((flags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
if (!mCanStartTasksFromRecents) {
throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
@@ -367,9 +363,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@VisibleForTesting
void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid, int callingPid,
String callingPackage) {
- if (callingUid == Process.SYSTEM_UID) {
- throw new IllegalStateException("Need to validate before calling identify is cleared");
- }
final ClipDescription desc = data != null ? data.getDescription() : null;
if (desc == null) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 4063cae42b6b..b6c14bbfd8ed 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -172,6 +172,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
*/
private final boolean mCanHostHomeTask;
+ private final Configuration mTempConfiguration = new Configuration();
+
TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
int displayAreaFeature) {
this(displayContent, service, name, displayAreaFeature, false /* createdByOrganizer */,
@@ -1893,6 +1895,15 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
@Override
+ public void setWindowingMode(int windowingMode) {
+ mTempConfiguration.setTo(getRequestedOverrideConfiguration());
+ WindowConfiguration tempRequestWindowConfiguration = mTempConfiguration.windowConfiguration;
+ tempRequestWindowConfiguration.setWindowingMode(windowingMode);
+ tempRequestWindowConfiguration.setDisplayWindowingMode(windowingMode);
+ onRequestedOverrideConfigurationChanged(mTempConfiguration);
+ }
+
+ @Override
TaskDisplayArea getTaskDisplayArea() {
return this;
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 136209417f39..8444489876b8 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -135,7 +135,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
final DisplayContent display = suggestedDisplayArea.mDisplayContent;
if (DEBUG) {
appendLog("display-id=" + display.getDisplayId()
- + " display-windowing-mode=" + display.getWindowingMode()
+ + " task-display-area-windowing-mode=" + suggestedDisplayArea.getWindowingMode()
+ " suggested-display-area=" + suggestedDisplayArea);
}
@@ -154,7 +154,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// source is a freeform window in a fullscreen display launching an activity on the same
// display.
if (launchMode == WINDOWING_MODE_UNDEFINED
- && canInheritWindowingModeFromSource(display, source)) {
+ && canInheritWindowingModeFromSource(display, suggestedDisplayArea, source)) {
// The source's windowing mode may be different from its task, e.g. activity is set
// to fullscreen and its task is pinned windowing mode when the activity is entering
// pip.
@@ -182,7 +182,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalculating the bounds.
boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;
- final boolean canApplyFreeformPolicy = canApplyFreeformWindowPolicy(display, launchMode);
+ final boolean canApplyFreeformPolicy =
+ canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);
if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
&& (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) {
hasInitialBounds = true;
@@ -237,7 +238,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
== display.getDisplayId())) {
// Only set windowing mode if display is in freeform. If the display is in fullscreen
// mode we should only launch a task in fullscreen mode.
- if (currentParams.hasWindowingMode() && display.inFreeformWindowingMode()) {
+ if (currentParams.hasWindowingMode()
+ && suggestedDisplayArea.inFreeformWindowingMode()) {
launchMode = currentParams.mWindowingMode;
fullyResolvedCurrentParam = launchMode != WINDOWING_MODE_FREEFORM;
if (DEBUG) {
@@ -265,11 +267,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// this step is to define the default policy when there is no initial bounds or a fully
// resolved current params from callers.
- // hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay is set if the outParams.mBounds
+ // hasInitialBoundsForSuggestedDisplayAreaInFreeformMode is set if the outParams.mBounds
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalcuating the bounds.
- boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = false;
- if (display.inFreeformWindowingMode()) {
+ boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false;
+ if (suggestedDisplayArea.inFreeformWindowingMode()) {
if (launchMode == WINDOWING_MODE_PINNED) {
if (DEBUG) appendLog("picture-in-picture");
} else if (!root.isResizeable()) {
@@ -278,7 +280,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
if (outParams.mBounds.isEmpty()) {
getTaskBounds(root, suggestedDisplayArea, layout, launchMode,
hasInitialBounds, outParams.mBounds);
- hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay = true;
+ hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;
}
if (DEBUG) appendLog("unresizable-freeform");
} else {
@@ -288,10 +290,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
}
}
} else {
- if (DEBUG) appendLog("non-freeform-display");
+ if (DEBUG) appendLog("non-freeform-task-display-area");
}
// If launch mode matches display windowing mode, let it inherit from display.
- outParams.mWindowingMode = launchMode == display.getWindowingMode()
+ outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()
? WINDOWING_MODE_UNDEFINED : launchMode;
if (phase == PHASE_WINDOWING_MODE) {
@@ -301,7 +303,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// STEP 3: Finalize the display area. Here we allow WM shell route all launches that match
// certain criteria to specific task display areas.
final int resolvedMode = (launchMode != WINDOWING_MODE_UNDEFINED) ? launchMode
- : display.getWindowingMode();
+ : suggestedDisplayArea.getWindowingMode();
TaskDisplayArea taskDisplayArea = suggestedDisplayArea;
// If launch task display area is set in options we should just use it. We assume the
// suggestedDisplayArea has the right one in this case.
@@ -319,14 +321,17 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
mTmpDisplayArea = displayArea;
return true;
});
- // We may need to recalculate the bounds if the new TaskDisplayArea is different from
- // the suggested one we used to calculate the bounds.
+ // We may need to recalculate the bounds and the windowing mode if the new
+ // TaskDisplayArea is different from the suggested one we used to calculate the two
+ // configurations.
if (mTmpDisplayArea != null && mTmpDisplayArea != suggestedDisplayArea) {
+ outParams.mWindowingMode = (launchMode == mTmpDisplayArea.getWindowingMode())
+ ? WINDOWING_MODE_UNDEFINED : launchMode;
if (hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow) {
outParams.mBounds.setEmpty();
getLayoutBounds(mTmpDisplayArea, root, layout, outParams.mBounds);
hasInitialBounds = !outParams.mBounds.isEmpty();
- } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformDisplay) {
+ } else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformMode) {
outParams.mBounds.setEmpty();
getTaskBounds(root, mTmpDisplayArea, layout, launchMode,
hasInitialBounds, outParams.mBounds);
@@ -533,7 +538,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
}
private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
- @Nullable ActivityRecord source) {
+ TaskDisplayArea suggestedDisplayArea, @Nullable ActivityRecord source) {
if (source == null) {
return false;
}
@@ -541,7 +546,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// There is not really any strong reason to tie the launching windowing mode and the source
// on freeform displays. The launching windowing mode is more tied to the content of the new
// activities.
- if (display.inFreeformWindowingMode()) {
+ if (suggestedDisplayArea.inFreeformWindowingMode()) {
return false;
}
@@ -557,9 +562,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
return display.getDisplayId() == source.getDisplayId();
}
- private boolean canApplyFreeformWindowPolicy(@NonNull DisplayContent display, int launchMode) {
+ private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea,
+ int launchMode) {
return mSupervisor.mService.mSupportsFreeformWindowManagement
- && (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM);
+ && (suggestedDisplayArea.inFreeformWindowingMode()
+ || launchMode == WINDOWING_MODE_FREEFORM);
}
private boolean canApplyPipWindowPolicy(int launchMode) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 72e7e65ae43a..805559035ef9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -631,12 +630,6 @@ class WindowToken extends WindowContainer<WindowState> {
getResolvedOverrideConfiguration().updateFrom(
mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
- if (getTaskDisplayArea() == null) {
- // We only defined behaviors of system windows in fullscreen mode, i.e. windows not
- // contained in a task display area.
- getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
- WINDOWING_MODE_FULLSCREEN);
- }
}
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java
new file mode 100644
index 000000000000..29bddfc32ff7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/EconomicPolicyTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.tare;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EconomicPolicyTest {
+
+ @Test
+ public void testMasksDisjoint() {
+ assertEquals(-1,
+ (-1 & EconomicPolicy.MASK_TYPE)
+ + (-1 & EconomicPolicy.MASK_POLICY)
+ + (-1 & EconomicPolicy.MASK_EVENT));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index 3e8cef9afc15..ae25c1bb3db8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -103,6 +103,24 @@ public final class SlogfTest {
}
@Test
+ public void testV_msgFormattedWithException_enabled() {
+ enableLogging(Log.VERBOSE);
+
+ Slogf.v(TAG, mException, "msg in a %s", "bottle");
+
+ verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+ }
+
+ @Test
+ public void testV_msgFormattedWithException_disabled() {
+ disableLogging(Log.VERBOSE);
+
+ Slogf.v(TAG, "msg in a %s", "bottle");
+
+ verify(()-> Slog.v(eq(TAG), any(String.class), any(Throwable.class)), never());
+ }
+
+ @Test
public void testD_msg() {
Slogf.d(TAG, "msg");
@@ -135,6 +153,24 @@ public final class SlogfTest {
}
@Test
+ public void testD_msgFormattedWithException_enabled() {
+ enableLogging(Log.DEBUG);
+
+ Slogf.d(TAG, mException, "msg in a %s", "bottle");
+
+ verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+ }
+
+ @Test
+ public void testD_msgFormattedWithException_disabled() {
+ disableLogging(Log.DEBUG);
+
+ Slogf.d(TAG, mException, "msg in a %s", "bottle");
+
+ verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
+ }
+
+ @Test
public void testI_msg() {
Slogf.i(TAG, "msg");
@@ -167,6 +203,24 @@ public final class SlogfTest {
}
@Test
+ public void testI_msgFormattedWithException_enabled() {
+ enableLogging(Log.INFO);
+
+ Slogf.i(TAG, mException, "msg in a %s", "bottle");
+
+ verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+ }
+
+ @Test
+ public void testI_msgFormattedWithException_disabled() {
+ disableLogging(Log.INFO);
+
+ Slogf.i(TAG, mException, "msg in a %s", "bottle");
+
+ verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
+ }
+
+ @Test
public void testW_msg() {
Slogf.w(TAG, "msg");
@@ -218,7 +272,7 @@ public final class SlogfTest {
public void testW_msgFormattedWithException_disabled() {
disableLogging(Log.WARN);
- Slogf.w(TAG, "msg in a %s", "bottle");
+ Slogf.w(TAG, mException, "msg in a %s", "bottle");
verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -268,7 +322,7 @@ public final class SlogfTest {
public void testE_msgFormattedWithException_disabled() {
disableLogging(Log.ERROR);
- Slogf.e(TAG, "msg in a %s", "bottle");
+ Slogf.e(TAG, mException, "msg in a %s", "bottle");
verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe079f423094..81f899c7d645 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -181,6 +181,11 @@ public class UserControllerTest {
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
mUserController = new UserController(mInjector);
+
+ // TODO(b/232452368): need to explicitly call setAllowUserUnlocking(), otherwise most
+ // tests would fail. But we might need to disable it for the onBootComplete() test (i.e,
+ // to make sure the users are unlocked at the right time)
+ mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
});
@@ -628,6 +633,16 @@ public class UserControllerTest {
}
@Test
+ public void testUserNotUnlockedBeforeAllowed() throws Exception {
+ mUserController.setAllowUserUnlocking(false);
+
+ mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
+
+ verify(mInjector.mStorageManagerMock, never())
+ .unlockUserKey(eq(TEST_USER_ID), anyInt(), any());
+ }
+
+ @Test
public void testStartProfile_fullUserFails() {
setUpUser(TEST_USER_ID1, 0);
assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 92e1f27ab624..837b55397416 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -253,6 +255,16 @@ public class FingerprintEnrollClientTest {
showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
}
+ @Test
+ public void testPowerPressForwardsAcquireMessage() throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+ client.start(mCallback);
+ client.onPowerPressed();
+
+ verify(mClientMonitorCallbackConverter).onAcquired(anyInt(),
+ eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt());
+ }
+
private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
throws RemoteException {
final FingerprintEnrollClient client = createClient();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index b5764f54ff92..4d71b30d71e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -134,9 +134,9 @@ public class AppChangeTransitionTests extends WindowTestsBase {
@Test
public void testNoChangeOnOldDisplayWhenMoveDisplay() {
- mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mDisplayContent.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- dc1.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ dc1.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FREEFORM);
setUpOnDisplay(dc1);
assertEquals(WINDOWING_MODE_FREEFORM, mTask.getWindowingMode());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7244d9492deb..44471f49668c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1067,6 +1067,7 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
+ dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final int newOrientation = getRotatedOrientation(dc);
final Task task = new TaskBuilder(mSupervisor)
@@ -1126,6 +1127,7 @@ public class DisplayContentTests extends WindowTestsBase {
IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
dc.getDisplayRotation().setUserRotation(
WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0);
+ dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final int newOrientation = getRotatedOrientation(dc);
final Task task = new TaskBuilder(mSupervisor)
@@ -2032,23 +2034,6 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
- public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() {
- final DisplayContent dc = createNewDisplay();
- final Task rootTask = new TaskBuilder(mSupervisor)
- .setDisplay(dc)
- .build();
- doAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- final Configuration config = ((Configuration) args[0]);
- assertEquals(config.windowConfiguration.getWindowingMode(),
- config.windowConfiguration.getDisplayWindowingMode());
- return null;
- }).when(rootTask).onConfigurationChanged(any());
- dc.setWindowingMode(WINDOWING_MODE_FREEFORM);
- dc.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- }
-
- @Test
public void testForceDesktopMode() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 093be82c6128..c398a0a26016 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -110,7 +110,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mPrimaryDisplay.getWindowingMode());
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -120,7 +120,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mPrimaryDisplay.getWindowingMode());
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -131,7 +131,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mPrimaryDisplay.getWindowingMode());
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -141,7 +141,8 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
- assertEquals(WINDOWING_MODE_FREEFORM, mPrimaryDisplay.getWindowingMode());
+ assertEquals(WINDOWING_MODE_FREEFORM,
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -154,7 +155,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.updateSettingsForDisplay(mPrimaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM,
- mPrimaryDisplay.getWindowingMode());
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -162,7 +163,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mSecondaryDisplay.getWindowingMode());
+ mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -172,7 +173,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mSecondaryDisplay.getWindowingMode());
+ mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -183,7 +184,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
assertEquals(WINDOWING_MODE_FREEFORM,
- mSecondaryDisplay.getWindowingMode());
+ mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
@@ -194,7 +195,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase {
mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
assertEquals(WINDOWING_MODE_FREEFORM,
- mSecondaryDisplay.getWindowingMode());
+ mSecondaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 28fc352e2f74..4526d18d63d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -467,8 +467,7 @@ public class DragDropControllerTests extends WindowTestsBase {
public void onAnimatorScaleChanged(float scale) {}
});
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
- TEST_UID);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
fail("Expected failure without permission");
} catch (SecurityException e) {
// Expected failure
@@ -484,8 +483,7 @@ public class DragDropControllerTests extends WindowTestsBase {
public void onAnimatorScaleChanged(float scale) {}
});
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
- TEST_UID);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
// Expected pass
} catch (SecurityException e) {
fail("Expected no failure with permission");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 9e658e09b8b8..4f03f54bab37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -108,9 +108,10 @@ public class RootWindowContainerTests extends WindowTestsBase {
}
@Test
- public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() {
+ public void testUpdateDefaultTaskDisplayAreaWindowingModeOnSettingsRetrieved() {
assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
- mWm.getDefaultDisplayContentLocked().getWindowingMode());
+ mWm.getDefaultDisplayContentLocked().getDefaultTaskDisplayArea()
+ .getWindowingMode());
mWm.mIsPc = true;
mWm.mAtmService.mSupportsFreeformWindowManagement = true;
@@ -118,7 +119,8 @@ public class RootWindowContainerTests extends WindowTestsBase {
mWm.mRoot.onSettingsRetrieved();
assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM,
- mWm.getDefaultDisplayContentLocked().getWindowingMode());
+ mWm.getDefaultDisplayContentLocked().getDefaultTaskDisplayArea()
+ .getWindowingMode());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 646f43cbdb5c..9f7f3411b2b3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -676,7 +676,8 @@ public class SizeCompatTests extends WindowTestsBase {
// The non-resizable activity should not be size compat because the display support
// changing windowing mode from fullscreen to freeform.
- mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mTask.mDisplayContent.getDefaultTaskDisplayArea()
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
assertFalse(activity.shouldCreateCompatDisplayInsets());
// Activity should not be sandboxed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 9e6c4c58371b..f5fc5c13eb4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -345,7 +345,7 @@ public class SystemServicesTestRule implements TestRule {
// Set default display to be in fullscreen mode. Devices with PC feature may start their
// default display in freeform mode but some of tests in WmTests have implicit assumption on
// that the default display is in fullscreen mode.
- display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ display.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
spyOn(display);
final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 22101c268a9c..aaf855f90122 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -1843,7 +1843,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase {
private TestDisplayContent createNewDisplayContent(int windowingMode) {
final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
- display.setWindowingMode(windowingMode);
+ display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
display.setBounds(DISPLAY_BOUNDS);
display.getConfiguration().densityDpi = DENSITY_DEFAULT;
display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 8e89d6f4e898..e352d762458a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1257,7 +1257,8 @@ public class TaskTests extends WindowTestsBase {
final Task task = getTestTask();
task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getDisplayContent().getDefaultTaskDisplayArea()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM);
task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
task.setHasBeenVisible(true);
@@ -1273,7 +1274,9 @@ public class TaskTests extends WindowTestsBase {
final Task task = getTestTask();
task.setHasBeenVisible(false);
- task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ task.getDisplayContent()
+ .getDefaultTaskDisplayArea()
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final DisplayContent oldDisplay = task.getDisplayContent();
@@ -1313,7 +1316,8 @@ public class TaskTests extends WindowTestsBase {
final Task task = getTestTask();
task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getDisplayContent().getDefaultTaskDisplayArea()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM);
task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED);
task.setHasBeenVisible(true);
@@ -1330,7 +1334,8 @@ public class TaskTests extends WindowTestsBase {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setCreateParentTask(true).build().getRootTask();
task.setHasBeenVisible(false);
- task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ task.getDisplayContent().getDefaultTaskDisplayArea()
+ .setWindowingMode(WINDOWING_MODE_FREEFORM);
task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final Task leafTask = createTaskInRootTask(task, 0 /* userId */);