diff options
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 */); |