Merge "Update uiautomator references in frameworks/base" am: b6d9367bc9 am: 089f7d6380
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2219973
Change-Id: Idd3875bea82b67b9f2d03c7cbbcdb18b2bb39175
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 1b2d905..e263810 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -23,6 +23,7 @@
#include <cstring>
#include <filesystem>
#include <fstream>
+#include <limits>
#include <memory>
#include <ostream>
#include <string>
@@ -295,26 +296,42 @@
return ok();
}
-Status Idmap2Service::acquireFabricatedOverlayIterator() {
+Status Idmap2Service::acquireFabricatedOverlayIterator(int32_t* _aidl_return) {
+ std::lock_guard l(frro_iter_mutex_);
if (frro_iter_.has_value()) {
LOG(WARNING) << "active ffro iterator was not previously released";
}
frro_iter_ = std::filesystem::directory_iterator(kIdmapCacheDir);
+ if (frro_iter_id_ == std::numeric_limits<int32_t>::max()) {
+ frro_iter_id_ = 0;
+ } else {
+ ++frro_iter_id_;
+ }
+ *_aidl_return = frro_iter_id_;
return ok();
}
-Status Idmap2Service::releaseFabricatedOverlayIterator() {
+Status Idmap2Service::releaseFabricatedOverlayIterator(int32_t iteratorId) {
+ std::lock_guard l(frro_iter_mutex_);
if (!frro_iter_.has_value()) {
LOG(WARNING) << "no active ffro iterator to release";
+ } else if (frro_iter_id_ != iteratorId) {
+ LOG(WARNING) << "incorrect iterator id in a call to release";
+ } else {
+ frro_iter_.reset();
}
return ok();
}
-Status Idmap2Service::nextFabricatedOverlayInfos(
+Status Idmap2Service::nextFabricatedOverlayInfos(int32_t iteratorId,
std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+ std::lock_guard l(frro_iter_mutex_);
+
constexpr size_t kMaxEntryCount = 100;
if (!frro_iter_.has_value()) {
return error("no active frro iterator");
+ } else if (frro_iter_id_ != iteratorId) {
+ return error("incorrect iterator id in a call to next");
}
size_t count = 0;
@@ -322,22 +339,22 @@
auto entry_iter_end = end(*frro_iter_);
for (; entry_iter != entry_iter_end && count < kMaxEntryCount; ++entry_iter) {
auto& entry = *entry_iter;
- if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path())) {
+ if (!entry.is_regular_file() || !android::IsFabricatedOverlay(entry.path().native())) {
continue;
}
- const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+ const auto overlay = FabricatedOverlayContainer::FromPath(entry.path().native());
if (!overlay) {
LOG(WARNING) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
continue;
}
- const auto info = (*overlay)->GetManifestInfo();
+ auto info = (*overlay)->GetManifestInfo();
os::FabricatedOverlayInfo out_info;
- out_info.packageName = info.package_name;
- out_info.overlayName = info.name;
- out_info.targetPackageName = info.target_package;
- out_info.targetOverlayable = info.target_name;
+ out_info.packageName = std::move(info.package_name);
+ out_info.overlayName = std::move(info.name);
+ out_info.targetPackageName = std::move(info.target_package);
+ out_info.targetOverlayable = std::move(info.target_name);
out_info.path = entry.path();
_aidl_return->emplace_back(std::move(out_info));
count++;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index c61e4bc..cc8cc5f 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -26,7 +26,10 @@
#include <filesystem>
#include <memory>
+#include <mutex>
+#include <optional>
#include <string>
+#include <variant>
#include <vector>
namespace android::os {
@@ -60,11 +63,11 @@
binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
bool* _aidl_return) override;
- binder::Status acquireFabricatedOverlayIterator() override;
+ binder::Status acquireFabricatedOverlayIterator(int32_t* _aidl_return) override;
- binder::Status releaseFabricatedOverlayIterator() override;
+ binder::Status releaseFabricatedOverlayIterator(int32_t iteratorId) override;
- binder::Status nextFabricatedOverlayInfos(
+ binder::Status nextFabricatedOverlayInfos(int32_t iteratorId,
std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
binder::Status dumpIdmap(const std::string& overlay_path, std::string* _aidl_return) override;
@@ -74,7 +77,9 @@
// be able to be recalculated if idmap2 dies and restarts.
std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+ int32_t frro_iter_id_ = 0;
std::optional<std::filesystem::directory_iterator> frro_iter_;
+ std::mutex frro_iter_mutex_;
template <typename T>
using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
diff --git a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 0059cf2..2bbfba9 100644
--- a/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -41,9 +41,9 @@
@nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
boolean deleteFabricatedOverlay(@utf8InCpp String path);
- void acquireFabricatedOverlayIterator();
- void releaseFabricatedOverlayIterator();
- List<FabricatedOverlayInfo> nextFabricatedOverlayInfos();
+ int acquireFabricatedOverlayIterator();
+ void releaseFabricatedOverlayIterator(int iteratorId);
+ List<FabricatedOverlayInfo> nextFabricatedOverlayInfos(int iteratorId);
@utf8InCpp String dumpIdmap(@utf8InCpp String overlayApkPath);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7c1f9c8..855366a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8045,8 +8045,9 @@
resultData.prepareToLeaveProcess(this);
}
upIntent.prepareToLeaveProcess(this);
- return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resultCode,
- resultData);
+ String resolvedType = upIntent.resolveTypeIfNeeded(getContentResolver());
+ return ActivityClient.getInstance().navigateUpTo(mToken, upIntent, resolvedType,
+ resultCode, resultData);
} else {
return mParent.navigateUpToFromChild(this, upIntent);
}
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 482f456..d1e6780 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -141,11 +141,11 @@
}
}
- boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+ boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType, int resultCode,
Intent resultData) {
try {
- return getActivityClientController().navigateUpTo(token, destIntent, resultCode,
- resultData);
+ return getActivityClientController().navigateUpTo(token, destIntent, resolvedType,
+ resultCode, resultData);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e1448..aa5fa5b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@
return mIsAlarmBroadcast;
}
+ /**
+ * Did this broadcast originate from a push message from the server?
+ *
+ * @return true if this broadcast is a push message, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingBroadcast() {
+ return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+ }
+
+ /**
+ * Did this broadcast originate from a push message from the server which was over the allowed
+ * quota?
+ *
+ * @return true if this broadcast is a push message over quota, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingOverQuotaBroadcast() {
+ return mTemporaryAppAllowlistReasonCode
+ == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+ }
+
/** {@hide} */
public long getRequireCompatChangeId() {
return mRequireCompatChangeId;
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index f5e5cda..9aa67bc 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -60,8 +60,8 @@
in SizeConfigurationBuckets sizeConfigurations);
boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
boolean shouldUpRecreateTask(in IBinder token, in String destAffinity);
- boolean navigateUpTo(in IBinder token, in Intent target, int resultCode,
- in Intent resultData);
+ boolean navigateUpTo(in IBinder token, in Intent target, in String resolvedType,
+ int resultCode, in Intent resultData);
boolean releaseActivityInstance(in IBinder token);
boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask);
boolean finishActivityAffinity(in IBinder token);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 34c91c3..8e09939 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12150,6 +12150,15 @@
* Attempts by the admin to grant these permissions, when the admin is restricted from doing
* so, will be silently ignored (no exception will be thrown).
*
+ * Control over the following permissions are restricted for managed profile owners:
+ * <ul>
+ * <li>Manifest.permission.READ_SMS</li>
+ * </ul>
+ * <p>
+ * A managed profile owner may not grant these permissions (i.e. call this method with any of
+ * the permissions listed above and {@code grantState} of
+ * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them.
+ *
* @param admin Which profile or device owner this request is associated with.
* @param packageName The application to grant or revoke a permission to.
* @param permission The permission to grant or revoke.
diff --git a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
new file mode 100644
index 0000000..dc5a966
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.hardware.input;
+
+/** @hide */
+oneway interface IInputDeviceBatteryListener {
+
+ /**
+ * Called when there is a change in battery state for a monitored device. This will be called
+ * immediately after the listener is successfully registered for a new device via IInputManager.
+ * The parameters are values exposed through {@link android.hardware.BatteryState}.
+ */
+ void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status, float capacity,
+ long eventTime);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2da12e6c..a645ae4 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -20,6 +20,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
@@ -155,4 +156,8 @@
void closeLightSession(int deviceId, in IBinder token);
void cancelCurrentTouch();
+
+ void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+ void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d17a952..97812ce 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -30,6 +30,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.BatteryState;
import android.hardware.SensorManager;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
@@ -66,6 +67,7 @@
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -74,6 +76,8 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Provides information about input devices and available key layouts.
@@ -111,6 +115,14 @@
private TabletModeChangedListener mTabletModeChangedListener;
private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
+ private final Object mBatteryListenersLock = new Object();
+ // Maps a deviceId whose battery is currently being monitored to an entry containing the
+ // registered listeners for that device.
+ @GuardedBy("mBatteryListenersLock")
+ private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
+ @GuardedBy("mBatteryListenersLock")
+ private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+
private InputDeviceSensorManager mInputDeviceSensorManager;
/**
* Broadcast Action: Query available keyboard layouts.
@@ -1752,6 +1764,129 @@
}
/**
+ * Adds a battery listener to be notified about {@link BatteryState} changes for an input
+ * device. The same listener can be registered for multiple input devices.
+ * The listener will be notified of the initial battery state of the device after it is
+ * successfully registered.
+ * @param deviceId the input device that should be monitored
+ * @param executor an executor on which the callback will be called
+ * @param listener the {@link InputDeviceBatteryListener}
+ * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+ * @hide
+ */
+ public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ mBatteryListeners = new SparseArray<>();
+ mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is currently not being monitored for battery changes.
+ // Start monitoring the device.
+ listenersForDevice = new RegisteredBatteryListeners();
+ mBatteryListeners.put(deviceId, listenersForDevice);
+ try {
+ mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ // The deviceId is already being monitored for battery changes.
+ // Ensure that the listener is not already registered.
+ for (InputDeviceBatteryListenerDelegate delegate : listenersForDevice.mDelegates) {
+ if (Objects.equals(listener, delegate.mListener)) {
+ throw new IllegalArgumentException(
+ "Attempting to register an InputDeviceBatteryListener that has "
+ + "already been registered for deviceId: "
+ + deviceId);
+ }
+ }
+ }
+ final InputDeviceBatteryListenerDelegate delegate =
+ new InputDeviceBatteryListenerDelegate(listener, executor);
+ listenersForDevice.mDelegates.add(delegate);
+
+ // Notify the listener immediately if we already have the latest battery state.
+ if (listenersForDevice.mLatestBatteryState != null) {
+ delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
+ }
+ }
+ }
+
+ /**
+ * Removes a previously registered battery listener for an input device.
+ * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+ * @hide
+ */
+ public void removeInputDeviceBatteryListener(int deviceId,
+ @NonNull InputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) {
+ return;
+ }
+ RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
+ if (listenersForDevice == null) {
+ // The deviceId is not currently being monitored.
+ return;
+ }
+ final List<InputDeviceBatteryListenerDelegate> delegates =
+ listenersForDevice.mDelegates;
+ for (int i = 0; i < delegates.size();) {
+ if (Objects.equals(listener, delegates.get(i).mListener)) {
+ delegates.remove(i);
+ continue;
+ }
+ i++;
+ }
+ if (!delegates.isEmpty()) {
+ return;
+ }
+
+ // There are no more battery listeners for this deviceId. Stop monitoring this device.
+ mBatteryListeners.remove(deviceId);
+ try {
+ mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (mBatteryListeners.size() == 0) {
+ // There are no more devices being monitored, so the registered
+ // IInputDeviceBatteryListener will be automatically dropped by the server.
+ mBatteryListeners = null;
+ mInputDeviceBatteryListener = null;
+ }
+ }
+ }
+
+ /**
+ * A callback used to be notified about battery state changes for an input device. The
+ * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
+ * listener is successfully registered to provide the initial battery state of the device.
+ * @see InputDevice#getBatteryState()
+ * @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
+ * @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
+ * @hide
+ */
+ public interface InputDeviceBatteryListener {
+ /**
+ * Called when the battery state of an input device changes.
+ * @param deviceId the input device for which the battery changed.
+ * @param eventTimeMillis the time (in ms) when the battery change took place.
+ * This timestamp is in the {@link SystemClock#uptimeMillis()} time base.
+ * @param batteryState the new battery state, never null.
+ */
+ void onBatteryStateChanged(
+ int deviceId, long eventTimeMillis, @NonNull BatteryState batteryState);
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
@@ -1861,4 +1996,76 @@
}
}
}
+
+ private static final class LocalBatteryState extends BatteryState {
+ final int mDeviceId;
+ final boolean mIsPresent;
+ final int mStatus;
+ final float mCapacity;
+ final long mEventTime;
+
+ LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
+ long eventTime) {
+ mDeviceId = deviceId;
+ mIsPresent = isPresent;
+ mStatus = status;
+ mCapacity = capacity;
+ mEventTime = eventTime;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return mIsPresent;
+ }
+
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public float getCapacity() {
+ return mCapacity;
+ }
+ }
+
+ private static final class RegisteredBatteryListeners {
+ final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
+ LocalBatteryState mLatestBatteryState;
+ }
+
+ private static final class InputDeviceBatteryListenerDelegate {
+ final InputDeviceBatteryListener mListener;
+ final Executor mExecutor;
+
+ InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void notifyBatteryStateChanged(LocalBatteryState batteryState) {
+ mExecutor.execute(() ->
+ mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
+ batteryState));
+ }
+ }
+
+ private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
+ @Override
+ public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
+ float capacity, long eventTime) {
+ synchronized (mBatteryListenersLock) {
+ if (mBatteryListeners == null) return;
+ final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
+ if (entry == null) return;
+
+ entry.mLatestBatteryState =
+ new LocalBatteryState(
+ deviceId, isBatteryPresent, status, capacity, eventTime);
+ for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
+ delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 06c35b5..3d5c34c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -962,7 +962,7 @@
* also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility", "attention"
+ "other", "button", "touch", "accessibility", "attention", "faceDown", "deviceState"
};
public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 58f9336..26e5e95 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -34,6 +34,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -449,6 +450,16 @@
return proto.getBytes();
}
+ /**
+ * Writes contents in a binary protobuffer format, using
+ * the android.os.BatteryUsageStatsAtomsProto proto.
+ */
+ public void dumpToProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ writeStatsProto(proto, /* max size */ Integer.MAX_VALUE);
+ proto.flush();
+ }
+
@NonNull
private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 7f9f720..fc4bb6a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4422,6 +4422,9 @@
int type = readInt();
if (isLengthPrefixed(type)) {
int objectLength = readInt();
+ if (objectLength < 0) {
+ return null;
+ }
int end = MathUtils.addOrThrow(dataPosition(), objectLength);
int valueLength = end - start;
setDataPosition(end);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13ca2c3..a46868e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -345,6 +345,39 @@
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 6956cd4..295171c 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -29,16 +29,18 @@
*
* @param doze If true, starts the doze dream component if one has been configured,
* otherwise starts the user-specified dream.
+ * @param reason The reason to start dreaming, which is logged to help debugging.
*/
- public abstract void startDream(boolean doze);
+ public abstract void startDream(boolean doze, String reason);
/**
* Called by the power manager to stop a dream.
*
* @param immediate If true, ends the dream summarily, otherwise gives it some time
* to perform a proper exit transition.
+ * @param reason The reason to stop dreaming, which is logged to help debugging.
*/
- public abstract void stopDream(boolean immediate);
+ public abstract void stopDream(boolean immediate, String reason);
/**
* Called by the power manager to determine whether a dream is running.
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index fae72a2..ced2a01 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -20,13 +20,13 @@
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import com.android.server.LocalServices;
-
/**
* Internal helper for launching dreams to ensure consistency between the
* <code>UiModeManagerService</code> system service and the <code>Somnambulator</code> activity.
@@ -75,28 +75,32 @@
}
private static void startDream(Context context, boolean docked) {
- DreamManagerInternal dreamManagerService =
- LocalServices.getService(DreamManagerInternal.class);
- if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
- if (docked) {
- Slog.i(TAG, "Activating dream while docked.");
+ try {
+ IDreamManager dreamManagerService = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+ if (dreamManagerService != null && !dreamManagerService.isDreaming()) {
+ if (docked) {
+ Slog.i(TAG, "Activating dream while docked.");
- // Wake up.
- // The power manager will wake up the system automatically when it starts
- // receiving power from a dock but there is a race between that happening
- // and the UI mode manager starting a dream. We want the system to already
- // be awake by the time this happens. Otherwise the dream may not start.
- PowerManager powerManager =
- context.getSystemService(PowerManager.class);
- powerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_PLUGGED_IN,
- "android.service.dreams:DREAM");
- } else {
- Slog.i(TAG, "Activating dream by user request.");
+ // Wake up.
+ // The power manager will wake up the system automatically when it starts
+ // receiving power from a dock but there is a race between that happening
+ // and the UI mode manager starting a dream. We want the system to already
+ // be awake by the time this happens. Otherwise the dream may not start.
+ PowerManager powerManager =
+ context.getSystemService(PowerManager.class);
+ powerManager.wakeUp(SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_PLUGGED_IN,
+ "android.service.dreams:DREAM");
+ } else {
+ Slog.i(TAG, "Activating dream by user request.");
+ }
+
+ // Dream.
+ dreamManagerService.dream();
}
-
- // Dream.
- dreamManagerService.requestDream();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Could not start dream when docked.", ex);
}
}
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 206e4fa..e5ad85c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -42,7 +42,8 @@
/** @hide */
@IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = {
- DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE
+ DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD,
+ DISMISSAL_SHADE, DISMISSAL_BUBBLE, DISMISSAL_LOCKSCREEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface DismissalSurface {}
@@ -75,7 +76,12 @@
* Notification has been dismissed as a bubble.
* @hide
*/
- public static final int DISMISSAL_BUBBLE = 3;
+ public static final int DISMISSAL_BUBBLE = 4;
+ /**
+ * Notification has been dismissed from the lock screen.
+ * @hide
+ */
+ public static final int DISMISSAL_LOCKSCREEN = 5;
/** @hide */
@IntDef(prefix = { "DISMISS_SENTIMENT_" }, value = {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 7d56039..7164412 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1022,6 +1022,15 @@
}
/**
+ * Reports whether the device has a battery.
+ * @return true if the device has a battery, false otherwise.
+ * @hide
+ */
+ public boolean hasBattery() {
+ return mHasBattery;
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c33358..89d31b9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1099,6 +1099,10 @@
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
}
+
+ // Update the last resource config in case the resource configuration was changed while
+ // activity relaunched.
+ mLastConfigurationFromResources.setTo(getConfiguration());
}
private Configuration getConfiguration() {
@@ -8156,6 +8160,7 @@
mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
mTempInsets, mTempControls, mRelayoutBundle);
mRelayoutRequested = true;
+
final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
@@ -8195,6 +8200,12 @@
}
}
+ if (mSurfaceControl.isValid() && !HardwareRenderer.isDrawingEnabled()) {
+ // When drawing is disabled the window layer won't have a valid buffer.
+ // Set a window crop so input can get delivered to the window.
+ mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply();
+ }
+
mLastTransformHint = transformHint;
mSurfaceControl.setTransformHint(transformHint);
@@ -8484,6 +8495,10 @@
if (mLocalSyncState != LOCAL_SYNC_NONE) {
writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
}
+ writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+ + mLastReportedMergedConfiguration);
+ writer.println(innerPrefix + "mLastConfigurationFromResources="
+ + mLastConfigurationFromResources);
writer.println(innerPrefix + "mIsAmbientMode=" + mIsAmbientMode);
writer.println(innerPrefix + "mUnbufferedInputSource="
+ Integer.toHexString(mUnbufferedInputSource));
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 218ca58..2f77901 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -777,12 +777,6 @@
int TAKE_SCREENSHOT_FULLSCREEN = 1;
/**
- * Invoke screenshot flow allowing the user to select a region.
- * @hide
- */
- int TAKE_SCREENSHOT_SELECTED_REGION = 2;
-
- /**
* Invoke screenshot flow with an image provided by the caller.
* @hide
*/
@@ -794,7 +788,6 @@
* @hide
*/
@IntDef({TAKE_SCREENSHOT_FULLSCREEN,
- TAKE_SCREENSHOT_SELECTED_REGION,
TAKE_SCREENSHOT_PROVIDED_IMAGE})
@interface ScreenshotType {}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 3c7cd02..36eaf49 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -51,16 +51,19 @@
in IWindowContainerTransactionCallback callback);
/**
- * Starts a transition.
+ * Starts a new transition.
* @param type The transition type.
- * @param transitionToken A token associated with the transition to start. If null, a new
- * transition will be created of the provided type.
* @param t Operations that are part of the transition.
- * @return a token representing the transition. This will just be transitionToken if it was
- * non-null.
+ * @return a token representing the transition.
*/
- IBinder startTransition(int type, in @nullable IBinder transitionToken,
- in @nullable WindowContainerTransaction t);
+ IBinder startNewTransition(int type, in @nullable WindowContainerTransaction t);
+
+ /**
+ * Starts the given transition.
+ * @param transitionToken A token associated with the transition to start.
+ * @param t Operations that are part of the transition.
+ */
+ oneway void startTransition(IBinder transitionToken, in @nullable WindowContainerTransaction t);
/**
* Starts a legacy transition.
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 64b2638..841354a 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -33,19 +33,19 @@
private final int mDisplayId;
- private final boolean mVisibleRequested;
+ private final boolean mVisible;
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
- boolean visibleRequested) {
+ boolean visible) {
mConfiguration.setTo(configuration);
mDisplayId = displayId;
- mVisibleRequested = visibleRequested;
+ mVisible = visible;
}
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
- mVisibleRequested = info.mVisibleRequested;
+ mVisible = info.mVisible;
}
/** The {@link Configuration} of the parent Task */
@@ -62,9 +62,9 @@
return mDisplayId;
}
- /** Whether the parent Task is requested to be visible or not */
- public boolean isVisibleRequested() {
- return mVisibleRequested;
+ /** Whether the parent Task is visible or not */
+ public boolean isVisible() {
+ return mVisible;
}
/**
@@ -80,7 +80,7 @@
return false;
}
return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@WindowConfiguration.WindowingMode
@@ -93,7 +93,7 @@
return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+ "config=" + mConfiguration
+ ", displayId=" + mDisplayId
- + ", visibleRequested=" + mVisibleRequested
+ + ", visible=" + mVisible
+ "}";
}
@@ -114,14 +114,14 @@
final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
return mConfiguration.equals(that.mConfiguration)
&& mDisplayId == that.mDisplayId
- && mVisibleRequested == that.mVisibleRequested;
+ && mVisible == that.mVisible;
}
@Override
public int hashCode() {
int result = mConfiguration.hashCode();
result = 31 * result + mDisplayId;
- result = 31 * result + (mVisibleRequested ? 1 : 0);
+ result = 31 * result + (mVisible ? 1 : 0);
return result;
}
@@ -129,13 +129,13 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
dest.writeInt(mDisplayId);
- dest.writeBoolean(mVisibleRequested);
+ dest.writeBoolean(mVisible);
}
private TaskFragmentParentInfo(Parcel in) {
mConfiguration.readFromParcel(in);
mDisplayId = in.readInt();
- mVisibleRequested = in.readBoolean();
+ mVisible = in.readBoolean();
}
public static final Creator<TaskFragmentParentInfo> CREATOR =
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 641d1a1..fbdd325 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -132,8 +132,11 @@
*/
public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+ /** This change happened underneath something else. */
+ public static final int FLAG_IS_OCCLUDED = 1 << 15;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 15;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 16;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -153,6 +156,7 @@
FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
FLAG_IS_BEHIND_STARTING_WINDOW,
+ FLAG_IS_OCCLUDED,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -362,6 +366,9 @@
if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
}
+ if ((flags & FLAG_IS_OCCLUDED) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 4ea5ea5..2a80d02 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -84,9 +84,8 @@
}
/**
- * Start a transition.
+ * Starts a new transition, don't use this to start an already created one.
* @param type The type of the transition. This is ignored if a transitionToken is provided.
- * @param transitionToken An existing transition to start. If null, a new transition is created.
* @param t The set of window operations that are part of this transition.
* @return A token identifying the transition. This will be the same as transitionToken if it
* was provided.
@@ -94,10 +93,24 @@
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@NonNull
- public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+ try {
+ return getWindowOrganizerController().startNewTransition(type, t);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts an already created transition.
+ * @param transitionToken An existing transition to start.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ public void startTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
try {
- return getWindowOrganizerController().startTransition(type, transitionToken, t);
+ getWindowOrganizerController().startTransition(transitionToken, t);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1ec5325..4f74ca7 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -86,7 +86,6 @@
private final ChooserActivityLogger mChooserActivityLogger;
private int mNumShortcutResults = 0;
- private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
private boolean mApplySharingAppLimits;
// Reserve spots for incoming direct share targets by adding placeholders
@@ -265,31 +264,20 @@
return;
}
- if (!(info instanceof DisplayResolveInfo)) {
- holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
- holder.bindIcon(info);
-
- if (info instanceof SelectableTargetInfo) {
- // direct share targets should append the application name for a better readout
- DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
- CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
- CharSequence extendedInfo = info.getExtendedInfo();
- String contentDescription = String.join(" ", info.getDisplayLabel(),
- extendedInfo != null ? extendedInfo : "", appName);
- holder.updateContentDescription(contentDescription);
- }
- } else {
+ holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
+ holder.bindIcon(info);
+ if (info instanceof SelectableTargetInfo) {
+ // direct share targets should append the application name for a better readout
+ DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
+ CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+ CharSequence extendedInfo = info.getExtendedInfo();
+ String contentDescription = String.join(" ", info.getDisplayLabel(),
+ extendedInfo != null ? extendedInfo : "", appName);
+ holder.updateContentDescription(contentDescription);
+ } else if (info instanceof DisplayResolveInfo) {
DisplayResolveInfo dri = (DisplayResolveInfo) info;
- holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel());
- LoadIconTask task = mIconLoaders.get(dri);
- if (task == null) {
- task = new LoadIconTask(dri, holder);
- mIconLoaders.put(dri, task);
- task.execute();
- } else {
- // The holder was potentially changed as the underlying items were
- // reshuffled, so reset the target holder
- task.setViewHolder(holder);
+ if (!dri.hasDisplayIcon()) {
+ loadIcon(dri);
}
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0e1ed7b..c70e26f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -55,6 +55,7 @@
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -1475,14 +1476,21 @@
mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
- ResolverListAdapter inactiveAdapter = mMultiProfilePagerAdapter.getInactiveListAdapter();
- DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
+ final ResolverListAdapter inactiveAdapter =
+ mMultiProfilePagerAdapter.getInactiveListAdapter();
+ final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
// Load the icon asynchronously
ImageView icon = findViewById(R.id.icon);
- ResolverListAdapter.LoadIconTask iconTask = inactiveAdapter.new LoadIconTask(
- otherProfileResolveInfo, new ResolverListAdapter.ViewHolder(icon));
- iconTask.execute();
+ inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) {
+ @Override
+ protected void onPostExecute(Drawable drawable) {
+ if (!isDestroyed()) {
+ otherProfileResolveInfo.setDisplayIcon(drawable);
+ new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
+ }
+ }
+ }.execute();
((TextView) findViewById(R.id.open_cross_profile)).setText(
getResources().getString(
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 66fff5c..f6075b0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -58,7 +58,10 @@
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ResolverListAdapter extends BaseAdapter {
private static final String TAG = "ResolverListAdapter";
@@ -87,6 +90,8 @@
private Runnable mPostListReadyRunnable;
private final boolean mIsAudioCaptureDevice;
private boolean mIsTabLoaded;
+ private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+ private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>();
public ResolverListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList,
@@ -636,26 +641,47 @@
if (info == null) {
holder.icon.setImageDrawable(
mContext.getDrawable(R.drawable.resolver_icon_placeholder));
+ holder.bindLabel("", "", false);
return;
}
- if (info instanceof DisplayResolveInfo
- && !((DisplayResolveInfo) info).hasDisplayLabel()) {
- getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
- } else {
- holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
- }
-
- if (info instanceof DisplayResolveInfo
- && !((DisplayResolveInfo) info).hasDisplayIcon()) {
- new LoadIconTask((DisplayResolveInfo) info, holder).execute();
- } else {
+ if (info instanceof DisplayResolveInfo) {
+ DisplayResolveInfo dri = (DisplayResolveInfo) info;
+ boolean hasLabel = dri.hasDisplayLabel();
+ holder.bindLabel(
+ dri.getDisplayLabel(),
+ dri.getExtendedInfo(),
+ hasLabel && alwaysShowSubLabel());
holder.bindIcon(info);
+ if (!hasLabel) {
+ loadLabel(dri);
+ }
+ if (!dri.hasDisplayIcon()) {
+ loadIcon(dri);
+ }
}
}
- protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
- return new LoadLabelTask(info, holder);
+ protected final void loadIcon(DisplayResolveInfo info) {
+ LoadIconTask task = mIconLoaders.get(info);
+ if (task == null) {
+ task = new LoadIconTask((DisplayResolveInfo) info);
+ mIconLoaders.put(info, task);
+ task.execute();
+ }
+ }
+
+ private void loadLabel(DisplayResolveInfo info) {
+ LoadLabelTask task = mLabelLoaders.get(info);
+ if (task == null) {
+ task = createLoadLabelTask(info);
+ mLabelLoaders.put(info, task);
+ task.execute();
+ }
+ }
+
+ protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+ return new LoadLabelTask(info);
}
public void onDestroy() {
@@ -666,6 +692,16 @@
if (mResolverListController != null) {
mResolverListController.destroy();
}
+ cancelTasks(mIconLoaders.values());
+ cancelTasks(mLabelLoaders.values());
+ mIconLoaders.clear();
+ mLabelLoaders.clear();
+ }
+
+ private <T extends AsyncTask> void cancelTasks(Collection<T> tasks) {
+ for (T task: tasks) {
+ task.cancel(false);
+ }
}
private static ColorMatrixColorFilter getSuspendedColorMatrix() {
@@ -883,11 +919,9 @@
protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
private final DisplayResolveInfo mDisplayResolveInfo;
- private final ViewHolder mHolder;
- protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
+ protected LoadLabelTask(DisplayResolveInfo dri) {
mDisplayResolveInfo = dri;
- mHolder = holder;
}
@Override
@@ -925,21 +959,22 @@
@Override
protected void onPostExecute(CharSequence[] result) {
+ if (mDisplayResolveInfo.hasDisplayLabel()) {
+ return;
+ }
mDisplayResolveInfo.setDisplayLabel(result[0]);
mDisplayResolveInfo.setExtendedInfo(result[1]);
- mHolder.bindLabel(result[0], result[1], alwaysShowSubLabel());
+ notifyDataSetChanged();
}
}
class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
protected final DisplayResolveInfo mDisplayResolveInfo;
private final ResolveInfo mResolveInfo;
- private ViewHolder mHolder;
- LoadIconTask(DisplayResolveInfo dri, ViewHolder holder) {
+ LoadIconTask(DisplayResolveInfo dri) {
mDisplayResolveInfo = dri;
mResolveInfo = dri.getResolveInfo();
- mHolder = holder;
}
@Override
@@ -953,17 +988,9 @@
mResolverListCommunicator.updateProfileViewButton();
} else if (!mDisplayResolveInfo.hasDisplayIcon()) {
mDisplayResolveInfo.setDisplayIcon(d);
- mHolder.bindIcon(mDisplayResolveInfo);
- // Notify in case view is already bound to resolve the race conditions on
- // low end devices
notifyDataSetChanged();
}
}
-
- public void setViewHolder(ViewHolder holder) {
- mHolder = holder;
- mHolder.bindIcon(mDisplayResolveInfo);
- }
}
/**
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 5f4a9cd..473134e 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -172,14 +172,14 @@
@Override
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
}
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
+ TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
@@ -224,13 +224,6 @@
}
};
- private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
- final int currentUserId = UserHandle.myUserId();
- if (targetUserId != currentUserId) {
- intent.fixUris(currentUserId);
- }
- }
-
private DisplayResolveInfo(Parcel in) {
mDisplayLabel = in.readCharSequence();
mExtendedInfo = in.readCharSequence();
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 264e4f7..4b9b7cb 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -232,6 +232,7 @@
}
intent.setComponent(mChooserTarget.getComponentName());
intent.putExtras(mChooserTarget.getIntentExtras());
+ TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId);
// Important: we will ignore the target security checks in ActivityManager
// if and only if the ChooserTarget's target package is the same package
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
index f56ab17..7bb7ddc 100644
--- a/core/java/com/android/internal/app/chooser/TargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -130,4 +130,15 @@
* @return true if this target should be pinned to the front by the request of the user
*/
boolean isPinned();
+
+ /**
+ * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called
+ * before launching the intent as another user.
+ */
+ static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
+ final int currentUserId = UserHandle.myUserId();
+ if (targetUserId != currentUserId) {
+ intent.fixUris(currentUserId);
+ }
+ }
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 62c7966..d503904 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -508,6 +508,5 @@
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
mIsObserving = true;
}
-
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index cc7150b..6814f87 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -167,7 +167,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- public static final int VERSION = 209;
+ public static final int VERSION = 210;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -6167,12 +6167,13 @@
@UnsupportedAppUsage
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event) {
noteUserActivityLocked(uid, event, mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+ long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -9995,14 +9996,14 @@
}
@Override
- public void noteUserActivityLocked(int type) {
+ public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[type].stepAtomic();
+ if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[event].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ Slog.w(TAG, "Unknown user activity type " + event + " was specified.",
new Throwable());
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a4da8de4..1235b60 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1522,8 +1522,7 @@
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
- SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1576,12 +1575,6 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
- * Some authentication is required because the trustagent either timed out or was disabled
- * manually.
- */
- public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
-
- /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/proto/android/os/processstarttime.proto b/core/proto/android/os/processstarttime.proto
deleted file mode 100644
index d0f8bae..0000000
--- a/core/proto/android/os/processstarttime.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.
- */
-
-syntax = "proto2";
-package android.os;
-
-option java_multiple_files = true;
-
-// This message is used for statsd logging and should be kept in sync with
-// frameworks/proto_logging/stats/atoms.proto
-/**
- * Logs information about process start time.
- *
- * Logged from:
- * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message ProcessStartTime {
- // The uid of the ProcessRecord.
- optional int32 uid = 1;
-
- // The process pid.
- optional int32 pid = 2;
-
- // The process name.
- // Usually package name, "system" for system server.
- // Provided by ActivityManagerService.
- optional string process_name = 3;
-
- enum StartType {
- UNKNOWN = 0;
- WARM = 1;
- HOT = 2;
- COLD = 3;
- }
-
- // The start type.
- optional StartType type = 4;
-
- // The elapsed realtime at the start of the process.
- optional int64 process_start_time_millis = 5;
-
- // Number of milliseconds it takes to reach bind application.
- optional int32 bind_application_delay_millis = 6;
-
- // Number of milliseconds it takes to finish start of the process.
- optional int32 process_start_delay_millis = 7;
-
- // hostingType field in ProcessRecord, the component type such as "activity",
- // "service", "content provider", "broadcast" or other strings.
- optional string hosting_type = 8;
-
- // hostingNameStr field in ProcessRecord. The component class name that runs
- // in this process.
- optional string hosting_name = 9;
-
- // Broadcast action name.
- optional string broadcast_action_name = 10;
-
- enum HostingTypeId {
- HOSTING_TYPE_UNKNOWN = 0;
- HOSTING_TYPE_ACTIVITY = 1;
- HOSTING_TYPE_ADDED_APPLICATION = 2;
- HOSTING_TYPE_BACKUP = 3;
- HOSTING_TYPE_BROADCAST = 4;
- HOSTING_TYPE_CONTENT_PROVIDER = 5;
- HOSTING_TYPE_LINK_FAIL = 6;
- HOSTING_TYPE_ON_HOLD = 7;
- HOSTING_TYPE_NEXT_ACTIVITY = 8;
- HOSTING_TYPE_NEXT_TOP_ACTIVITY = 9;
- HOSTING_TYPE_RESTART = 10;
- HOSTING_TYPE_SERVICE = 11;
- HOSTING_TYPE_SYSTEM = 12;
- HOSTING_TYPE_TOP_ACTIVITY = 13;
- HOSTING_TYPE_EMPTY = 14;
- }
-
- optional HostingTypeId hosting_type_id = 11;
-}
-
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
new file mode 100644
index 0000000..e3b3ea7
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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 android.hardware.input
+
+import android.hardware.BatteryState
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import com.android.server.testutils.any
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager.InputDeviceBatteryListener].
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:InputDeviceBatteryListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class InputDeviceBatteryListenerTest {
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ private lateinit var testLooper: TestLooper
+ private var registeredListener: IInputDeviceBatteryListener? = null
+ private val monitoredDevices = mutableListOf<Int>()
+ private lateinit var executor: Executor
+ private lateinit var inputManager: InputManager
+
+ @Mock
+ private lateinit var iInputManagerMock: IInputManager
+
+ @Before
+ fun setUp() {
+ testLooper = TestLooper()
+ executor = HandlerExecutor(Handler(testLooper.looper))
+ registeredListener = null
+ monitoredDevices.clear()
+ inputManager = InputManager.resetInstance(iInputManagerMock)
+
+ // Handle battery listener registration.
+ doAnswer {
+ val deviceId = it.getArgument(0) as Int
+ val listener = it.getArgument(1) as IInputDeviceBatteryListener
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered battery listener per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ if (monitoredDevices.contains(deviceId)) {
+ fail("Trying to start monitoring a device that was already being monitored")
+ }
+ monitoredDevices.add(deviceId)
+ registeredListener = listener
+ null
+ }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
+
+ // Handle battery listener being unregistered.
+ doAnswer {
+ val deviceId = it.getArgument(0) as Int
+ val listener = it.getArgument(1) as IInputDeviceBatteryListener
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ if (!monitoredDevices.remove(deviceId)) {
+ fail("Trying to stop monitoring a device that is not being monitored")
+ }
+ if (monitoredDevices.isEmpty()) {
+ registeredListener = null
+ }
+ }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
+ }
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ private fun notifyBatteryStateChanged(
+ deviceId: Int,
+ isPresent: Boolean = true,
+ status: Int = BatteryState.STATUS_FULL,
+ capacity: Float = 1.0f,
+ eventTime: Long = 12345L
+ ) {
+ registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
+ }
+
+ @Test
+ fun testListenerIsNotifiedCorrectly() {
+ var callbackCount = 0
+
+ // Add a battery listener to monitor battery changes.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
+ deviceId: Int, eventTime: Long, batteryState: BatteryState ->
+ callbackCount++
+ assertEquals(1, deviceId)
+ assertEquals(true, batteryState.isPresent)
+ assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
+ assertEquals(0.5f, batteryState.capacity)
+ assertEquals(8675309L, eventTime)
+ }
+
+ // Adding the listener should register the callback with InputManagerService.
+ assertNotNull(registeredListener)
+ assertTrue(monitoredDevices.contains(1))
+
+ // Notifying battery change for a different device should not trigger the listener.
+ notifyBatteryStateChanged(deviceId = 2)
+ testLooper.dispatchAll()
+ assertEquals(0, callbackCount)
+
+ // Notifying battery change for the registered device will notify the listener.
+ notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
+ BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testMultipleListeners() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+ val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+ // Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
+ // while the second callback monitors devices 2 and 3.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+ assertEquals(1, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
+ assertEquals(2, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
+ assertEquals(3, monitoredDevices.size)
+ inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
+ assertEquals(3, monitoredDevices.size)
+
+ // Notifying battery change for each of the devices should trigger the registered callbacks.
+ notifyBatteryStateChanged(deviceId = 1)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+ assertEquals(0, callbackCount2)
+
+ notifyBatteryStateChanged(deviceId = 2)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ notifyBatteryStateChanged(deviceId = 3)
+ testLooper.dispatchNext()
+ testLooper.dispatchNext()
+ assertEquals(2, callbackCount1)
+ assertEquals(2, callbackCount2)
+
+ // Stop monitoring devices 1 and 2.
+ inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
+ assertEquals(2, monitoredDevices.size)
+ inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
+ assertEquals(1, monitoredDevices.size)
+
+ // Ensure device 3 continues to be monitored.
+ notifyBatteryStateChanged(deviceId = 3)
+ testLooper.dispatchNext()
+ testLooper.dispatchNext()
+ assertEquals(3, callbackCount1)
+ assertEquals(3, callbackCount2)
+
+ // Stop monitoring all devices.
+ inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
+ assertEquals(1, monitoredDevices.size)
+ inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
+ assertEquals(0, monitoredDevices.size)
+ }
+
+ @Test
+ fun testAdditionalListenersNotifiedImmediately() {
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
+ val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
+
+ // Add a battery listener and send the latest battery state.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
+ assertEquals(1, monitoredDevices.size)
+ notifyBatteryStateChanged(deviceId = 1)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount1)
+
+ // Add a second listener for the same device that already has the latest battery state.
+ inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
+ assertEquals(1, monitoredDevices.size)
+
+ // Ensure that this listener is notified immediately.
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount2)
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
index 56a7070..2861428 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
@@ -46,14 +46,14 @@
}
@Override
- protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
- return new LoadLabelWrapperTask(info, holder);
+ protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+ return new LoadLabelWrapperTask(info);
}
class LoadLabelWrapperTask extends LoadLabelTask {
- protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
- super(dri, holder);
+ protected LoadLabelWrapperTask(DisplayResolveInfo dri) {
+ super(dri);
}
@Override
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index fd4fb13..2719431 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,7 +17,6 @@
package com.android.internal.util;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
-import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -85,12 +84,6 @@
}
@Test
- public void testSelectedRegionScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
- }
-
- @Test
public void testProvidedImageScreenshot() {
mScreenshotHelper.provideScreenshot(
new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 203ece0..1174b68 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -240,6 +240,16 @@
}
/**
+ * Clears the listener set in {@link SplitController#setSplitInfoListener}.
+ */
+ @Override
+ public void clearSplitInfoCallback() {
+ synchronized (mLock) {
+ mEmbeddingCallback = null;
+ }
+ }
+
+ /**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 91573ff..00943f2d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -140,7 +140,7 @@
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId();
- mIsVisible = info.isVisibleRequested();
+ mIsVisible = info.isVisible();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c76f568..0fb6ff8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -103,14 +103,23 @@
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
* as a parameter.
+ *
+ * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
+ * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
+ * together. However only the first registered consumer of a {@link Context} will actually
+ * invoke {@link #addWindowLayoutInfoListener(Context, Consumer)}.
+ * Here we enforce that {@link #addWindowLayoutInfoListener(Context, Consumer)} can only be
+ * called once for each {@link Context}.
*/
- // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+ @Override
public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
@NonNull Consumer<WindowLayoutInfo> consumer) {
if (mWindowLayoutChangeListeners.containsKey(context)
+ // In theory this method can be called on the same consumer with different context.
|| mWindowLayoutChangeListeners.containsValue(consumer)) {
- // Early return if the listener or consumer has been registered.
- return;
+ throw new IllegalArgumentException(
+ "Context or Consumer has already been registered for WindowLayoutInfo"
+ + " callback.");
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 2c766d8..4978e04 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec..82573b2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,7 @@
srcs: [
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
],
path: "src",
}
@@ -100,6 +101,21 @@
out: ["wm_shell_protolog.json"],
}
+genrule {
+ name: "protolog.json.gz",
+ srcs: [":generate-wm_shell_protolog.json"],
+ out: ["wmshell.protolog.json.gz"],
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
+}
+
+prebuilt_etc {
+ name: "wmshell.protolog.json.gz",
+ system_ext_specific: true,
+ src: ":protolog.json.gz",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -123,9 +139,6 @@
resource_dirs: [
"res",
],
- java_resources: [
- ":generate-wm_shell_protolog.json",
- ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
new file mode 100644
index 0000000..66e5b43
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ >
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/>
+
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
index 8207365..53a8bb1 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -15,8 +15,6 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
- android:tintMode="multiply"
- android:tint="@color/decor_caption_title_color"
xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorPrimary" />
+ <solid android:color="@android:color/white" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
index f2f1a1d..851cbf2 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -18,15 +18,13 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
- android:viewportHeight="32.0"
- android:tint="@color/decor_button_dark_color"
- >
+ android:viewportHeight="32.0">
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="8.0"
android:translateY="8.0" >
<path
- android:fillColor="@android:color/white"
- android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ android:fillColor="@android:color/black"
+ android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
new file mode 100644
index 0000000..ee0f466
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index d183e42..38cd570 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -17,39 +17,33 @@
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/caption"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="end"
+ android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
- android:id="@+id/minimize_window"
- android:visibility="gone"
+ android:id="@+id/back_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="top|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_minimize_button_dark"
- android:duplicateParentState="true"/>
+ android:contentDescription="@string/back_button_text"
+ android:background="@drawable/decor_back_button_dark"
+ />
<Button
- android:id="@+id/maximize_window"
- android:layout_width="32dp"
+ android:id="@+id/caption_handle"
+ android:layout_width="128dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="center_vertical|end"
- android:contentDescription="@string/maximize_button_text"
- android:background="@drawable/decor_maximize_button_dark"
- android:duplicateParentState="true"/>
+ android:contentDescription="@string/handle_text"
+ android:background="@drawable/decor_handle_dark"/>
<Button
android:id="@+id/close_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
- android:layout_gravity="center_vertical|end"
android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"
- android:duplicateParentState="true"/>
-</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
+ android:background="@drawable/decor_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 1d1162d..d8a5074 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -193,4 +193,8 @@
<string name="minimize_button_text">Minimize</string>
<!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
<string name="close_button_text">Close</string>
+ <!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
+ <string name="back_button_text">Back</string>
+ <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
+ <string name="handle_text">Handle</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b..8cbe44b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@
public interface BackAnimation {
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 33ecdd8..938189f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,10 +58,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -105,6 +108,7 @@
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
+ private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@Nullable
@@ -231,21 +235,25 @@
public BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ this(shellInit, shellController, shellExecutor, backgroundHandler,
+ new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
+ context, context.getContentResolver());
}
@VisibleForTesting
BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
+ mShellController = shellController;
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
@@ -257,6 +265,8 @@
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ this::createExternalInterface, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -289,7 +299,11 @@
return mBackAnimation;
}
- private final BackAnimation mBackAnimation = new BackAnimationImpl();
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IBackAnimationImpl(this);
+ }
+
+ private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
public Context getContext() {
@@ -302,17 +316,6 @@
}
private class BackAnimationImpl implements BackAnimation {
- private IBackAnimationImpl mBackAnimation;
-
- @Override
- public IBackAnimation createExternalInterface() {
- if (mBackAnimation != null) {
- mBackAnimation.invalidate();
- }
- mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
- return mBackAnimation;
- }
-
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -331,7 +334,8 @@
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private static class IBackAnimationImpl extends IBackAnimation.Stub
+ implements ExternalInterfaceBinder {
private BackAnimationController mController;
IBackAnimationImpl(BackAnimationController controller) {
@@ -356,7 +360,8 @@
(controller) -> controller.onBackToLauncherAnimationFinished());
}
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
new file mode 100644
index 0000000..e029358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DockStateReader.java
@@ -0,0 +1,57 @@
+/*
+ * 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.common;
+
+import static android.content.Intent.EXTRA_DOCK_STATE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Provides information about the docked state of the device.
+ */
+@WMSingleton
+public class DockStateReader {
+
+ private static final IntentFilter DOCK_INTENT_FILTER = new IntentFilter(
+ Intent.ACTION_DOCK_EVENT);
+
+ private final Context mContext;
+
+ @Inject
+ public DockStateReader(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * @return True if the device is docked and false otherwise.
+ */
+ public boolean isDocked() {
+ Intent dockStatus = mContext.registerReceiver(/* receiver */ null, DOCK_INTENT_FILTER);
+ if (dockStatus != null) {
+ int dockState = dockStatus.getIntExtra(EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ return dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
new file mode 100644
index 0000000..aa5b0cb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.common;
+
+import android.os.IBinder;
+
+/**
+ * An interface for binders which can be registered to be sent to other processes.
+ */
+public interface ExternalInterfaceBinder {
+ /**
+ * Invalidates this binder (detaches it from the controller it would call).
+ */
+ void invalidate();
+
+ /**
+ * Returns the IBinder to send.
+ */
+ IBinder asBinder();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 235fd9c..6627de5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -37,6 +37,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
@@ -109,6 +110,7 @@
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
+ private final DockStateReader mDockStateReader;
private CompatUICallback mCallback;
@@ -127,7 +129,8 @@
DisplayImeController imeController,
SyncTransactionQueue syncQueue,
ShellExecutor mainExecutor,
- Lazy<Transitions> transitionsLazy) {
+ Lazy<Transitions> transitionsLazy,
+ DockStateReader dockStateReader) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -138,6 +141,7 @@
mTransitionsLazy = transitionsLazy;
mCompatUIHintsState = new CompatUIHintsState();
shellInit.addInitCallback(this::onInit, this);
+ mDockStateReader = dockStateReader;
}
private void onInit() {
@@ -315,7 +319,8 @@
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
mTransitionsLazy.get(),
- this::onLetterboxEduDismissed);
+ this::onLetterboxEduDismissed,
+ mDockStateReader);
}
private void onLetterboxEduDismissed() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 35f1038..867d0ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -34,6 +34,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
import com.android.wm.shell.transition.Transitions;
@@ -88,19 +89,21 @@
*/
private final int mDialogVerticalMargin;
+ private final DockStateReader mDockStateReader;
+
public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions,
- Runnable onDismissCallback) {
+ Runnable onDismissCallback, DockStateReader dockStateReader) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
- onDismissCallback, new LetterboxEduAnimationController(context));
+ onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader);
}
@VisibleForTesting
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
- LetterboxEduAnimationController animationController) {
+ LetterboxEduAnimationController animationController, DockStateReader dockStateReader) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
@@ -111,6 +114,7 @@
Context.MODE_PRIVATE);
mDialogVerticalMargin = (int) mContext.getResources().getDimension(
R.dimen.letterbox_education_dialog_margin);
+ mDockStateReader = dockStateReader;
}
@Override
@@ -130,13 +134,15 @@
@Override
protected boolean eligibleToShowLayout() {
+ // - The letterbox education should not be visible if the device is docked.
// - If taskbar education is showing, the letterbox education shouldn't be shown for the
// given task until the taskbar education is dismissed and the compat info changes (then
// the controller will create a new instance of this class since this one isn't eligible).
// - If the layout isn't null then it was previously showing, and we shouldn't check if the
// user has seen the letterbox education before.
- return mEligibleForLetterboxEducation && !isTaskbarEduShowing() && (mLayout != null
- || !getHasSeenLetterboxEducation());
+ return mEligibleForLetterboxEducation && !isTaskbarEduShowing()
+ && (mLayout != null || !getHasSeenLetterboxEducation())
+ && !mDockStateReader.isDocked();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c25bbbf..28a1959 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -46,6 +46,7 @@
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -69,7 +70,6 @@
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -192,33 +192,16 @@
@WMSingleton
@Provides
- static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
- Context context,
- ShellInit shellInit,
- ShellCommandHandler shellCommandHandler,
- SyncTransactionQueue syncTransactionQueue,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler
- ) {
- return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
- syncTransactionQueue, displayController, displayInsetsController,
- unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
- }
-
- @WMSingleton
- @Provides
static CompatUIController provideCompatUIController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
- @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
+ @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
+ DockStateReader dockStateReader) {
return new CompatUIController(context, shellInit, shellController, displayController,
- displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
+ displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
+ dockStateReader);
}
@WMSingleton
@@ -278,13 +261,14 @@
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
- context));
+ new BackAnimationController(shellInit, shellController, shellExecutor,
+ backgroundHandler, context));
}
return Optional.empty();
}
@@ -309,17 +293,17 @@
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
+ abstract FullscreenTaskListener optionalFullscreenTaskListener();
@WMSingleton
@Provides
- static FullscreenTaskListener<?> provideFullscreenTaskListener(
- @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
+ static FullscreenTaskListener provideFullscreenTaskListener(
+ @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
@@ -333,7 +317,7 @@
//
@BindsOptionalOf
- abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+ abstract WindowDecorViewModel optionalWindowDecorViewModel();
//
// Unfold transition
@@ -488,6 +472,7 @@
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -495,9 +480,9 @@
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellController,
+ shellCommandHandler, taskStackListener, activityTaskManager,
+ desktopModeTaskRepository, mainExecutor));
}
//
@@ -514,14 +499,15 @@
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor);
+ return new Transitions(context, shellInit, shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor);
}
@WMSingleton
@@ -638,13 +624,15 @@
@WMSingleton
@Provides
- static StartingWindowController provideStartingWindowController(Context context,
+ static StartingWindowController provideStartingWindowController(
+ Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
@@ -781,12 +769,11 @@
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener<?> fullscreenTaskListener,
+ FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 37a50b6..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -55,7 +55,7 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -182,7 +182,7 @@
@WMSingleton
@Provides
- static WindowDecorViewModel<?> provideWindowDecorViewModel(
+ static WindowDecorViewModel provideWindowDecorViewModel(
Context context,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
@@ -191,13 +191,13 @@
SyncTransactionQueue syncQueue,
@DynamicOverride DesktopModeController desktopModeController) {
return new CaptionWindowDecorViewModel(
- context,
- mainHandler,
- mainChoreographer,
- taskOrganizer,
- displayController,
- syncQueue,
- desktopModeController);
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController);
}
//
@@ -208,7 +208,7 @@
@Provides
@DynamicOverride
static FreeformComponents provideFreeformComponents(
- FreeformTaskListener<?> taskListener,
+ FreeformTaskListener taskListener,
FreeformTaskTransitionHandler transitionHandler,
FreeformTaskTransitionObserver transitionObserver) {
return new FreeformComponents(
@@ -217,18 +217,18 @@
@WMSingleton
@Provides
- static FreeformTaskListener<?> provideFreeformTaskListener(
+ static FreeformTaskListener provideFreeformTaskListener(
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
ShellInit init = FreeformComponents.isFreeformEnabled(context)
? shellInit
: null;
- return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository,
+ return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository,
windowDecorViewModel);
}
@@ -237,7 +237,7 @@
static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
}
@@ -247,10 +247,9 @@
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
return new FreeformTaskTransitionObserver(
- context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
+ context, shellInit, transitions, windowDecorViewModel);
}
//
@@ -599,7 +598,9 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+ static DesktopModeController provideDesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -607,7 +608,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
@@ -620,6 +621,28 @@
}
//
+ // Kids mode
+ //
+ @WMSingleton
+ @Provides
+ static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
+ Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ SyncTransactionQueue syncTransactionQueue,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler
+ ) {
+ return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+ syncTransactionQueue, displayController, displayInsetsController,
+ unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
+ }
+
+ //
// Misc
//
@@ -630,6 +653,7 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DefaultMixedHandler defaultMixedHandler,
+ KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<DesktopModeController> desktopModeController) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38..44a467f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -23,9 +23,4 @@
*/
@ExternalThread
public interface DesktopMode {
-
- /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
- default IDesktopMode createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 99739c4..b96facf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -29,23 +31,30 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -55,18 +64,22 @@
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+ Transitions.TransitionHandler {
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
- private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
- public DesktopModeController(Context context, ShellInit shellInit,
+ public DesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -74,6 +87,7 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTransitions = transitions;
@@ -85,10 +99,13 @@
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+ this::createExternalInterface, this);
mSettingsObserver.observe();
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
+ mTransitions.addHandler(this);
}
@Override
@@ -108,6 +125,13 @@
return mDesktopModeImpl;
}
+ /**
+ * Creates a new instance of the external interface to pass to another process.
+ */
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDesktopModeImpl(this);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -157,7 +181,7 @@
/**
* Show apps on desktop
*/
- public void showDesktopApps() {
+ WindowContainerTransaction showDesktopApps() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -173,7 +197,12 @@
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- mShellTaskOrganizer.applyTransaction(wct);
+
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+
+ return wct;
}
/**
@@ -195,6 +224,35 @@
.configuration.windowConfiguration.getWindowingMode();
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+
+ // Only do anything if we are in desktop mode and opening a task/app
+ if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+ return null;
+ }
+
+ WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ wct.merge(showDesktopApps(), true /* transfer */);
+ wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+ return wct;
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -235,24 +293,15 @@
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
-
- private IDesktopModeImpl mIDesktopMode;
-
- @Override
- public IDesktopMode createExternalInterface() {
- if (mIDesktopMode != null) {
- mIDesktopMode.invalidate();
- }
- mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
- return mIDesktopMode;
- }
+ // Do nothing
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub {
+ private static class IDesktopModeImpl extends IDesktopMode.Stub
+ implements ExternalInterfaceBinder {
private DesktopModeController mController;
@@ -263,7 +312,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d..fbf326e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@
### SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+ controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
### Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+ extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+ references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+ the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+ and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+ call the SystemUIProxy method as needed in that code
+
### Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@
### General Do's & Dont's
Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
index 9356660..f86d467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -33,9 +33,4 @@
* - If there is a floating task for this intent, and it's not stashed, this stashes it.
*/
void showOrSetStashed(Intent intent);
-
- /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
- default IFloatingTasks createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
index 6755299..b3c09d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
import android.annotation.Nullable;
import android.content.Context;
@@ -40,6 +41,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -136,11 +138,13 @@
if (isFloatingTasksEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
- mShellCommandHandler.addDumpCallback(this::dump, this);
}
protected void onInit() {
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+ this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/** Only used for testing. */
@@ -168,6 +172,10 @@
return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IFloatingTasksImpl(this);
+ }
+
@Override
public void onThemeChanged() {
if (mIsFloatingLayerAdded) {
@@ -412,28 +420,18 @@
*/
@ExternalThread
private class FloatingTaskImpl implements FloatingTasks {
- private IFloatingTasksImpl mIFloatingTasks;
-
@Override
public void showOrSetStashed(Intent intent) {
mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
}
-
- @Override
- public IFloatingTasks createExternalInterface() {
- if (mIFloatingTasks != null) {
- mIFloatingTasks.invalidate();
- }
- mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
- return mIFloatingTasks;
- }
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
+ private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+ implements ExternalInterfaceBinder {
private FloatingTasksController mController;
IFloatingTasksImpl(FloatingTasksController controller) {
@@ -443,7 +441,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index e2d5a49..f82a346 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -19,12 +19,8 @@
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -41,31 +37,26 @@
/**
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
- *
- * @param <T> the type of window decoration instance
*/
-public class FreeformTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
- private final WindowDecorViewModel<T> mWindowDecorationViewModel;
+ private final WindowDecorViewModel mWindowDecorationViewModel;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
public FreeformTaskListener(
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
- WindowDecorViewModel<T> windowDecorationViewModel) {
+ WindowDecorViewModel windowDecorationViewModel) {
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -80,13 +71,18 @@
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mTasks.get(taskInfo.taskId) != null) {
+ throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+ final State state = new State();
+ state.mTaskInfo = taskInfo;
+ state.mLeash = leash;
+ mTasks.put(taskInfo.taskId, state);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
t.apply();
}
@@ -97,28 +93,8 @@
}
}
- private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
- }
-
- state = new State<>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
- }
-
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
@@ -129,26 +105,18 @@
mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
}
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
}
- releaseWindowDecor(state.mWindowDecoration);
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = updateTaskInfo(taskInfo);
+ final State state = mTasks.get(taskInfo.taskId);
+ state.mTaskInfo = taskInfo;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
- }
+ mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
if (DesktopModeStatus.IS_SUPPORTED) {
if (taskInfo.isVisible) {
@@ -159,15 +127,6 @@
}
}
- private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
- }
- state.mTaskInfo = taskInfo;
- return state;
- }
-
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
@@ -186,103 +145,6 @@
return mTasks.get(taskId).mLeash;
}
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if it creates the window decoration; {@code false} otherwise
- */
- boolean createWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (state.mWindowDecoration != null) {
- return false;
- }
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return true;
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- T giveWindowDecoration(
- RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (windowDecor == null) {
- return null;
- }
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- return windowDecor;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorationViewModel.setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- return false;
- }
- }
-
- void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- private void releaseWindowDecor(T windowDecor) {
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
- }
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index fd4c85fa..04fc79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -46,14 +46,14 @@
implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
private final Transitions mTransitions;
- private final WindowDecorViewModel<?> mWindowDecorViewModel;
+ private final WindowDecorViewModel mWindowDecorViewModel;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
public FreeformTaskTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- WindowDecorViewModel<?> windowDecorViewModel) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 17d6067..f4888fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -16,13 +16,9 @@
package com.android.wm.shell.freeform;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
import android.app.ActivityManager;
import android.content.Context;
import android.os.IBinder;
-import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -31,9 +27,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,23 +43,19 @@
* be a part of transitions.
*/
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
- private static final String TAG = "FreeformTO";
-
private final Transitions mTransitions;
- private final FreeformTaskListener<?> mFreeformTaskListener;
- private final FullscreenTaskListener<?> mFullscreenTaskListener;
+ private final WindowDecorViewModel mWindowDecorViewModel;
- private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();
+ private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
+ new HashMap<>();
public FreeformTaskTransitionObserver(
Context context,
ShellInit shellInit,
Transitions transitions,
- FullscreenTaskListener<?> fullscreenTaskListener,
- FreeformTaskListener<?> freeformTaskListener) {
+ WindowDecorViewModel windowDecorViewModel) {
mTransitions = transitions;
- mFreeformTaskListener = freeformTaskListener;
- mFullscreenTaskListener = fullscreenTaskListener;
+ mWindowDecorViewModel = windowDecorViewModel;
if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -80,7 +72,7 @@
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
- final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
+ final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
@@ -110,92 +102,40 @@
onOpenTransitionReady(change, startT, finishT);
break;
case WindowManager.TRANSIT_CLOSE: {
- onCloseTransitionReady(change, windowDecors, startT, finishT);
+ taskInfoList.add(change.getTaskInfo());
+ onCloseTransitionReady(change, startT, finishT);
break;
}
case WindowManager.TRANSIT_CHANGE:
- onChangeTransitionReady(info.getType(), change, startT, finishT);
+ onChangeTransitionReady(change, startT, finishT);
break;
}
}
- if (!windowDecors.isEmpty()) {
- mTransitionToWindowDecors.put(transition, windowDecors);
- }
+ mTransitionToTaskInfo.put(transition, taskInfoList);
}
private void onOpenTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- switch (change.getTaskInfo().getWindowingMode()){
- case WINDOWING_MODE_FREEFORM:
- mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- break;
- }
+ mWindowDecorViewModel.createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onCloseTransitionReady(
TransitionInfo.Change change,
- ArrayList<AutoCloseable> windowDecors,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- final AutoCloseable windowDecor;
- switch (change.getTaskInfo().getWindowingMode()) {
- case WINDOWING_MODE_FREEFORM:
- windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- case WINDOWING_MODE_FULLSCREEN:
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
- startT, finishT);
- break;
- default:
- windowDecor = null;
- }
- if (windowDecor != null) {
- windowDecors.add(windowDecor);
- }
+ mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
private void onChangeTransitionReady(
- int type,
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- AutoCloseable windowDecor = null;
-
- boolean adopted = false;
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- windowDecor = mFreeformTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFullscreenTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
-
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- windowDecor = mFullscreenTaskListener.giveWindowDecoration(
- change.getTaskInfo(), startT, finishT);
- if (windowDecor != null) {
- adopted = mFreeformTaskListener.adoptWindowDecoration(
- change, startT, finishT, windowDecor);
- } else {
- // will return false if it already has the window decor.
- adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
- }
- }
-
- if (!adopted) {
- releaseWindowDecor(windowDecor);
- }
+ mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
@Override
@@ -203,43 +143,32 @@
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
- if (windowDecorsOfMerged == null) {
+ final List<ActivityManager.RunningTaskInfo> infoOfMerged =
+ mTransitionToTaskInfo.get(merged);
+ if (infoOfMerged == null) {
// We are adding window decorations of the merged transition to them of the playing
// transition so if there is none of them there is nothing to do.
return;
}
- mTransitionToWindowDecors.remove(merged);
+ mTransitionToTaskInfo.remove(merged);
- final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
- if (windowDecorsOfPlaying != null) {
- windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
+ final List<ActivityManager.RunningTaskInfo> infoOfPlaying =
+ mTransitionToTaskInfo.get(playing);
+ if (infoOfPlaying != null) {
+ infoOfPlaying.addAll(infoOfMerged);
} else {
- mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
+ mTransitionToTaskInfo.put(playing, infoOfMerged);
}
}
@Override
public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
- final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
- transition, Collections.emptyList());
- mTransitionToWindowDecors.remove(transition);
+ final List<ActivityManager.RunningTaskInfo> taskInfo =
+ mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
+ mTransitionToTaskInfo.remove(transition);
- for (AutoCloseable windowDecor : windowDecors) {
- releaseWindowDecor(windowDecor);
- }
- mFullscreenTaskListener.onTaskTransitionFinished();
- mFreeformTaskListener.onTaskTransitionFinished();
- }
-
- private static void releaseWindowDecor(AutoCloseable windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ for (int i = 0; i < taskInfo.size(); ++i) {
+ mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 76e296b..75a4091 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -22,13 +22,10 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Point;
-import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,23 +43,20 @@
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
* @param <T> the type of window decoration instance
*/
-public class FullscreenTaskListener<T extends AutoCloseable>
- implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+ private final SparseArray<State> mTasks = new SparseArray<>();
- private static class State<T extends AutoCloseable> {
+ private static class State {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
- T mWindowDecoration;
}
private final SyncTransactionQueue mSyncQueue;
private final Optional<RecentTasksController> mRecentTasksOptional;
- private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
+ private final Optional<WindowDecorViewModel> mWindowDecorViewModelOptional;
/**
* This constructor is used by downstream products.
*/
@@ -75,7 +69,7 @@
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional,
- Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
+ Optional<WindowDecorViewModel> windowDecorViewModelOptional) {
mShellTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mRecentTasksOptional = recentTasksOptional;
@@ -98,21 +92,21 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
- final State<T> state = new State();
+ final State state = new State();
state.mLeash = leash;
state.mTaskInfo = taskInfo;
mTasks.put(taskInfo.taskId, state);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
+ boolean createdWindowDecor = false;
if (mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- state.mWindowDecoration =
- mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
- leash, t, t);
+ createdWindowDecor = mWindowDecorViewModelOptional.get()
+ .createWindowDecoration(taskInfo, leash, t, t);
t.apply();
}
- if (state.mWindowDecoration == null) {
+ if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
@@ -127,12 +121,11 @@
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
+ final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
state.mTaskInfo = taskInfo;
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().onTaskInfoChanged(
- state.mTaskInfo, state.mWindowDecoration);
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().onTaskInfoChanged(state.mTaskInfo);
}
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -147,160 +140,13 @@
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- // This is possible if the transition happens before this method.
- return;
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Save window decorations of closing tasks so that we can hand them over to the
- // transition system if this method happens before the transition. In case where the
- // transition didn't happen, it'd be cleared when the next transition finished.
- if (state.mWindowDecoration != null) {
- mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
- }
- return;
- }
- releaseWindowDecor(state.mWindowDecoration);
- }
-
- /**
- * Creates a window decoration for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @return {@code true} if a decoration was actually created.
- */
- public boolean createWindowDecoration(TransitionInfo.Change change,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- if (!mWindowDecorViewModelOptional.isPresent()) return false;
- if (state.mWindowDecoration != null) {
- // Already has a decoration.
- return false;
- }
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- return true;
- }
- return false;
- }
-
- /**
- * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
- *
- * @param change the change of this task transition that needs to have the task layer as the
- * leash
- * @param startT the start transaction of this transition
- * @param finishT the finish transaction of this transition
- * @param windowDecor the window decoration to adopt
- * @return {@code true} if it adopts the window decoration; {@code false} otherwise
- */
- public boolean adoptWindowDecoration(
- TransitionInfo.Change change,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- @Nullable AutoCloseable windowDecor) {
- if (!mWindowDecorViewModelOptional.isPresent()) {
- return false;
- }
- final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
- state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
- windowDecor);
- if (state.mWindowDecoration != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- state.mTaskInfo, startT, finishT, state.mWindowDecoration);
- return true;
- } else {
- T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration(
- state.mTaskInfo, state.mLeash, startT, finishT);
- if (newWindowDecor != null) {
- state.mWindowDecoration = newWindowDecor;
- }
- return false;
- }
- }
-
- /**
- * Clear window decors of vanished tasks.
- */
- public void onTaskTransitionFinished() {
- if (mWindowDecorOfVanishedTasks.size() == 0) {
- return;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Clearing window decors of vanished tasks. There could be visual defects "
- + "if any of them is used later in transitions.");
- for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
- releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
- }
- mWindowDecorOfVanishedTasks.clear();
- }
-
- /**
- * Gives out the ownership of the task's window decoration. The given task is leaving (of has
- * left) this task listener. This is the transition system asking for the ownership.
- *
- * @param taskInfo the maximizing task
- * @return the window decor of the maximizing task if any
- */
- public T giveWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT) {
- T windowDecor;
- final State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- windowDecor = state.mWindowDecoration;
- state.mWindowDecoration = null;
- } else {
- windowDecor =
- mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
- }
- if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) {
- mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
- taskInfo, startT, finishT, windowDecor);
- }
-
- return windowDecor;
- }
-
- private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state != null) {
- updateTaskInfo(taskInfo);
- return state;
- }
-
- state = new State<T>();
- state.mTaskInfo = taskInfo;
- state.mLeash = leash;
- mTasks.put(taskInfo.taskId, state);
-
- return state;
- }
-
- private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
- final State<T> state = mTasks.get(taskInfo.taskId);
- state.mTaskInfo = taskInfo;
- return state;
- }
-
- private void releaseWindowDecor(T windowDecor) {
- if (windowDecor == null) {
- return;
- }
- try {
- windowDecor.close();
- } catch (Exception e) {
- Log.e(TAG, "Failed to release window decoration.", e);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+ if (mWindowDecorViewModelOptional.isPresent()) {
+ mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo);
}
}
@@ -342,6 +188,4 @@
public String toString() {
return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
}
-
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165..2ee3348 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
/**
- * Returns a binder that can be passed to an external process to manipulate OneHanded.
- */
- default IOneHanded createExternalInterface() {
- return null;
- }
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e0c4fe8..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -24,6 +24,7 @@
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -49,6 +50,7 @@
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ this::createExternalInterface, this);
}
public OneHanded asOneHanded() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IOneHandedImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -709,17 +717,6 @@
*/
@ExternalThread
private class OneHandedImpl implements OneHanded {
- private IOneHandedImpl mIOneHanded;
-
- @Override
- public IOneHanded createExternalInterface() {
- if (mIOneHanded != null) {
- mIOneHanded.invalidate();
- }
- mIOneHanded = new IOneHandedImpl(OneHandedController.this);
- return mIOneHanded;
- }
-
@Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IOneHandedImpl extends IOneHanded.Stub {
+ private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 4def15d..2624ee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -59,10 +59,15 @@
/**
* Sets listener to get pinned stack animation callbacks.
*/
- oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+ oneway void setPipAnimationListener(IPipAnimationListener listener) = 3;
/**
* Sets the shelf height and visibility.
*/
oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+
+ /**
+ * Sets the next pip animation type to be the alpha animation.
+ */
+ oneway void setPipAnimationTypeToAlpha() = 5;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index c06881a..f34d2a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface Pip {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate PIP.
- */
- default IPip createExternalInterface() {
- return null;
- }
-
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
@@ -51,15 +43,6 @@
}
/**
- * Sets both shelf visibility and its height.
- *
- * @param visible visibility of shelf.
- * @param height to specify the height for shelf.
- */
- default void setShelfHeight(boolean visible, int height) {
- }
-
- /**
* Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
*
* @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
@@ -68,14 +51,6 @@
default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
/**
- * Set the pinned stack with {@link PipAnimationController.AnimationType}
- *
- * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
- */
- default void setPinnedStackAnimationType(int animationType) {
- }
-
- /**
* Called when showing Pip menu.
*/
default void showPictureInPictureMenu() {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index af47666..30124a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -23,6 +23,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
@@ -32,6 +33,7 @@
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -67,6 +69,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -158,6 +161,10 @@
// early bail out if the keep clear areas feature is disabled
return;
}
+ if (mPipBoundsState.isStashed()) {
+ // don't move when stashed
+ return;
+ }
// if there is another animation ongoing, wait for it to finish and try again
if (mPipAnimationController.isAnimating()) {
mMainExecutor.removeCallbacks(
@@ -631,6 +638,12 @@
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
}
@Override
@@ -1039,17 +1052,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class PipImpl implements Pip {
- private IPipImpl mIPip;
-
- @Override
- public IPip createExternalInterface() {
- if (mIPip != null) {
- mIPip.invalidate();
- }
- mIPip = new IPipImpl(PipController.this);
- return mIPip;
- }
-
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1065,13 +1067,6 @@
}
@Override
- public void setShelfHeight(boolean visible, int height) {
- mMainExecutor.execute(() -> {
- PipController.this.setShelfHeight(visible, height);
- });
- }
-
- @Override
public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
PipController.this.setOnIsInPipStateChangedListener(callback);
@@ -1079,13 +1074,6 @@
}
@Override
- public void setPinnedStackAnimationType(int animationType) {
- mMainExecutor.execute(() -> {
- PipController.this.setPinnedStackAnimationType(animationType);
- });
- }
-
- @Override
public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
mMainExecutor.execute(() -> {
mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
@@ -1111,7 +1099,7 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IPipImpl extends IPip.Stub {
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
private final SingleInstanceRemoteListener<PipController,
IPipAnimationListener> mListener;
@@ -1142,7 +1130,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
@@ -1178,8 +1167,8 @@
}
@Override
- public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
- executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
(controller) -> {
if (listener != null) {
mListener.register(listener);
@@ -1188,5 +1177,13 @@
}
});
}
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {
+ executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
+ (controller) -> {
+ controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA);
+ });
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
package com.android.wm.shell.protolog;
import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.BaseProtoLogImpl;
import com.android.internal.protolog.ProtoLogViewerConfigReader;
import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
-import org.json.JSONException;
-
/**
* A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: Get the right path for the proto log file when we initialize the shell components
- private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+ // TODO: find a proper location to save the protolog message file
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+ private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
private static ShellProtoLogImpl sServiceInstance = null;
@@ -111,18 +104,8 @@
}
public int startTextLogging(String[] groups, PrintWriter pw) {
- try (InputStream is =
- getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
- mViewerConfig.loadViewerConfig(is);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- } catch (IOException e) {
- Log.i(TAG, "Unable to load log definitions: IOException while reading "
- + "wm_shell_protolog. " + e);
- } catch (JSONException e) {
- Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
- + "wm_shell_protolog. " + e);
- }
- return -1;
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(true /* setTextLogging */, true, pw, groups);
}
public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@
}
private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a62552..069066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@
@ExternalThread
public interface RecentTasks {
/**
- * Returns a binder that can be passed to an external process to fetch recent tasks.
- */
- default IRecentTasks createExternalInterface() {
- return null;
- }
-
- /**
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 02b5a35..08f3db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -69,11 +72,12 @@
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
- private final RecentTasks mImpl = new RecentTasksImpl();
+ private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+ taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,7 +138,13 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IRecentTasksImpl(this);
+ }
+
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -366,17 +379,6 @@
*/
@ExternalThread
private class RecentTasksImpl implements RecentTasks {
- private IRecentTasksImpl mIRecentTasks;
-
- @Override
- public IRecentTasks createExternalInterface() {
- if (mIRecentTasks != null) {
- mIRecentTasks.invalidate();
- }
- mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
- return mIRecentTasks;
- }
-
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +395,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IRecentTasksImpl extends IRecentTasks.Stub {
+ private static class IRecentTasksImpl extends IRecentTasks.Stub
+ implements ExternalInterfaceBinder {
private RecentTasksController mController;
private final SingleInstanceRemoteListener<RecentTasksController,
IRecentTasksListener> mListener;
@@ -424,7 +427,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index ecdafa9..eb08d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -79,26 +79,47 @@
/**
* Starts tasks simultaneously in one transition.
*/
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 10;
+ oneway void startTasks(int taskId1, in Bundle options1, int taskId2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteTransition remoteTransition,
+ in InstanceId instanceId) = 10;
+
+ /**
+ * Starts a pair of intent and task in one transition.
+ */
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
+ in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
+
+ /**
+ * Starts a pair of shortcut and task in one transition.
+ */
+ oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
+ oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
+ in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
/**
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
- int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
in InstanceId instanceId) = 12;
/**
+ * Starts a pair of shortcut and task using legacy transition system.
+ */
+ oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -111,11 +132,5 @@
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
-
- /**
- * Starts a pair of shortcut and task using legacy transition system.
- */
- oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo, int taskId,
- in Bundle mainOptions, in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
}
+// Last id = 17
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799..d86aadc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,13 +70,6 @@
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 07a6895..c6a2b83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -29,6 +29,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
@@ -71,6 +72,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new ISplitScreenImpl(this);
+ }
+
/**
* This will be called after ShellTaskOrganizer has initialized/registered because of the
* dependency order.
@@ -224,6 +230,8 @@
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
@@ -658,7 +666,6 @@
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
@@ -704,15 +711,6 @@
};
@Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -752,7 +750,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private static class ISplitScreenImpl extends ISplitScreen.Stub
+ implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
@@ -779,7 +778,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
@@ -828,47 +828,68 @@
}
@Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ taskId1, options1, taskId2, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
- int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
+ int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
- sidePosition, splitRatio, adapter, instanceId));
+ pendingIntent, fillInIntent, options1, taskId, options2,
+ splitPosition, splitRatio, adapter, instanceId));
}
@Override
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
- shortcutInfo, taskId, mainOptions, sideOptions, sidePosition,
+ shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio,
+ public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition,
+ (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
+ taskId2, options2, splitPosition, splitRatio, remoteTransition,
+ instanceId));
+ }
+
+ @Override
+ public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
+ (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
+ fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId));
+ }
+
+ @Override
+ public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
+ (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
+ options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
instanceId));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c17f822..9102bd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -516,14 +516,55 @@
}
/** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
+ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
+ startWithTask(wct, taskId2, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Start an intent and a task to a split pair in one transition. */
+ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /** Starts a shortcut and a task to a split pair in one transition. */
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(splitPosition, wct);
+ options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+
+ startWithTask(wct, taskId, options2, splitRatio, remoteTransition, instanceId);
+ }
+
+ /**
+ * Starts with the second task to a split pair in one transition.
+ *
+ * @param wct transaction to start the first task
+ * @param instanceId if {@code null}, will not log. Otherwise it will be used in
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ */
+ private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
if (mMainStage.isActive()) {
mMainStage.evictAllChildren(wct);
mSideStage.evictAllChildren(wct);
@@ -538,60 +579,61 @@
wct.setForceTranslucent(mRootTaskInfo.token, false);
// Make sure the launch options will put tasks in the corresponding split roots
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
setEnterInstanceId(instanceId);
}
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ /** Starts a pair of tasks using legacy transition. */
+ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
+ int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startTask(sideTaskId, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startTask(taskId1, options1);
- startWithLegacyTransition(wct, mainTaskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
- /** Start an intent and a task ordered by {@code intentFirst}. */
+ /** Starts a pair of intent and task using legacy transition. */
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
+ /** Starts a pair of shortcut and task using legacy transition. */
void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideOptions == null) sideOptions = new Bundle();
- addActivityOptions(sideOptions, mSideStage);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, sideOptions);
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, mainOptions, sidePosition, splitRatio, adapter,
+ startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
/**
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1727,6 +1769,7 @@
@StageType
private int getStageType(StageTaskListener stage) {
+ if (stage == null) return STAGE_TYPE_UNDEFINED;
return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
}
@@ -1981,8 +2024,8 @@
}
}
- // TODO: fallback logic. Probably start a new transition to exit split before applying
- // anything here. Ideally consolidate with transition-merging.
+ // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
throw new IllegalStateException("Launched a task in split, but didn't receive any"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a3..538bbec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@
* Interface to engage starting window feature.
*/
public interface StartingSurface {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate starting windows.
- */
- default IStartingWindow createExternalInterface() {
- return null;
- }
-
/**
* Returns the background color for a starting window if existing.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21..0c23f10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -43,10 +44,12 @@
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
/**
@@ -76,6 +79,7 @@
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
@@ -86,12 +90,14 @@
public StartingWindowController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
@@ -107,8 +113,14 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IStartingWindowImpl(this);
+ }
+
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ this::createExternalInterface, this);
}
@Override
@@ -222,17 +234,6 @@
* The interface for calls from outside the Shell, within the host process.
*/
private class StartingSurfaceImpl implements StartingSurface {
- private IStartingWindowImpl mIStartingWindow;
-
- @Override
- public IStartingWindowImpl createExternalInterface() {
- if (mIStartingWindow != null) {
- mIStartingWindow.invalidate();
- }
- mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
- return mIStartingWindow;
- }
-
@Override
public int getBackgroundColor(TaskInfo taskInfo) {
synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private static class IStartingWindowImpl extends IStartingWindow.Stub
+ implements ExternalInterfaceBinder {
private StartingWindowController mController;
private SingleInstanceRemoteListener<StartingWindowController,
IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 5799394..fdf073f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+ new ArrayMap<>();
+ // References to the existing interfaces, to be invalidated when they are recreated
+ private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
private Configuration mLastConfiguration;
@@ -67,6 +77,11 @@
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/**
@@ -124,6 +139,47 @@
mUserChangeListeners.remove(listener);
}
+ /**
+ * Adds an interface that can be called from a remote process. This method takes a supplier
+ * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+ * will request new binder instances for each instance of Launcher that it provides binders
+ * to.
+ *
+ * @param extra the key for the interface, {@see ShellSharedConstants}
+ * @param binderSupplier the supplier of the binder to pass to the external process
+ * @param callerInstance the instance of the caller, purely for logging
+ */
+ public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+ Object callerInstance) {
+ ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+ callerInstance.getClass().getSimpleName(), extra);
+ if (mExternalInterfaceSuppliers.containsKey(extra)) {
+ throw new IllegalArgumentException("Supplier with same key already exists: "
+ + extra);
+ }
+ mExternalInterfaceSuppliers.put(extra, binderSupplier);
+ }
+
+ /**
+ * Updates the given bundle with the set of external interfaces, invalidating the old set of
+ * binders.
+ */
+ private void createExternalInterfaces(Bundle output) {
+ // Invalidate the old binders
+ for (int i = 0; i < mExternalInterfaces.size(); i++) {
+ mExternalInterfaces.valueAt(i).invalidate();
+ }
+ mExternalInterfaces.clear();
+
+ // Create new binders for each key
+ for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+ final String key = mExternalInterfaceSuppliers.keyAt(i);
+ final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+ mExternalInterfaces.put(key, b);
+ output.putBinder(key, b.asBinder());
+ }
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+ if (!mExternalInterfaces.isEmpty()) {
+ pw.println(innerPrefix + "mExternalInterfaces={");
+ for (String key : mExternalInterfaces.keySet()) {
+ pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+ }
+ pw.println(innerPrefix + "}");
+ }
}
/**
@@ -211,7 +275,6 @@
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
-
@Override
public void onInit() {
try {
@@ -222,28 +285,6 @@
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = mShellCommandHandler.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@
mMainExecutor.execute(() ->
ShellController.this.onUserProfilesChanged(profiles));
}
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void createExternalInterfaces(Bundle bundle) {
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ ShellController.this.createExternalInterfaces(bundle);
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to get Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c82..bc5dd11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -37,18 +38,6 @@
default void onInit() {}
/**
- * Dumps the shell state.
- */
- default void dump(PrintWriter pw) {}
-
- /**
- * Handles a shell command.
- */
- default boolean handleCommand(final String[] args, PrintWriter pw) {
- return false;
- }
-
- /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@
* Notifies the Shell when a profile belonging to the user changes.
*/
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Updates the given {@param bundle} with the set of exposed interfaces.
+ */
+ default void createExternalInterfaces(Bundle bundle) {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 0000000..bdda6a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * 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.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
+ // See IRecentTasks.aidl
+ public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+ // See IBackAnimation.aidl
+ public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IFloatingTasks.aidl
+ public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d..da39017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@
*/
@ExternalThread
public interface ShellTransitions {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate remote transitions.
- */
- default IShellTransitions createExternalInterface() {
- return null;
- }
-
/**
* Registers a remote transition.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 29d25bc..d1bc738 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -25,10 +25,12 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,11 +63,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -115,6 +119,7 @@
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -142,6 +147,7 @@
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@@ -156,10 +162,14 @@
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellController = shellController;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
+
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -193,6 +203,10 @@
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IShellTransitionsImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -441,31 +455,34 @@
return;
}
- // apply transfer starting window directly if there is no other task change. Since this
- // is an activity->activity situation, we can detect it by selecting transitions with only
- // 2 changes where neither are tasks and one is a starting-window recipient.
final int changeSize = info.getChanges().size();
- if (changeSize == 2) {
- boolean nonTaskChange = true;
- boolean transferStartingWindow = false;
- for (int i = changeSize - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null) {
- nonTaskChange = false;
- break;
- }
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- transferStartingWindow = true;
- }
+ boolean taskChange = false;
+ boolean transferStartingWindow = false;
+ boolean allOccluded = changeSize > 0;
+ for (int i = changeSize - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ taskChange |= change.getTaskInfo() != null;
+ transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+ if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
+ allOccluded = false;
}
- if (nonTaskChange && transferStartingWindow) {
- t.apply();
- finishT.apply();
- // Treat this as an abort since we are bypassing any merge logic and effectively
- // finishing immediately.
- onAbort(transitionToken);
- return;
- }
+ }
+ // There does not need animation when:
+ // A. Transfer starting window. Apply transfer starting window directly if there is no other
+ // task change. Since this is an activity->activity situation, we can detect it by selecting
+ // transitions with only 2 changes where neither are tasks and one is a starting-window
+ // recipient.
+ if (!taskChange && transferStartingWindow && changeSize == 2
+ // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
+ // changes are underneath another change.
+ || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
+ && allOccluded)) {
+ t.apply();
+ finishT.apply();
+ // Treat this as an abort since we are bypassing any merge logic and effectively
+ // finishing immediately.
+ onAbort(transitionToken);
+ return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -542,6 +559,22 @@
"This shouldn't happen, maybe the default handler is broken.");
}
+ /**
+ * Gives every handler (in order) a chance to handle request until one consumes the transition.
+ * @return the WindowContainerTransaction given by the handler which consumed the transition.
+ */
+ public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ if (mHandlers.get(i) == skip) continue;
+ WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+ if (wct != null) {
+ return wct;
+ }
+ }
+ return null;
+ }
+
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -716,8 +749,8 @@
null /* newDisplayAreaInfo */);
}
}
- active.mToken = mOrganizer.startTransition(
- request.getType(), transitionToken, wct);
+ mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
+ active.mToken = transitionToken;
mActiveTransitions.add(active);
}
@@ -726,7 +759,7 @@
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
- active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
+ active.mToken = mOrganizer.startNewTransition(type, wct);
mActiveTransitions.add(active);
return active.mToken;
}
@@ -897,17 +930,6 @@
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
- private IShellTransitionsImpl mIShellTransitions;
-
- @Override
- public IShellTransitions createExternalInterface() {
- if (mIShellTransitions != null) {
- mIShellTransitions.invalidate();
- }
- mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
- return mIShellTransitions;
- }
-
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull RemoteTransition remoteTransition) {
@@ -928,7 +950,8 @@
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub
+ implements ExternalInterfaceBinder {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
@@ -938,7 +961,8 @@
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mTransitions = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9e49b51..dca516a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,14 +19,20 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -46,7 +52,9 @@
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+ private static final String TAG = "CaptionViewModel";
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -57,6 +65,8 @@
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
+ private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
@@ -81,12 +91,12 @@
}
@Override
- public CaptionWindowDecoration createWindowDecoration(
+ public boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- if (!shouldShowWindowDecor(taskInfo)) return null;
+ if (!shouldShowWindowDecor(taskInfo)) return false;
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
mContext,
mDisplayController,
@@ -96,47 +106,45 @@
mMainHandler,
mMainChoreographer,
mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
- setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
- setupCaptionColor(taskInfo, windowDecoration);
- return windowDecoration;
+ setupWindowDecorationForTransition(taskInfo, startT, finishT);
+ return true;
}
@Override
- public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
- if (!(windowDecor instanceof CaptionWindowDecoration)) return null;
- final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor;
- if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) {
- return null;
- }
- return captionWindowDecor;
- }
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
decoration.relayout(taskInfo);
-
- setupCaptionColor(taskInfo, decoration);
- }
-
- private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
- int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
}
@Override
public void setupWindowDecorationForTransition(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- CaptionWindowDecoration decoration) {
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (decoration == null) return;
+
decoration.relayout(taskInfo, startT, finishT);
}
+ @Override
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ final CaptionWindowDecoration decoration =
+ mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ if (decoration == null) return;
+
+ decoration.close();
+ }
+
private class CaptionTouchEventListener implements
View.OnClickListener, View.OnTouchListener {
@@ -145,6 +153,7 @@
private final DragResizeCallback mDragResizeCallback;
private int mDragPointerId = -1;
+ private boolean mDragActive = false;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
@@ -165,42 +174,38 @@
} else {
mSyncQueue.queue(wct);
}
- } else if (id == R.id.maximize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
- int displayWindowingMode =
- taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
- wct.setWindowingMode(mTaskToken,
- targetWindowingMode == displayWindowingMode
- ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
- if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- wct.setBounds(mTaskToken, null);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct);
- } else {
- mSyncQueue.queue(wct);
- }
- } else if (id == R.id.minimize_window) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, false);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitionStarter.startMinimizedModeTransition(wct);
- } else {
- mSyncQueue.queue(wct);
- }
+ } else if (id == R.id.back_button) {
+ injectBackKey();
+ }
+ }
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK,
+ 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD,
+ 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
}
}
@Override
public boolean onTouch(View v, MotionEvent e) {
- if (v.getId() != R.id.caption) {
+ int id = v.getId();
+ if (id != R.id.caption_handle && id != R.id.caption) {
return false;
}
- handleEventForMove(e);
-
+ if (id == R.id.caption_handle || mDragActive) {
+ handleEventForMove(e);
+ }
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
@@ -223,6 +228,7 @@
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mDragActive = true;
mDragPointerId = e.getPointerId(0);
mDragResizeCallback.onDragResizeStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
@@ -235,6 +241,7 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mDragActive = false;
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
.stableInsets().top;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 733f6b7..beace75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,12 +22,12 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewConfiguration;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
@@ -38,11 +38,7 @@
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
- *
- * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
- * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
- * caption buttons according to the luminance of the background.
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
@@ -54,7 +50,10 @@
// Height of button (32dp) + 2 * margin (5dp each)
private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
+ // Width of buttons (64dp) + handle (128dp) + padding (24dp total)
+ private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216;
private static final int RESIZE_HANDLE_IN_DIP = 30;
+ private static final int RESIZE_CORNER_IN_DIP = 44;
private static final Rect EMPTY_OUTSET = new Rect();
private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
@@ -73,6 +72,8 @@
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
+ private boolean mDesktopActive;
+
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -87,6 +88,7 @@
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
}
void setCaptionListeners(
@@ -123,8 +125,8 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, startT, finishT, wct, mResult);
- taskInfo = null; // Clear it just in case we use it accidentally
+ DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp,
+ startT, finishT, wct, mResult);
mTaskOrganizer.applyTransaction(wct);
@@ -137,6 +139,17 @@
setupRootView();
}
+ // If this task is not focused, do not show caption.
+ setCaptionVisibility(taskInfo.isFocused);
+
+ // Only handle should show if Desktop Mode is inactive.
+ boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
+ if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+ mDesktopActive = desktopCurrentStatus;
+ setButtonVisibility();
+ }
+ taskInfo = null; // Clear it just in case we use it accidentally
+
if (!isDragResizeable) {
closeDragResizeListener();
return;
@@ -145,16 +158,19 @@
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
- mContext,
- mHandler,
- mChoreographer,
- mDisplay.getDisplayId(),
- mDecorationContainerSurface,
- mDragResizeCallback);
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
}
+ int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+
mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
+ mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP),
+ (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop);
}
/**
@@ -163,42 +179,46 @@
private void setupRootView() {
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
- View maximize = caption.findViewById(R.id.maximize_window);
- if (DesktopModeStatus.IS_SUPPORTED) {
- // Hide maximize button when desktop mode is available
- maximize.setVisibility(View.GONE);
- } else {
- maximize.setVisibility(View.VISIBLE);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
- }
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
- View minimize = caption.findViewById(R.id.minimize_window);
- minimize.setOnClickListener(mOnCaptionButtonClickListener);
+ View back = caption.findViewById(R.id.back_button);
+ back.setOnClickListener(mOnCaptionButtonClickListener);
+ View handle = caption.findViewById(R.id.caption_handle);
+ handle.setOnTouchListener(mOnCaptionTouchListener);
+ setButtonVisibility();
}
- void setCaptionColor(int captionColor) {
- if (mResult.mRootView == null) {
- return;
- }
-
+ /**
+ * Sets caption visibility based on task focus.
+ *
+ * @param visible whether or not the caption should be visible
+ */
+ private void setCaptionVisibility(boolean visible) {
+ int v = visible ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
- GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
- captionDrawable.setColor(captionColor);
+ caption.setVisibility(v);
+ }
+ /**
+ * Sets the visibility of buttons and color of caption based on desktop mode status
+ *
+ */
+ public void setButtonVisibility() {
+ int v = mDesktopActive ? View.VISIBLE : View.GONE;
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ View back = caption.findViewById(R.id.back_button);
+ View close = caption.findViewById(R.id.close_window);
+ back.setVisibility(v);
+ close.setVisibility(v);
int buttonTintColorRes =
- Color.valueOf(captionColor).luminance() < 0.5
- ? R.color.decor_button_light_color
- : R.color.decor_button_dark_color;
+ mDesktopActive ? R.color.decor_button_dark_color
+ : R.color.decor_button_light_color;
ColorStateList buttonTintColor =
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- View maximize = caption.findViewById(R.id.maximize_window);
- VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
- maximizeBackground.setTintList(buttonTintColor);
-
- View close = caption.findViewById(R.id.close_window);
- VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
- closeBackground.setTintList(buttonTintColor);
+ View handle = caption.findViewById(R.id.caption_handle);
+ VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
+ handleBackground.setTintList(buttonTintColor);
+ caption.setBackgroundColor(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
private void closeDragResizeListener() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 3d01495..b9f16b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -16,11 +16,13 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -42,8 +44,11 @@
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
- * convert them to drag resize requests.
+ * and corners. Converts them to drag resize requests.
+ * Task edges are for resizing with a mouse.
+ * Task corners are for resizing with touch input.
*/
+// TODO(b/251270585): investigate how to pass taps in corners to the tasks
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
@@ -63,8 +68,15 @@
private int mWidth;
private int mHeight;
private int mResizeHandleThickness;
+ private int mCornerSize;
+
+ private Rect mLeftTopCornerBounds;
+ private Rect mRightTopCornerBounds;
+ private Rect mLeftBottomCornerBounds;
+ private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
+ private int mTouchSlop;
DragResizeInputListener(
Context context,
@@ -118,16 +130,23 @@
* @param height The height of the drag resize handler in pixels, including resize handle
* thickness. That is task height + 2 * resize handle thickness.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
+ * @param cornerSize The size of the resize handle centered in each corner.
+ * @param touchSlop The distance in pixels user has to drag with touch for it to register as
+ * a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness) {
+ void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ int touchSlop) {
if (mWidth == width && mHeight == height
- && mResizeHandleThickness == resizeHandleThickness) {
+ && mResizeHandleThickness == resizeHandleThickness
+ && mCornerSize == cornerSize) {
return;
}
mWidth = width;
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
+ mCornerSize = cornerSize;
+ mTouchSlop = touchSlop;
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -146,6 +165,40 @@
mWidth, mHeight);
touchRegion.union(bottomInputBounds);
+ // Set up touch areas in each corner.
+ int cornerRadius = mCornerSize / 2;
+ mLeftTopCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftTopCornerBounds);
+
+ mRightTopCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightTopCornerBounds);
+
+ mLeftBottomCornerBounds = new Rect(
+ mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mLeftBottomCornerBounds);
+
+ mRightBottomCornerBounds = new Rect(
+ mWidth - mResizeHandleThickness - cornerRadius,
+ mHeight - mResizeHandleThickness - cornerRadius,
+ mWidth - mResizeHandleThickness + cornerRadius,
+ mHeight - mResizeHandleThickness + cornerRadius
+ );
+ touchRegion.union(mRightBottomCornerBounds);
+
try {
mWindowSession.updateInputChannel(
mInputChannel.getToken(),
@@ -173,6 +226,9 @@
private final Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
+ private boolean mShouldHandleEvents;
+ private boolean mDragging;
+ private final PointF mActionDownPoint = new PointF();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -216,41 +272,101 @@
}
MotionEvent e = (MotionEvent) inputEvent;
+ boolean result = false;
+ // Check if this is a touch event vs mouse event.
+ // Touch events are tracked in four corners. Other events are tracked in resize edges.
+ boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mDragPointerId = e.getPointerId(0);
- mCallback.onDragResizeStart(
- calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
+ float x = e.getX(0);
+ float y = e.getY(0);
+ if (isTouch) {
+ mShouldHandleEvents = isInCornerBounds(x, y);
+ } else {
+ mShouldHandleEvents = isInResizeHandleBounds(x, y);
+ }
+ if (mShouldHandleEvents) {
+ mDragPointerId = e.getPointerId(0);
+ float rawX = e.getRawX(0);
+ float rawY = e.getRawY(0);
+ mActionDownPoint.set(rawX, rawY);
+ int ctrlType = calculateCtrlType(isTouch, x, y);
+ mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_MOVE: {
+ if (!mShouldHandleEvents) {
+ break;
+ }
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeMove(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ float rawX = e.getRawX(dragPointerIndex);
+ float rawY = e.getRawY(dragPointerIndex);
+ if (isTouch) {
+ // Check for touch slop for touch events
+ float dx = rawX - mActionDownPoint.x;
+ float dy = rawY - mActionDownPoint.y;
+ if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
+ mDragging = true;
+ }
+ } else {
+ // For all other types allow immediate dragging.
+ mDragging = true;
+ }
+ if (mDragging) {
+ mCallback.onDragResizeMove(rawX, rawY);
+ result = true;
+ }
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeEnd(
- e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ if (mDragging) {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ }
+ mDragging = false;
+ mShouldHandleEvents = false;
+ mActionDownPoint.set(0, 0);
mDragPointerId = -1;
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ result = true;
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
+ result = true;
break;
}
- return true;
+ return result;
+ }
+
+ private boolean isInCornerBounds(float xf, float yf) {
+ return calculateCornersCtrlType(xf, yf) != 0;
+ }
+
+ private boolean isInResizeHandleBounds(float x, float y) {
+ return calculateResizeHandlesCtrlType(x, y) != 0;
}
@TaskPositioner.CtrlType
- private int calculateCtrlType(float x, float y) {
+ private int calculateCtrlType(boolean isTouch, float x, float y) {
+ if (isTouch) {
+ return calculateCornersCtrlType(x, y);
+ }
+ return calculateResizeHandlesCtrlType(x, y);
+ }
+
+ @TaskPositioner.CtrlType
+ private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
if (x < mResizeHandleThickness) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
@@ -267,8 +383,27 @@
return ctrlType;
}
+ @TaskPositioner.CtrlType
+ private int calculateCornersCtrlType(float x, float y) {
+ int xi = (int) x;
+ int yi = (int) y;
+ if (mLeftTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mLeftBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ if (mRightTopCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (mRightBottomCornerBounds.contains(xi, yi)) {
+ return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ return 0;
+ }
+
private void updateCursorType(float x, float y) {
- @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
+ @TaskPositioner.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
@@ -292,4 +427,4 @@
mInputManager.setPointerIconType(cursorType);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 280569b..27c1011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -25,9 +25,10 @@
class TaskPositioner implements DragResizeCallback {
- @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
@interface CtrlType {}
+ static final int CTRL_TYPE_UNDEFINED = 0;
static final int CTRL_TYPE_LEFT = 1;
static final int CTRL_TYPE_RIGHT = 2;
static final int CTRL_TYPE_TOP = 4;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d9697d2..d7f71c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -19,8 +19,6 @@
import android.app.ActivityManager;
import android.view.SurfaceControl;
-import androidx.annotation.Nullable;
-
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
/**
@@ -28,10 +26,8 @@
* customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
* interactions with UI widgets in window decorations and send corresponding requests to system
* servers.
- *
- * @param <T> The actual decoration type
*/
-public interface WindowDecorViewModel<T extends AutoCloseable> {
+public interface WindowDecorViewModel {
/**
* Sets the transition starter that starts freeform task transitions.
@@ -50,29 +46,19 @@
* @param finishT the finish transaction to restore states after the transition
* @return the window decoration object
*/
- @Nullable T createWindowDecoration(
+ boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
/**
- * Adopts the window decoration if possible.
- * May be {@code null} if a window decor is not needed or the given one is incompatible.
- *
- * @param windowDecor the potential window decoration to adopt
- * @return the window decoration if it can be adopted, or {@code null} otherwise.
- */
- @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
-
- /**
* Notifies a task info update on the given task, with the window decoration created previously
* for this task by {@link #createWindowDecoration}.
*
* @param taskInfo the new task info of the task
- * @param windowDecoration the window decoration created for the task
*/
- void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+ void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo);
/**
* Notifies a transition is about to start about the given task to give the window decoration a
@@ -80,11 +66,16 @@
*
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
- * @param windowDecoration the window decoration created for the task
*/
void setupWindowDecorationForTransition(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT,
- T windowDecoration);
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Destroys the window decoration of the give task.
+ *
+ * @param taskInfo the info of the task
+ */
+ void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 3e3a864..bf863ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -143,9 +143,9 @@
abstract void relayout(RunningTaskInfo taskInfo);
void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction startT,
- SurfaceControl.Transaction finishT, WindowContainerTransaction wct,
- RelayoutResult<T> outResult) {
+ float captionWidthDp, Rect outsetsDp, float shadowRadiusDp,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+ WindowContainerTransaction wct, RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
@@ -249,8 +249,15 @@
}
final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
+ final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity);
+
+ //Prevent caption from going offscreen if task is too high up
+ final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
+
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX, -decorContainerOffsetY)
+ mCaptionContainerSurface, -decorContainerOffsetX
+ + taskBounds.width() / 2 - captionWidth / 2,
+ -decorContainerOffsetY - captionYPos)
.setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
.show(mCaptionContainerSurface);
@@ -264,7 +271,7 @@
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
+ new WindowManager.LayoutParams(captionWidth, captionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -282,7 +289,7 @@
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
} else {
startT.hide(mCaptionContainerSurface);
@@ -388,4 +395,4 @@
return new SurfaceControlViewHost(c, d, wmm);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 90a3773..077e9ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,7 +63,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Ignore;
@@ -102,6 +104,9 @@
@Mock
private IBackNaviAnimationController mIBackNaviAnimationController;
+ @Mock
+ private ShellController mShellController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -118,7 +123,7 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit,
+ mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
@@ -175,6 +180,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -250,7 +261,7 @@
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit,
+ mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 6292130..2fc0914 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
@@ -93,6 +94,7 @@
private @Mock Lazy<Transitions> mMockTransitionsLazy;
private @Mock CompatUIWindowManager mMockCompatLayout;
private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+ private @Mock DockStateReader mDockStateReader;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -113,7 +115,7 @@
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
- mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index f3a8cf4..16517c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -54,6 +54,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
@@ -103,6 +104,7 @@
@Mock private SurfaceControlViewHost mViewHost;
@Mock private Transitions mTransitions;
@Mock private Runnable mOnDismissCallback;
+ @Mock private DockStateReader mDockStateReader;
private SharedPreferences mSharedPreferences;
@Nullable
@@ -153,6 +155,16 @@
}
@Test
+ public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+ true, /* isDocked */ true);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -382,17 +394,27 @@
return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
}
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, boolean isDocked) {
+ return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */
+ false, isDocked);
+ }
+
private LetterboxEduWindowManager createWindowManager(boolean eligible,
int userId, boolean isTaskbarEduShowing) {
+ return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible,
+ int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+ doReturn(isDocked).when(mDockStateReader).isDocked();
LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mTransitions, mOnDismissCallback,
- mAnimationController);
+ mAnimationController, mDockStateReader);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
-
return windowManager;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97..c850a3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -68,6 +69,8 @@
public class DesktopModeControllerTest extends ShellTestCase {
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -94,8 +97,8 @@
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockTransitions,
+ mController = new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
mDesktopModeTaskRepository, mMockHandler, mExecutor);
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
index a88c837..d378a17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.After;
import org.junit.Before;
@@ -168,6 +169,18 @@
}
}
+ @Test
+ public void onInit_addExternalInterface() {
+ if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+ createController();
+ setUpTabletConfig();
+ mController.onInit();
+
+ verify(mShellController, times(1)).addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+ }
+ }
+
//
// Tests for floating layer, which is only available for tablets.
//
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 0fd5cb0..7068a84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,17 +17,10 @@
package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_MAXIMIZE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
-
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
@@ -44,9 +37,9 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import org.junit.Before;
import org.junit.Test;
@@ -65,9 +58,7 @@
@Mock
private Transitions mTransitions;
@Mock
- private FullscreenTaskListener<?> mFullscreenTaskListener;
- @Mock
- private FreeformTaskListener<?> mFreeformTaskListener;
+ private WindowDecorViewModel mWindowDecorViewModel;
private FreeformTaskTransitionObserver mTransitionObserver;
@@ -82,7 +73,7 @@
doReturn(pm).when(context).getPackageManager();
mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions, mFullscreenTaskListener, mFreeformTaskListener);
+ context, mShellInit, mTransitions, mWindowDecorViewModel);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
Runnable.class);
@@ -112,11 +103,12 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).createWindowDecoration(change, startT, finishT);
+ verify(mWindowDecorViewModel).createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
- public void testObtainsWindowDecorOnCloseTransition_freeform() {
+ public void testPreparesWindowDecorOnCloseTransition_freeform() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
@@ -128,7 +120,8 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+ verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+ change.getTaskInfo(), startT, finishT);
}
@Test
@@ -138,17 +131,13 @@
final TransitionInfo info = new TransitionInfo(TRANSIT_CLOSE, 0);
info.addChange(change);
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(windowDecor, never()).close();
+ verify(mWindowDecorViewModel, never()).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -159,8 +148,6 @@
info.addChange(change);
final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -169,7 +156,7 @@
mTransitionObserver.onTransitionStarting(transition);
mTransitionObserver.onTransitionFinished(transition, false);
- verify(windowDecor).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change.getTaskInfo());
}
@Test
@@ -192,10 +179,6 @@
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -204,7 +187,7 @@
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor2).close();
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
@Test
@@ -215,10 +198,6 @@
final TransitionInfo info1 = new TransitionInfo(TRANSIT_CLOSE, 0);
info1.addChange(change1);
- final AutoCloseable windowDecor1 = mock(AutoCloseable.class);
- doReturn(windowDecor1).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change1.getTaskInfo()), any(), any());
-
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
@@ -231,10 +210,6 @@
final TransitionInfo info2 = new TransitionInfo(TRANSIT_CLOSE, 0);
info2.addChange(change2);
- final AutoCloseable windowDecor2 = mock(AutoCloseable.class);
- doReturn(windowDecor2).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change2.getTaskInfo()), any(), any());
-
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
@@ -243,48 +218,8 @@
mTransitionObserver.onTransitionFinished(transition1, false);
- verify(windowDecor1).close();
- verify(windowDecor2).close();
- }
-
- @Test
- public void testTransfersWindowDecorOnMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FULLSCREEN);
- final TransitionInfo info = new TransitionInfo(TRANSIT_MAXIMIZE, 0);
- info.addChange(change);
-
- final AutoCloseable windowDecor = mock(AutoCloseable.class);
- doReturn(windowDecor).when(mFreeformTaskListener).giveWindowDecoration(
- eq(change.getTaskInfo()), any(), any());
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFreeformTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFullscreenTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
- }
-
- @Test
- public void testTransfersWindowDecorOnRestoreFromMaximize() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CHANGE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfo(TRANSIT_RESTORE_FROM_MAXIMIZE, 0);
- info.addChange(change);
-
- final IBinder transition = mock(IBinder.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- mTransitionObserver.onTransitionStarting(transition);
-
- verify(mFullscreenTaskListener).giveWindowDecoration(change.getTaskInfo(), startT, finishT);
- verify(mFreeformTaskListener).adoptWindowDecoration(
- eq(change), same(startT), same(finishT), any());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change1.getTaskInfo());
+ verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index cf8297e..8ad3d2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +177,12 @@
}
@Test
+ public void testControllerRegisteresExternalInterface() {
+ verify(mMockShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 1e08f1e..d06fb55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -61,6 +62,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -152,6 +154,12 @@
}
@Test
+ public void instantiatePipController_registerExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerUserChangeListener() {
verify(mShellController, times(1)).addUserChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa7..f6ac3ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -57,7 +58,9 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -84,6 +87,8 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@@ -101,7 +106,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -121,6 +126,12 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ }
+
+ @Test
public void testAddRemoveSplitNotifyChange() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5a68361..55883ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -133,6 +134,15 @@
}
@Test
+ public void instantiateController_addExternalInterface() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ }
+
+ @Test
public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3..90165d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -36,7 +37,9 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -56,25 +59,34 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
- private @Mock ShellInit mShellInit;
+ private @Mock ShellController mShellController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
private @Mock IconProvider mIconProvider;
private @Mock TransactionPool mTransactionPool;
private StartingWindowController mController;
+ private ShellInit mShellInit;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
- mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
- mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mController = new StartingWindowController(mContext, mShellInit, mShellController,
+ mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit.init();
}
@Test
- public void instantiate_addInitCallback() {
+ public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
+
+ @Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9..fbc50c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.sysui;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +34,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
@@ -49,6 +54,7 @@
public class ShellControllerTest extends ShellTestCase {
private static final int TEST_USER_ID = 100;
+ private static final String EXTRA_TEST_BINDER = "test_binder";
@Mock
private ShellInit mShellInit;
@@ -81,6 +87,47 @@
}
@Test
+ public void testAddExternalInterface_ensureCallback() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+ Bundle b = new Bundle();
+ mController.asShell().createExternalInterfaces(b);
+ assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+ }
+
+ @Test
+ public void testAddExternalInterface_disallowDuplicateKeys() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ assertThrows(IllegalArgumentException.class, () -> {
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ });
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c6492be..c764741 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -67,10 +66,12 @@
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.IWindowContainerToken;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
@@ -86,7 +87,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -117,18 +120,31 @@
@Before
public void setUp() {
doAnswer(invocation -> invocation.getArguments()[1])
- .when(mOrganizer).startTransition(anyInt(), any(), any());
+ .when(mOrganizer).startTransition(any(), any());
}
@Test
public void instantiate_addInitCallback() {
ShellInit shellInit = mock(ShellInit.class);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
verify(shellInit, times(1)).addInitCallback(any(), eq(t));
}
@Test
+ public void instantiateController_addExternalInterface() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ ShellController shellController = mock(ShellController.class);
+ final Transitions t = new Transitions(mContext, shellInit, shellController,
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ verify(shellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -136,7 +152,7 @@
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -188,7 +204,7 @@
// Make a request that will be rejected by the testhandler.
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -199,10 +215,12 @@
// Make a request that will be handled by testhandler but not animated by it.
RunningTaskInfo mwTaskInfo =
createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ // Make the wct non-empty.
+ handlerWCT.setFocusable(new WindowContainerToken(mock(IWindowContainerToken.class)), true);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+ eq(transitToken), eq(handlerWCT));
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
@@ -217,8 +235,8 @@
transitions.addHandler(topHandler);
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(
- eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT));
+ verify(mOrganizer, times(2)).startTransition(
+ eq(transitToken), eq(handlerWCT));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
@@ -256,7 +274,7 @@
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
new RemoteTransition(testRemote)));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -406,7 +424,7 @@
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
@@ -1060,8 +1078,9 @@
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index ab6ac94..fa62b9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -77,6 +77,7 @@
@RunWith(AndroidTestingRunner.class)
public class WindowDecorationTests extends ShellTestCase {
private static final int CAPTION_HEIGHT_DP = 32;
+ private static final int CAPTION_WIDTH_DP = 216;
private static final int SHADOW_RADIUS_DP = 5;
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
@@ -220,7 +221,7 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -410,7 +411,7 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
+ CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
}
}
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index 0a5afe4..db54ae3 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,9 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
- android:tint="@android:color/system_accent1_600"/>
+ android:tint="@android:color/system_accent1_600"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
<TextView
android:id="@android:id/text1"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 54916a2..a3d71b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -30,7 +30,8 @@
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
- android:contentDescription="Permission Icon"/>
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index fc5ff08..5c9ab7b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -103,7 +103,7 @@
private final Runnable mTimeoutRunnable = this::timeout;
- private boolean mStopAfterFirstMatch;;
+ private boolean mStopAfterFirstMatch;
/**
* A state enum for devices' discovery.
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index eff9e74..ee65ef4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -22,8 +22,11 @@
public class AccessibilityContentDescriptions {
private AccessibilityContentDescriptions() {}
+
+ public static final int PHONE_SIGNAL_STRENGTH_NONE = R.string.accessibility_no_phone;
+
public static final int[] PHONE_SIGNAL_STRENGTH = {
- R.string.accessibility_no_phone,
+ PHONE_SIGNAL_STRENGTH_NONE,
R.string.accessibility_phone_one_bar,
R.string.accessibility_phone_two_bars,
R.string.accessibility_phone_three_bars,
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
new file mode 100644
index 0000000..3daf8c2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcon.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.settingslib
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+/**
+ * A specification for the icon displaying the mobile network type -- 4G, 5G, LTE, etc. (aka "RAT
+ * icon" or "data type icon"). This is *not* the signal strength triangle.
+ *
+ * This is intended to eventually replace [SignalIcon.MobileIconGroup]. But for now,
+ * [MobileNetworkTypeIcons] just reads from the existing set of [SignalIcon.MobileIconGroup]
+ * instances to not duplicate data.
+ *
+ * TODO(b/238425913): Remove [SignalIcon.MobileIconGroup] and replace it with this class so that we
+ * don't need to fill in the superfluous fields from its parent [SignalIcon.IconGroup] class. Then
+ * this class can become either a sealed class or an enum with parameters.
+ */
+data class MobileNetworkTypeIcon(
+ /** A human-readable name for this network type, used for logging. */
+ val name: String,
+
+ /** The resource ID of the icon drawable to use. */
+ @DrawableRes val iconResId: Int,
+
+ /** The resource ID of the content description to use. */
+ @StringRes val contentDescriptionResId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
new file mode 100644
index 0000000..2c5ee89
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/MobileNetworkTypeIcons.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.settingslib
+
+import com.android.settingslib.mobile.TelephonyIcons.ICON_NAME_TO_ICON
+
+/**
+ * A utility class to fetch instances of [MobileNetworkTypeIcon] given a
+ * [SignalIcon.MobileIconGroup].
+ *
+ * Use [getNetworkTypeIcon] to fetch the instances.
+ */
+class MobileNetworkTypeIcons {
+ companion object {
+ /**
+ * A map from a [SignalIcon.MobileIconGroup.name] to an instance of [MobileNetworkTypeIcon],
+ * which is the preferred class going forward.
+ */
+ private val MOBILE_NETWORK_TYPE_ICONS: Map<String, MobileNetworkTypeIcon>
+
+ init {
+ // Build up the mapping from the old implementation to the new one.
+ val tempMap: MutableMap<String, MobileNetworkTypeIcon> = mutableMapOf()
+
+ ICON_NAME_TO_ICON.forEach { (_, mobileIconGroup) ->
+ tempMap[mobileIconGroup.name] = mobileIconGroup.toNetworkTypeIcon()
+ }
+
+ MOBILE_NETWORK_TYPE_ICONS = tempMap
+ }
+
+ /**
+ * A converter function between the old mobile network type icon implementation and the new
+ * one. Given an instance of the old class [mobileIconGroup], outputs an instance of the
+ * new class [MobileNetworkTypeIcon].
+ */
+ @JvmStatic
+ fun getNetworkTypeIcon(
+ mobileIconGroup: SignalIcon.MobileIconGroup
+ ): MobileNetworkTypeIcon {
+ return MOBILE_NETWORK_TYPE_ICONS[mobileIconGroup.name]
+ ?: mobileIconGroup.toNetworkTypeIcon()
+ }
+
+ private fun SignalIcon.MobileIconGroup.toNetworkTypeIcon(): MobileNetworkTypeIcon {
+ return MobileNetworkTypeIcon(
+ name = this.name,
+ iconResId = this.dataType,
+ contentDescriptionResId = this.dataContentDescription
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
index 280e407..6aaab3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
@@ -15,6 +15,9 @@
*/
package com.android.settingslib;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.StringRes;
+
/**
* Icons for SysUI and Settings.
*/
@@ -66,34 +69,31 @@
}
/**
- * Holds icons for a given MobileState.
+ * Holds RAT icons for a given MobileState.
*/
public static class MobileIconGroup extends IconGroup {
- public final int dataContentDescription; // mContentDescriptionDataType
- public final int dataType;
+ @StringRes public final int dataContentDescription;
+ @DrawableRes public final int dataType;
public MobileIconGroup(
String name,
- int[][] sbIcons,
- int[][] qsIcons,
- int[] contentDesc,
- int sbNullState,
- int qsNullState,
- int sbDiscState,
- int qsDiscState,
- int discContentDesc,
int dataContentDesc,
int dataType
) {
super(name,
- sbIcons,
- qsIcons,
- contentDesc,
- sbNullState,
- qsNullState,
- sbDiscState,
- qsDiscState,
- discContentDesc);
+ // The rest of the values are the same for every type of MobileIconGroup, so
+ // just provide them here.
+ // TODO(b/238425913): Eventually replace with {@link MobileNetworkTypeIcon} so
+ // that we don't have to fill in these superfluous fields.
+ /* sbIcons= */ null,
+ /* qsIcons= */ null,
+ /* contentDesc= */ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ /* sbNullState= */ 0,
+ /* qsNullState= */ 0,
+ /* sbDiscState= */ 0,
+ /* qsDiscState= */ 0,
+ /* discContentDesc= */
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE);
this.dataContentDescription = dataContentDesc;
this.dataType = dataType;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 23e0923..094567c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -16,7 +16,6 @@
package com.android.settingslib.mobile;
-import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.R;
import com.android.settingslib.SignalIcon.MobileIconGroup;
@@ -49,297 +48,129 @@
public static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.carrier_network_change_mode,
- 0
+ /* dataType= */ 0
);
public static final MobileIconGroup THREE_G = new MobileIconGroup(
"3G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3g,
TelephonyIcons.ICON_3G
);
public static final MobileIconGroup WFC = new MobileIconGroup(
"WFC",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0,
- 0);
+ /* dataContentDescription= */ 0,
+ /* dataType= */ 0);
public static final MobileIconGroup UNKNOWN = new MobileIconGroup(
"Unknown",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0,
- 0);
+ /* dataContentDescription= */ 0,
+ /* dataType= */ 0);
public static final MobileIconGroup E = new MobileIconGroup(
"E",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_edge,
TelephonyIcons.ICON_E
);
public static final MobileIconGroup ONE_X = new MobileIconGroup(
"1X",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_cdma,
TelephonyIcons.ICON_1X
);
public static final MobileIconGroup G = new MobileIconGroup(
"G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_gprs,
TelephonyIcons.ICON_G
);
public static final MobileIconGroup H = new MobileIconGroup(
"H",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g,
TelephonyIcons.ICON_H
);
public static final MobileIconGroup H_PLUS = new MobileIconGroup(
"H+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g_plus,
TelephonyIcons.ICON_H_PLUS
);
public static final MobileIconGroup FOUR_G = new MobileIconGroup(
"4G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g,
TelephonyIcons.ICON_4G
);
public static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
"4G+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_plus,
TelephonyIcons.ICON_4G_PLUS
);
public static final MobileIconGroup LTE = new MobileIconGroup(
"LTE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte,
TelephonyIcons.ICON_LTE
);
public static final MobileIconGroup LTE_PLUS = new MobileIconGroup(
"LTE+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte_plus,
TelephonyIcons.ICON_LTE_PLUS
);
public static final MobileIconGroup FOUR_G_LTE = new MobileIconGroup(
"4G LTE",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_lte,
TelephonyIcons.ICON_4G_LTE
);
public static final MobileIconGroup FOUR_G_LTE_PLUS = new MobileIconGroup(
"4G LTE+",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_lte_plus,
TelephonyIcons.ICON_4G_LTE_PLUS
);
public static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
"5Ge",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5ge_html,
TelephonyIcons.ICON_5G_E
);
public static final MobileIconGroup NR_5G = new MobileIconGroup(
"5G",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g,
TelephonyIcons.ICON_5G
);
public static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
"5G_PLUS",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g_plus,
TelephonyIcons.ICON_5G_PLUS
);
public static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.cell_data_off_content_description,
0
);
public static final MobileIconGroup NOT_DEFAULT_DATA = new MobileIconGroup(
"NotDefaultData",
- null,
- null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0,
- 0,
- 0,
- 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.not_default_data_content_description,
- 0
+ /* dataType= */ 0
);
public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup(
"CWF",
- /* sbIcons= */ null,
- /* qsIcons= */ null,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- /* sbNullState= */ 0,
- /* qsNullState= */ 0,
- /* sbDiscState= */ 0,
- /* qsDiscState= */ 0,
- AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_carrier_wifi,
TelephonyIcons.ICON_CWF
);
- // When adding a new MobileIconGround, check if the dataContentDescription has to be filtered
+ // When adding a new MobileIconGroup, check if the dataContentDescription has to be filtered
// in QSCarrier#hasValidTypeContentDescription
/** Mapping icon name(lower case) to the icon object. */
@@ -368,14 +199,6 @@
ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
}
- public static final int[] WIFI_CALL_STRENGTH_ICONS = {
- R.drawable.ic_wifi_call_strength_0,
- R.drawable.ic_wifi_call_strength_1,
- R.drawable.ic_wifi_call_strength_2,
- R.drawable.ic_wifi_call_strength_3,
- R.drawable.ic_wifi_call_strength_4
- };
-
public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
R.drawable.ic_mobile_call_strength_0,
R.drawable.ic_mobile_call_strength_1,
@@ -384,4 +207,3 @@
R.drawable.ic_mobile_call_strength_4
};
}
-
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
new file mode 100644
index 0000000..39977df
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.settingslib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settingslib.mobile.TelephonyIcons;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MobileNetworkTypeIconsTest {
+
+ @Test
+ public void getNetworkTypeIcon_hPlus_returnsHPlus() {
+ MobileNetworkTypeIcon icon =
+ MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.H_PLUS);
+
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_H_PLUS);
+ }
+
+ @Test
+ public void getNetworkTypeIcon_fourG_returnsFourG() {
+ MobileNetworkTypeIcon icon =
+ MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
+
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
+ }
+
+ @Test
+ public void getNetworkTypeIcon_unknown_returnsUnknown() {
+ SignalIcon.MobileIconGroup unknownGroup =
+ new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+
+ MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
+
+ assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
+ assertThat(icon.getIconResId()).isEqualTo(45);
+ assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index a6bfc408b..76cee7b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -44,8 +44,6 @@
Settings.System.DIM_SCREEN,
Settings.System.SCREEN_OFF_TIMEOUT,
Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
Settings.System.ADAPTIVE_SLEEP, // moved to secure
Settings.System.APPLY_RAMPING_RINGER,
Settings.System.VIBRATE_INPUT_DEVICES,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8e82b8b..8b9d118 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -102,7 +102,9 @@
Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
Settings.System.SCREEN_BRIGHTNESS_FLOAT,
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED
);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2107ba0..c28792e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -297,5 +297,6 @@
dxflags: ["--multi-dex"],
required: [
"privapp_whitelist_com.android.systemui",
+ "wmshell.protolog.json.gz",
],
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index dc2c6356..1b7e26b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -361,13 +361,17 @@
*
* The end state of the animation is controlled by [destination]. This value can be any of
* the four corners, any of the four edges, or the center of the view.
+ *
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateRemoval(
rootView: View,
destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
!occupiesSpace(
@@ -391,13 +395,28 @@
addListener(child, listener, recursive = false)
}
- // Remove the view so that a layout update is triggered for the siblings and they
- // animate to their next position while the view's removal is also animating.
- parent.removeView(rootView)
- // By adding the view to the overlay, we can animate it while it isn't part of the view
- // hierarchy. It is correctly positioned because we have its previous bounds, and we set
- // them manually during the animation.
- parent.overlay.add(rootView)
+ val viewHasSiblings = parent.childCount > 1
+ if (viewHasSiblings) {
+ // Remove the view so that a layout update is triggered for the siblings and they
+ // animate to their next position while the view's removal is also animating.
+ parent.removeView(rootView)
+ // By adding the view to the overlay, we can animate it while it isn't part of the
+ // view hierarchy. It is correctly positioned because we have its previous bounds,
+ // and we set them manually during the animation.
+ parent.overlay.add(rootView)
+ }
+ // If this view has no siblings, the parent view may shrink to (0,0) size and mess
+ // up the animation if we immediately remove the view. So instead, we just leave the
+ // view in the real hierarchy until the animation finishes.
+
+ val endRunnable = Runnable {
+ if (viewHasSiblings) {
+ parent.overlay.remove(rootView)
+ } else {
+ parent.removeView(rootView)
+ }
+ onAnimationEnd?.run()
+ }
val startValues =
mapOf(
@@ -430,7 +449,8 @@
endValues,
interpolator,
duration,
- ephemeral = true
+ ephemeral = true,
+ endRunnable,
)
if (rootView is ViewGroup) {
@@ -463,7 +483,6 @@
.alpha(0f)
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
}
@@ -477,7 +496,6 @@
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
.setStartDelay(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
.start()
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
new file mode 100644
index 0000000..4eb7c7d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/** Detects usage of Context.getSystemService() and suggests to use an injected instance instead. */
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("getSystemService")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+ if (
+ !evaluator.isStatic(method) &&
+ method.name == "getSystemService" &&
+ method.containingClass?.qualifiedName == "android.content.Context"
+ ) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Use @Inject to get the handle to a system-level services instead of using " +
+ "Context.getSystemService()"
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "NonInjectedService",
+ briefDescription =
+ "System-level services should be retrieved using " +
+ "@Inject instead of Context.getSystemService().",
+ explanation =
+ "Context.getSystemService() should be avoided because it makes testing " +
+ "difficult. Instead, use an injected service. For example, " +
+ "instead of calling Context.getSystemService(UserManager.class), " +
+ "use @Inject and add UserManager to the constructor",
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(NonInjectedServiceDetector::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index b72d03d..eb71d32 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -27,6 +27,7 @@
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
+@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 4879883..312810b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -35,6 +35,7 @@
GetMainLooperViaContextDetector.ISSUE,
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
+ NonInjectedServiceDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
new file mode 100644
index 0000000..26bd8d0
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+
+/*
+ * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
+ * stubs are not used in the lint detectors themselves.
+ */
+@Suppress("UnstableApiUsage")
+internal val androidStubs =
+ arrayOf(
+ java(
+ """
+package android.app;
+
+public class ActivityManager {
+ public static int getCurrentUser() {}
+}
+"""
+ ),
+ java(
+ """
+package android.os;
+import android.content.pm.UserInfo;
+import android.annotation.UserIdInt;
+
+public class UserManager {
+ public UserInfo getUserInfo(@UserIdInt int userId) {}
+}
+"""
+ ),
+ java("""
+package android.annotation;
+
+public @interface UserIdInt {}
+"""),
+ java("""
+package android.content.pm;
+
+public class UserInfo {}
+"""),
+ java("""
+package android.os;
+
+public class Looper {}
+"""),
+ java("""
+package android.os;
+
+public class Handler {}
+"""),
+ java("""
+package android.content;
+
+public class ServiceConnection {}
+"""),
+ java("""
+package android.os;
+
+public enum UserHandle {
+ ALL
+}
+"""),
+ java(
+ """
+package android.content;
+import android.os.UserHandle;
+import android.os.Handler;
+import android.os.Looper;
+import java.util.concurrent.Executor;
+
+public class Context {
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
+ public void registerReceiverAsUser(
+ BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {}
+ public void registerReceiverForAllUsers(
+ BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
+ Handler scheduler) {}
+ public void sendBroadcast(Intent intent) {}
+ public void sendBroadcast(Intent intent, String receiverPermission) {}
+ public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
+ public void bindService(Intent intent) {}
+ public void bindServiceAsUser(
+ Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
+ public void unbindService(ServiceConnection connection) {}
+ public Looper getMainLooper() { return null; }
+ public Executor getMainExecutor() { return null; }
+ public Handler getMainThreadHandler() { return null; }
+ public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
+ public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+}
+"""
+ ),
+ java(
+ """
+package android.app;
+import android.content.Context;
+
+public class Activity extends Context {}
+"""
+ ),
+ java(
+ """
+package android.graphics;
+
+public class Bitmap {
+ public enum Config {
+ ARGB_8888,
+ RGB_565,
+ HARDWARE
+ }
+ public static Bitmap createBitmap(int width, int height, Config config) {
+ return null;
+ }
+}
+"""
+ ),
+ java("""
+package android.content;
+
+public class BroadcastReceiver {}
+"""),
+ java("""
+package android.content;
+
+public class IntentFilter {}
+"""),
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ ),
+ )
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
similarity index 61%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
index bf685f7..564afcb 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class BindServiceViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = BindServiceViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- BindServiceViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(BindServiceViaContextDetector.ISSUE)
private val explanation = "Binding or unbinding services are synchronous calls"
@Test
fun testBindService() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -49,17 +49,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testBindServiceAsUser() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -73,17 +76,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testUnbindService() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -96,45 +102,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(BindServiceViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.UserHandle;
-
- public class Context {
- public void bindService(Intent intent) {};
- public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
- UserHandle userHandle) {};
- public void unbindService(ServiceConnection connection) {};
- }
- """
- )
-
- private val serviceConnectionStub: TestFile = java(
- """
- package android.content;
-
- public class ServiceConnection {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
similarity index 62%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index da010212f2..06aee8e 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -1,26 +1,43 @@
+/*
+ * 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.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- BroadcastSentViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
@Test
fun testSendBroadcast() {
- lint().files(
- TestFiles.java(
- """
+ println(stubs.size)
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
@@ -31,21 +48,25 @@
}
}
"""
- ).indented(),
- *stubs)
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testSendBroadcastAsUser() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
import android.os.UserHandle;
@@ -56,21 +77,26 @@
context.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testSendBroadcastInActivity() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.app.Activity;
import android.os.UserHandle;
@@ -82,21 +108,26 @@
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectWarningCount(1)
.expectContains(
- "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
- "Context, use com.android.systemui.broadcast.BroadcastSender instead.")
+ "Please don't call sendBroadcast/sendBroadcastAsUser directly on " +
+ "Context, use com.android.systemui.broadcast.BroadcastSender instead."
+ )
}
@Test
fun testNoopIfNoCall() {
- lint().files(
- TestFiles.java(
- """
+ lint()
+ .files(
+ TestFiles.java(
+ """
package test.pkg;
import android.content.Context;
@@ -106,45 +137,15 @@
context.startActivity(intent);
}
}
- """).indented(),
- *stubs)
+ """
+ )
+ .indented(),
+ *stubs
+ )
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.UserHandle;
-
- public class Context {
- public void sendBroadcast(Intent intent) {};
- public void sendBroadcast(Intent intent, String receiverPermission) {};
- public void sendBroadcastAsUser(Intent intent, UserHandle userHandle,
- String permission) {};
- }
- """
- )
-
- private val activityStub: TestFile = java(
- """
- package android.app;
- import android.content.Context;
-
- public class Activity extends Context {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, activityStub, userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
similarity index 64%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
index ec761cd..c55f399 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -17,13 +17,13 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = GetMainLooperViaContextDetector()
@@ -35,7 +35,8 @@
@Test
fun testGetMainThreadHandler() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -48,17 +49,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testGetMainLooper() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -71,17 +75,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testGetMainExecutor() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -94,42 +101,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(GetMainLooperViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
-
- public class Context {
- public Looper getMainLooper() { return null; };
- public Executor getMainExecutor() { return null; };
- public Handler getMainThreadHandler() { return null; };
- }
- """
- )
-
- private val looperStub: TestFile = java(
- """
- package android.os;
-
- public class Looper {}
- """
- )
-
- private val handlerStub: TestFile = java(
- """
- package android.os;
-
- public class Handler {}
- """
- )
-
- private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
new file mode 100644
index 0000000..6b9f88f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class NonInjectedServiceDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = NonInjectedServiceDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+ override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
+
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+
+ public class TestClass1 {
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService("user");
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains("Use @Inject to get the handle")
+ }
+
+ @Test
+ fun testGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass2 {
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains("Use @Inject to get the handle")
+ }
+
+ private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
similarity index 60%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 76c0519..802ceba 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -17,26 +17,26 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
- override fun getIssues(): List<Issue> = listOf(
- RegisterReceiverViaContextDetector.ISSUE)
+ override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
@Test
fun testRegisterReceiver() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -51,17 +51,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testRegisterReceiverAsUser() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -79,17 +82,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
@Test
fun testRegisterReceiverForAllUsers() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
package test.pkg;
@@ -107,65 +113,15 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(RegisterReceiverViaContextDetector.ISSUE)
- .run()
- .expectWarningCount(1)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
}
- private val contextStub: TestFile = java(
- """
- package android.content;
- import android.os.Handler;
- import android.os.UserHandle;
-
- public class Context {
- public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
- int flags) {};
- public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
- IntentFilter filter, String broadcastPermission, Handler scheduler) {};
- public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
- String broadcastPermission, Handler scheduler) {};
- }
- """
- )
-
- private val broadcastReceiverStub: TestFile = java(
- """
- package android.content;
-
- public class BroadcastReceiver {}
- """
- )
-
- private val intentFilterStub: TestFile = java(
- """
- package android.content;
-
- public class IntentFilter {}
- """
- )
-
- private val handlerStub: TestFile = java(
- """
- package android.os;
-
- public class Handler {}
- """
- )
-
- private val userHandleStub: TestFile = java(
- """
- package android.os;
-
- public enum UserHandle {
- ALL
- }
- """
- )
-
- private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
- userHandleStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
similarity index 74%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 2738f04..e265837 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -1,13 +1,29 @@
+/*
+ * 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.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
+@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -134,61 +150,5 @@
.expectClean()
}
- private val activityManagerStub: TestFile =
- java(
- """
- package android.app;
-
- public class ActivityManager {
- public static int getCurrentUser() {};
- }
- """
- )
-
- private val userManagerStub: TestFile =
- java(
- """
- package android.os;
- import android.content.pm.UserInfo;
- import android.annotation.UserIdInt;
-
- public class UserManager {
- public UserInfo getUserInfo(@UserIdInt int userId) {};
- }
- """
- )
-
- private val userIdIntStub: TestFile =
- java(
- """
- package android.annotation;
-
- public @interface UserIdInt {}
- """
- )
-
- private val userInfoStub: TestFile =
- java(
- """
- package android.content.pm;
-
- public class UserInfo {}
- """
- )
-
- private val userTrackerStub: TestFile =
- java(
- """
- package com.android.systemui.settings;
- import android.content.pm.UserInfo;
-
- public interface UserTracker {
- public int getUserId();
- public UserInfo getUserInfo();
- }
- """
- )
-
- private val stubs =
- arrayOf(activityManagerStub, userManagerStub, userIdIntStub, userInfoStub, userTrackerStub)
+ private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
similarity index 70%
rename from packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
rename to packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 890f2b8..fd6ab09 100644
--- a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -17,7 +17,6 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
@@ -36,7 +35,8 @@
@Test
fun testSoftwareBitmap() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
import android.graphics.Bitmap;
@@ -48,17 +48,20 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(SoftwareBitmapDetector.ISSUE)
- .run()
- .expectWarningCount(2)
- .expectContains(explanation)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(2)
+ .expectContains(explanation)
}
@Test
fun testHardwareBitmap() {
- lint().files(
+ lint()
+ .files(
TestFiles.java(
"""
import android.graphics.Bitmap;
@@ -69,29 +72,14 @@
}
}
"""
- ).indented(),
- *stubs)
- .issues(SoftwareBitmapDetector.ISSUE)
- .run()
- .expectWarningCount(0)
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(0)
}
- private val bitmapStub: TestFile = java(
- """
- package android.graphics;
-
- public class Bitmap {
- public enum Config {
- ARGB_8888,
- RGB_565,
- HARDWARE
- }
- public static Bitmap createBitmap(int width, int height, Config config) {
- return null;
- }
- }
- """
- )
-
- private val stubs = arrayOf(bitmapStub)
+ private val stubs = androidStubs
}
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
index 3175dcf..4d94bab 100644
--- 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
@@ -17,8 +17,6 @@
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
@@ -50,10 +48,8 @@
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
@@ -62,6 +58,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.ui.compose.load
import com.android.systemui.compose.SysUiOutlinedButton
import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@
remember(viewModel.iconResourceId) {
val drawable =
checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+ val size = with(density) { 20.dp.toPx() }.toInt()
drawable
.toBitmap(
- size = with(density) { 20.dp.toPx() }.toInt(),
- tintColor = Color.White,
+ width = size,
+ height = size,
)
.asImageBitmap()
}
@@ -392,32 +390,3 @@
),
)
}
-
-/**
- * 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/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 01e5d86..1e74c3d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -39,19 +39,19 @@
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- fun createClock(id: ClockId): Clock
+ fun createClock(id: ClockId): ClockController
/** A static thumbnail for rendering in some examples */
fun getClockThumbnail(id: ClockId): Drawable?
}
/** Interface for controlling an active clock */
-interface Clock {
+interface ClockController {
/** A small version of the clock, appropriate for smaller viewports */
- val smallClock: View
+ val smallClock: ClockFaceController
/** A large version of the clock, appropriate when a bigger viewport is available */
- val largeClock: View
+ val largeClock: ClockFaceController
/** Events that clocks may need to respond to */
val events: ClockEvents
@@ -61,7 +61,7 @@
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- events.onColorPaletteChanged(resources, true, true)
+ events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
events.onTimeTick()
@@ -71,10 +71,19 @@
fun dump(pw: PrintWriter) { }
}
+/** Interface for a specific clock face version rendered by the clock */
+interface ClockFaceController {
+ /** View that renders the clock face */
+ val view: View
+
+ /** Events specific to this clock face */
+ val events: ClockFaceEvents
+}
+
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call every time tick */
- fun onTimeTick()
+ fun onTimeTick() { }
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) { }
@@ -89,11 +98,7 @@
fun onFontSettingChanged() { }
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(
- resources: Resources,
- smallClockIsDark: Boolean,
- largeClockIsDark: Boolean
- ) { }
+ fun onColorPaletteChanged(resources: Resources) { }
}
/** Methods which trigger various clock animations */
@@ -111,6 +116,12 @@
fun charge() { }
}
+/** Events that have specific data about the related face */
+interface ClockFaceEvents {
+ /** Region Darkness specific to the clock face */
+ fun onRegionDarknessChanged(isDark: Boolean) { }
+}
+
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
index 506ccf3..5f6f11c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
@@ -67,7 +67,6 @@
*
* @param triggerBack if back will be triggered in current state.
*/
- // TODO(b/247883311): Remove default impl once SwipeBackGestureHandler overrides this.
- default void setTriggerBack(boolean triggerBack) {}
+ void setTriggerBack(boolean triggerBack);
}
}
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 6986961..9d063e9 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -21,11 +21,12 @@
android:paddingEnd="0dp">
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
<corners android:radius="32dp" />
</shape>
</item>
<item
+ android:id="@+id/user_switcher_key_down"
android:drawable="@drawable/ic_ksh_key_down"
android:gravity="end|center_vertical"
android:width="32dp"
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 1ce106e..2261ae8 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -46,7 +46,7 @@
>
<com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
+ android:id="@id/multi_user_switch"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle"
@@ -61,7 +61,7 @@
</com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
+ android:id="@id/settings_button_container"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle"
@@ -85,7 +85,7 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
<com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
+ android:id="@id/pm_lite"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle_color"
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
new file mode 100644
index 0000000..29832a0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/mobile_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_height="17dp"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/mobile_in"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ android:paddingEnd="2dp"
+ />
+ <ImageView
+ android:id="@+id/mobile_out"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_up"
+ android:paddingEnd="2dp"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="2.5dp"
+ android:paddingEnd="1dp"
+ android:visibility="gone" />
+ <Space
+ android:id="@+id/mobile_roaming_space"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/roaming_icon_start_padding"
+ android:visibility="gone"
+ />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+ <com.android.systemui.statusbar.AnimatedImageView
+ android:id="@+id/mobile_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ systemui:hasOverlappingRendering="false"
+ />
+ <ImageView
+ android:id="@+id/mobile_roaming"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_roaming_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming_large"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 0000000..1b38fd2
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mobile_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical" >
+
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res-keyguard/values/ids.xml b/packages/SystemUI/res-keyguard/values/ids.xml
new file mode 100644
index 0000000..0dff4ff
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/ids.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~
+ -->
+
+<resources>
+ <item type="id" name="header_footer_views_added_tag_key" />
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index a1d1266..b86929e 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -25,7 +25,7 @@
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
- <item name="android:textSize">14dp</item>
+ <item name="android:textSize">14sp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingLeft">12dp</item>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 8df8c49..6120863 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,7 +59,7 @@
</LinearLayout>
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/start_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
@@ -71,7 +71,7 @@
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="gone" />
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/end_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index 226bc6a..e474938 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -49,6 +49,8 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
+ android:focusable="false"
+ android:clickable="false"
android:gravity="center"
android:paddingBottom="@*android:dimen/chooser_view_spacing"
android:paddingLeft="24dp"
diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
index a2b3c40..31baf26 100644
--- a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
+++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml
@@ -23,8 +23,9 @@
>
<FrameLayout
+ android:id="@+id/media_projection_recent_tasks_container"
android:layout_width="match_parent"
- android:layout_height="256dp">
+ android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_projection_recent_tasks_recycler"
diff --git a/packages/SystemUI/res/layout/media_projection_task_item.xml b/packages/SystemUI/res/layout/media_projection_task_item.xml
index 75f73cd..cfa586f 100644
--- a/packages/SystemUI/res/layout/media_projection_task_item.xml
+++ b/packages/SystemUI/res/layout/media_projection_task_item.xml
@@ -18,7 +18,9 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:clickable="true"
+ >
<ImageView
android:id="@+id/task_icon"
@@ -27,12 +29,12 @@
android:layout_margin="8dp"
android:importantForAccessibility="no" />
- <!-- TODO(b/240924926) use a custom view that will handle thumbnail cropping correctly -->
- <!-- TODO(b/240924926) dynamically change the view size based on the screen size -->
- <ImageView
+ <!-- This view size will be calculated in runtime -->
+ <com.android.systemui.mediaprojection.appselector.view.MediaProjectionTaskView
android:id="@+id/task_thumbnail"
- android:layout_width="100dp"
- android:layout_height="216dp"
- android:scaleType="centerCrop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:focusable="false"
/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index d886806..ae8e38e 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -16,7 +16,7 @@
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
is the root view of a window, we cannot change the root view's margins.) -->
<!-- Alphas start as 0 because the view will be animated in. -->
-<FrameLayout
+<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/media_ttt_sender_chip"
@@ -97,4 +97,4 @@
/>
</LinearLayout>
-</FrameLayout>
+</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index e079fd3..21d12c2 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -29,6 +29,7 @@
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
+ android:background="@drawable/media_ttt_chip_background_receiver"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index c29e11b..c134c8e 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -35,12 +35,6 @@
android:visibility="gone"
android:elevation="7dp"
android:src="@android:color/white"/>
- <com.android.systemui.screenshot.ScreenshotSelectorView
- android:id="@+id/screenshot_selector"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:pointerIcon="crosshair"/>
<include layout="@layout/screenshot_static"
android:id="@+id/screenshot_static"/>
</com.android.systemui.screenshot.ScreenshotView>
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b3..d6c63eb 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
-->
<com.android.systemui.statusbar.StatusBarMobileView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/mobile_combo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" >
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/mobile_group"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
- <FrameLayout
- android:id="@+id/inout_container"
- android:layout_height="17dp"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical">
- <ImageView
- android:id="@+id/mobile_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/mobile_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingStart="2.5dp"
- android:paddingEnd="1dp"
- android:visibility="gone" />
- <Space
- android:id="@+id/mobile_roaming_space"
- android:layout_height="match_parent"
- android:layout_width="@dimen/roaming_icon_start_padding"
- android:visibility="gone"
- />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical">
- <com.android.systemui.statusbar.AnimatedImageView
- android:id="@+id/mobile_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- systemui:hasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/mobile_roaming"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_roaming_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming_large"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
</com.android.systemui.statusbar.StatusBarMobileView>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9d7b01c..49ef330 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -59,4 +59,5 @@
<dimen name="large_dialog_width">348dp</dimen>
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 5dcbeb5..599bf30 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,6 +68,7 @@
<dimen name="qs_security_footer_background_inset">0dp</dimen>
<dimen name="qs_panel_padding_top">8dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">472dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 37549c9..9188ce0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -337,9 +337,6 @@
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
- <!-- Whether or not the notifications should always fade as they are dismissed. -->
- <bool name="config_fadeNotificationsOnDismiss">false</bool>
-
<!-- Whether or not the fade on the notification is based on the amount that it has been swiped
off-screen. -->
<bool name="config_fadeDependingOnAmountSwiped">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f7019dc..778dd45 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -559,7 +559,8 @@
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
- <dimen name="qs_panel_padding_top">80dp</dimen>
+ <dimen name="qs_panel_padding_top">48dp</dimen>
+ <dimen name="qs_panel_padding_top_combined_headers">80dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -1056,9 +1057,8 @@
<!-- Media tap-to-transfer chip for receiver device -->
<dimen name="media_ttt_chip_size_receiver">100dp</dimen>
<dimen name="media_ttt_icon_size_receiver">95dp</dimen>
- <!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
- the circular chip. -->
- <dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <!-- Add some padding for the generic icon so it doesn't go all the way to the border. -->
+ <dimen name="media_ttt_generic_icon_padding">12dp</dimen>
<dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
@@ -1457,6 +1457,9 @@
<dimen name="media_projection_app_selector_icon_size">32dp</dimen>
<dimen name="media_projection_app_selector_recents_padding">16dp</dimen>
<dimen name="media_projection_app_selector_loader_size">32dp</dimen>
+ <dimen name="media_projection_app_selector_task_rounded_corners">10dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_size">24dp</dimen>
+ <dimen name="media_projection_app_selector_task_icon_margin">8dp</dimen>
<!-- Dream overlay related dimensions -->
<dimen name="dream_overlay_status_bar_height">60dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index f22e797..7ca42f7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -188,5 +188,11 @@
<item type="id" name="face_scanning_anim"/>
<item type="id" name="qqs_tile_layout"/>
+
+ <!-- The buttons in the Quick Settings footer actions.-->
+ <item type="id" name="multi_user_switch"/>
+ <item type="id" name="pm_lite"/>
+ <item type="id" name="settings_button_container"/>
+
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53f1227..9b9111f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -246,6 +246,16 @@
<string name="screenrecord_start_label">Start Recording?</string>
<!-- Message reminding the user that sensitive information may be captured during a screen recording [CHAR_LIMIT=NONE]-->
<string name="screenrecord_description">While recording, Android System can capture any sensitive information that\u2019s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio.</string>
+ <!-- Dropdown option to record the entire screen [CHAR_LIMIT=30]-->
+ <string name="screenrecord_option_entire_screen">Record entire screen</string>
+ <!-- Dropdown option to record a single app [CHAR_LIMIT=30]-->
+ <string name="screenrecord_option_single_app">Record a single app</string>
+ <!-- Message reminding the user that sensitive information may be captured during a full screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+ <string name="screenrecord_warning_entire_screen">While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+ <!-- Message reminding the user that sensitive information may be captured during a single app screen recording for the updated dialog that includes partial screen sharing option [CHAR_LIMIT=350]-->
+ <string name="screenrecord_warning_single_app">While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+ <!-- Button to start a screen recording in the updated screen record dialog that allows to select an app to record [CHAR LIMIT=50]-->
+ <string name="screenrecord_start_recording">Start recording</string>
<!-- Label for a switch to enable recording audio [CHAR LIMIT=NONE]-->
<string name="screenrecord_audio_label">Record audio</string>
<!-- Label for the option to record audio from the device [CHAR LIMIT=NONE]-->
@@ -958,7 +968,26 @@
<!-- Media projection permission dialog warning title. [CHAR LIMIT=NONE] -->
<string name="media_projection_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g>?</string>
- <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
+ <!-- Media projection permission dialog title. [CHAR LIMIT=NONE] -->
+ <string name="media_projection_permission_dialog_title">Allow <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> to share or record?</string>
+
+ <!-- Media projection permission dropdown option for capturing the whole screen. [CHAR LIMIT=30] -->
+ <string name="media_projection_permission_dialog_option_entire_screen">Entire screen</string>
+
+ <!-- Media projection permission dropdown option for capturing single app. [CHAR LIMIT=30] -->
+ <string name="media_projection_permission_dialog_option_single_app">A single app</string>
+
+ <!-- Media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
+ <string name="media_projection_permission_dialog_warning_entire_screen">When you\'re sharing, recording, or casting, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+ <!-- Media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
+ <string name="media_projection_permission_dialog_warning_single_app">When you\'re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+ <!-- Media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
+ <string name="media_projection_permission_dialog_continue">Continue</string>
+
+ <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
+ <string name="media_projection_permission_app_selector_title">Share or record an app</string>
<!-- The text to clear all notifications. [CHAR LIMIT=60] -->
<string name="clear_all_notifications_text">Clear all</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index ffab3cd..12e0b9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.animation
import android.view.View
+import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.ViewGroup
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -58,9 +59,15 @@
// progress == 0 -> -translationMax
// progress == 1 -> 0
val xTrans = (progress - 1f) * translationMax
+ val rtlMultiplier =
+ if (rootView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ -1
+ } else {
+ 1
+ }
viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
if (shouldBeAnimated()) {
- view.get()?.translationX = xTrans * direction.multiplier
+ view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
}
}
}
@@ -90,7 +97,7 @@
/** Direction of the animation. */
enum class Direction(val multiplier: Float) {
- LEFT(-1f),
- RIGHT(1f),
+ START(-1f),
+ END(1f),
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index c2e7445..860a5da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -58,6 +58,7 @@
private var lastOnTextChanged: CharSequence? = null
private var lastInvalidate: CharSequence? = null
private var lastTimeZoneChange: CharSequence? = null
+ private var lastAnimationCall: CharSequence? = null
private val time = Calendar.getInstance()
@@ -222,6 +223,7 @@
}
fun animateAppearOnLockscreen() {
+ lastAnimationCall = "${getTimestamp()} call=animateAppearOnLockscreen"
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -246,6 +248,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
+ lastAnimationCall = "${getTimestamp()} call=animateFoldAppear"
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -272,6 +275,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
+ lastAnimationCall = "${getTimestamp()} call=animateCharge"
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -295,6 +299,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
+ lastAnimationCall = "${getTimestamp()} call=animateDoze"
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -408,6 +413,11 @@
pw.println(" lastTimeZoneChange=$lastTimeZoneChange")
pw.println(" currText=$text")
pw.println(" currTimeContextDesc=$contentDescription")
+ pw.println(" lastAnimationCall=$lastAnimationCall")
+ pw.println(" dozingWeightInternal=$dozingWeightInternal")
+ pw.println(" lockScreenWeightInternal=$lockScreenWeightInternal")
+ pw.println(" dozingColor=$dozingColor")
+ pw.println(" lockScreenColor=$lockScreenColor")
pw.println(" time=$time")
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 38a3124..f03fee4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -22,7 +22,7 @@
import android.provider.Settings
import android.util.Log
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
@@ -33,7 +33,7 @@
import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
-private val DEBUG = true
+private const val DEBUG = true
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
@@ -130,6 +130,10 @@
}
availableClocks[id] = ClockInfo(clock, provider)
+ if (DEBUG) {
+ Log.i(TAG, "Added ${clock.clockId}")
+ }
+
if (currentId == id) {
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
@@ -143,6 +147,9 @@
val currentId = currentClockId
for (clock in provider.getClocks()) {
availableClocks.remove(clock.clockId)
+ if (DEBUG) {
+ Log.i(TAG, "Removed ${clock.clockId}")
+ }
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
@@ -161,7 +168,7 @@
fun getClockThumbnail(clockId: ClockId): Drawable? =
availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
- fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+ fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
fun registerClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.add(listener)
@@ -169,11 +176,14 @@
fun unregisterClockChangeListener(listener: ClockChangeListener) =
clockChangeListeners.remove(listener)
- fun createCurrentClock(): Clock {
+ fun createCurrentClock(): ClockController {
val clockId = currentClockId
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
+ if (DEBUG) {
+ Log.i(TAG, "Rendering clock $clockId")
+ }
return clock
} else {
Log.e(TAG, "Clock $clockId not found; using default")
@@ -183,7 +193,7 @@
return createClock(DEFAULT_CLOCK_ID)!!
}
- private fun createClock(clockId: ClockId): Clock? =
+ private fun createClock(clockId: ClockId): ClockController? =
availableClocks[clockId]?.provider?.createClock(clockId)
private data class ClockInfo(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
new file mode 100644
index 0000000..b887951
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -0,0 +1,240 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Color
+import android.icu.text.NumberFormat
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.shared.R
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+private val TAG = DefaultClockController::class.simpleName
+
+/**
+ * Controls the default clock visuals.
+ *
+ * This serves as an adapter between the clock interface and the AnimatableClockView used by the
+ * existing lockscreen clock.
+ */
+class DefaultClockController(
+ ctx: Context,
+ private val layoutInflater: LayoutInflater,
+ private val resources: Resources,
+) : ClockController {
+ override val smallClock: DefaultClockFaceController
+ override val largeClock: LargeClockFaceController
+ private val clocks: List<AnimatableClockView>
+
+ private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
+ private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
+ private val burmeseLineSpacing =
+ resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
+ private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+
+ override val events: DefaultClockEvents
+ override lateinit var animations: DefaultClockAnimations
+ private set
+
+ init {
+ val parent = FrameLayout(ctx)
+ smallClock =
+ DefaultClockFaceController(
+ layoutInflater.inflate(R.layout.clock_default_small, parent, false)
+ as AnimatableClockView
+ )
+ largeClock =
+ LargeClockFaceController(
+ layoutInflater.inflate(R.layout.clock_default_large, parent, false)
+ as AnimatableClockView
+ )
+ clocks = listOf(smallClock.view, largeClock.view)
+
+ events = DefaultClockEvents()
+ animations = DefaultClockAnimations(0f, 0f)
+ events.onLocaleChanged(Locale.getDefault())
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ largeClock.recomputePadding()
+ animations = DefaultClockAnimations(dozeFraction, foldFraction)
+ events.onColorPaletteChanged(resources)
+ events.onTimeZoneChanged(TimeZone.getDefault())
+ events.onTimeTick()
+ }
+
+ open inner class DefaultClockFaceController(
+ override val view: AnimatableClockView,
+ ) : ClockFaceController {
+ // MAGENTA is a placeholder, and will be assigned correctly in initialize
+ private var currentColor = Color.MAGENTA
+ private var isRegionDark = false
+
+ init {
+ view.setColors(currentColor, currentColor)
+ }
+
+ override val events =
+ object : ClockFaceEvents {
+ override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+ this@DefaultClockFaceController.isRegionDark = isRegionDark
+ updateColor()
+ }
+ }
+
+ fun updateColor() {
+ val color =
+ if (isRegionDark) {
+ resources.getColor(android.R.color.system_accent1_100)
+ } else {
+ resources.getColor(android.R.color.system_accent2_600)
+ }
+
+ if (currentColor == color) {
+ return
+ }
+
+ currentColor = color
+ view.setColors(DOZE_COLOR, color)
+ view.animateAppearOnLockscreen()
+ }
+ }
+
+ inner class LargeClockFaceController(
+ view: AnimatableClockView,
+ ) : DefaultClockFaceController(view) {
+ fun recomputePadding() {
+ val lp = view.getLayoutParams() as FrameLayout.LayoutParams
+ lp.topMargin = (-0.5f * view.bottom).toInt()
+ view.setLayoutParams(lp)
+ }
+ }
+
+ inner class DefaultClockEvents : ClockEvents {
+ override fun onTimeTick() = clocks.forEach { it.refreshTime() }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) =
+ clocks.forEach { it.refreshFormat(is24Hr) }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) =
+ clocks.forEach { it.onTimeZoneChanged(timeZone) }
+
+ override fun onFontSettingChanged() {
+ smallClock.view.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ largeClock.view.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ largeClock.recomputePadding()
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {
+ largeClock.updateColor()
+ smallClock.updateColor()
+ }
+
+ override fun onLocaleChanged(locale: Locale) {
+ val nf = NumberFormat.getInstance(locale)
+ if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
+ clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
+ } else {
+ clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
+ }
+
+ clocks.forEach { it.refreshFormat() }
+ }
+ }
+
+ inner class DefaultClockAnimations(
+ dozeFraction: Float,
+ foldFraction: Float,
+ ) : ClockAnimations {
+ private var foldState = AnimationState(0f)
+ private var dozeState = AnimationState(0f)
+
+ init {
+ dozeState = AnimationState(dozeFraction)
+ foldState = AnimationState(foldFraction)
+
+ if (foldState.isActive) {
+ clocks.forEach { it.animateFoldAppear(false) }
+ } else {
+ clocks.forEach { it.animateDoze(dozeState.isActive, false) }
+ }
+ }
+
+ override fun enter() {
+ if (!dozeState.isActive) {
+ clocks.forEach { it.animateAppearOnLockscreen() }
+ }
+ }
+
+ override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+
+ override fun fold(fraction: Float) {
+ val (hasChanged, hasJumped) = foldState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateFoldAppear(!hasJumped) }
+ }
+ }
+
+ override fun doze(fraction: Float) {
+ val (hasChanged, hasJumped) = dozeState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
+ }
+ }
+ }
+
+ private class AnimationState(
+ var fraction: Float,
+ ) {
+ var isActive: Boolean = fraction < 0.5f
+ fun update(newFraction: Float): Pair<Boolean, Boolean> {
+ val wasActive = isActive
+ val hasJumped =
+ (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f)
+ isActive = newFraction > fraction
+ fraction = newFraction
+ return Pair(wasActive != isActive, hasJumped)
+ }
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.print("smallClock=")
+ smallClock.view.dump(pw)
+
+ pw.print("largeClock=")
+ largeClock.view.dump(pw)
+ }
+
+ companion object {
+ @VisibleForTesting const val DOZE_COLOR = Color.WHITE
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 19ac2e4..6627c5d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,24 +15,14 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.Color
import android.graphics.drawable.Drawable
-import android.icu.text.NumberFormat
-import android.util.TypedValue
import android.view.LayoutInflater
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.shared.R
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
import javax.inject.Inject
private val TAG = DefaultClockProvider::class.simpleName
@@ -48,11 +38,12 @@
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
- override fun createClock(id: ClockId): Clock {
+ override fun createClock(id: ClockId): ClockController {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- return DefaultClock(ctx, layoutInflater, resources)
+
+ return DefaultClockController(ctx, layoutInflater, resources)
}
override fun getClockThumbnail(id: ClockId): Drawable? {
@@ -64,190 +55,3 @@
return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
}
}
-
-/**
- * Controls the default clock visuals.
- *
- * This serves as an adapter between the clock interface and the
- * AnimatableClockView used by the existing lockscreen clock.
- */
-class DefaultClock(
- ctx: Context,
- private val layoutInflater: LayoutInflater,
- private val resources: Resources
-) : Clock {
- override val smallClock: AnimatableClockView
- override val largeClock: AnimatableClockView
- private val clocks get() = listOf(smallClock, largeClock)
-
- private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
- private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
- private val burmeseLineSpacing =
- resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
- private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
-
- override val events: ClockEvents
- override lateinit var animations: ClockAnimations
- private set
-
- private var smallRegionDarkness = false
- private var largeRegionDarkness = false
-
- init {
- val parent = FrameLayout(ctx)
-
- smallClock = layoutInflater.inflate(
- R.layout.clock_default_small,
- parent,
- false
- ) as AnimatableClockView
-
- largeClock = layoutInflater.inflate(
- R.layout.clock_default_large,
- parent,
- false
- ) as AnimatableClockView
-
- events = DefaultClockEvents()
- animations = DefaultClockAnimations(0f, 0f)
-
- events.onLocaleChanged(Locale.getDefault())
-
- // DOZE_COLOR is a placeholder, and will be assigned correctly in initialize
- clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
- }
-
- override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- recomputePadding()
- animations = DefaultClockAnimations(dozeFraction, foldFraction)
- events.onColorPaletteChanged(resources, true, true)
- events.onTimeZoneChanged(TimeZone.getDefault())
- events.onTimeTick()
- }
-
- inner class DefaultClockEvents() : ClockEvents {
- override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
- override fun onTimeFormatChanged(is24Hr: Boolean) =
- clocks.forEach { it.refreshFormat(is24Hr) }
-
- override fun onTimeZoneChanged(timeZone: TimeZone) =
- clocks.forEach { it.onTimeZoneChanged(timeZone) }
-
- override fun onFontSettingChanged() {
- smallClock.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- largeClock.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- recomputePadding()
- }
-
- override fun onColorPaletteChanged(
- resources: Resources,
- smallClockIsDark: Boolean,
- largeClockIsDark: Boolean
- ) {
- if (smallRegionDarkness != smallClockIsDark) {
- smallRegionDarkness = smallClockIsDark
- updateClockColor(smallClock, smallClockIsDark)
- }
- if (largeRegionDarkness != largeClockIsDark) {
- largeRegionDarkness = largeClockIsDark
- updateClockColor(largeClock, largeClockIsDark)
- }
- }
-
- override fun onLocaleChanged(locale: Locale) {
- val nf = NumberFormat.getInstance(locale)
- if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
- clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
- } else {
- clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
- }
-
- clocks.forEach { it.refreshFormat() }
- }
- }
-
- inner class DefaultClockAnimations(
- dozeFraction: Float,
- foldFraction: Float
- ) : ClockAnimations {
- private var foldState = AnimationState(0f)
- private var dozeState = AnimationState(0f)
-
- init {
- dozeState = AnimationState(dozeFraction)
- foldState = AnimationState(foldFraction)
-
- if (foldState.isActive) {
- clocks.forEach { it.animateFoldAppear(false) }
- } else {
- clocks.forEach { it.animateDoze(dozeState.isActive, false) }
- }
- }
-
- override fun enter() {
- if (!dozeState.isActive) {
- clocks.forEach { it.animateAppearOnLockscreen() }
- }
- }
-
- override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
-
- override fun fold(fraction: Float) {
- val (hasChanged, hasJumped) = foldState.update(fraction)
- if (hasChanged) {
- clocks.forEach { it.animateFoldAppear(!hasJumped) }
- }
- }
-
- override fun doze(fraction: Float) {
- val (hasChanged, hasJumped) = dozeState.update(fraction)
- if (hasChanged) {
- clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
- }
- }
- }
-
- private class AnimationState(
- var fraction: Float
- ) {
- var isActive: Boolean = fraction < 0.5f
- fun update(newFraction: Float): Pair<Boolean, Boolean> {
- val wasActive = isActive
- val hasJumped = (fraction == 0f && newFraction == 1f) ||
- (fraction == 1f && newFraction == 0f)
- isActive = newFraction > fraction
- fraction = newFraction
- return Pair(wasActive != isActive, hasJumped)
- }
- }
-
- private fun updateClockColor(clock: AnimatableClockView, isRegionDark: Boolean) {
- val color = if (isRegionDark) {
- resources.getColor(android.R.color.system_accent1_100)
- } else {
- resources.getColor(android.R.color.system_accent2_600)
- }
- clock.setColors(DOZE_COLOR, color)
- clock.animateAppearOnLockscreen()
- }
-
- private fun recomputePadding() {
- val lp = largeClock.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * largeClock.bottom).toInt()
- largeClock.setLayoutParams(lp)
- }
-
- override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
-
- companion object {
- @VisibleForTesting const val DOZE_COLOR = Color.WHITE
- private const val FORMAT_NUMBER = 1234567890
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index c7d5ffe..023ef31 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -217,12 +217,16 @@
unregisterSamplingListener();
mSamplingListenerRegistered = true;
SurfaceControl wrappedStopLayer = wrap(stopLayerControl);
+
+ // pass this to background thread to avoid empty Rect race condition
+ final Rect boundsCopy = new Rect(mSamplingRequestBounds);
+
mBackgroundExecutor.execute(() -> {
if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
return;
}
mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
- wrappedStopLayer, mSamplingRequestBounds);
+ wrappedStopLayer, boundsCopy);
});
mRegisteredSamplingBounds.set(mSamplingRequestBounds);
mRegisteredStopLayer = stopLayerControl;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e77c650..2b2b05ce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -81,11 +81,6 @@
*/
void stopScreenPinning() = 17;
- /*
- * Notifies that the swipe-to-home (recents animation) is finished.
- */
- void notifySwipeToHomeFinished() = 23;
-
/**
* Notifies that quickstep will switch to a new task
* @param rotation indicates which Surface.Rotation the gesture was started in
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
new file mode 100644
index 0000000..72f8b7b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -0,0 +1,209 @@
+package com.android.systemui.shared.recents.utilities;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+/**
+ * Utility class to position the thumbnail in the TaskView
+ */
+public class PreviewPositionHelper {
+
+ public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+
+ // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
+ private final RectF mClippedInsets = new RectF();
+ private final Matrix mMatrix = new Matrix();
+ private boolean mIsOrientationChanged;
+
+ public Matrix getMatrix() {
+ return mMatrix;
+ }
+
+ public void setOrientationChanged(boolean orientationChanged) {
+ mIsOrientationChanged = orientationChanged;
+ }
+
+ public boolean isOrientationChanged() {
+ return mIsOrientationChanged;
+ }
+
+ /**
+ * Updates the matrix based on the provided parameters
+ */
+ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
+ int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+ int currentRotation, boolean isRtl) {
+ boolean isRotated = false;
+ boolean isOrientationDifferent;
+
+ int thumbnailRotation = thumbnailData.rotation;
+ int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+ RectF thumbnailClipHint = new RectF();
+ float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+ float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+ thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+
+ float scale = thumbnailData.scale;
+ final float thumbnailScale;
+
+ // Landscape vs portrait change.
+ // Note: Disable rotation in grid layout.
+ boolean windowingModeSupportsRotation =
+ thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet;
+ isOrientationDifferent = isOrientationChange(deltaRotate)
+ && windowingModeSupportsRotation;
+ if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
+ // If we haven't measured , skip the thumbnail drawing and only draw the background
+ // color
+ thumbnailScale = 0f;
+ } else {
+ // Rotate the screenshot if not in multi-window mode
+ isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
+
+ float surfaceWidth = thumbnailBounds.width() / scale;
+ float surfaceHeight = thumbnailBounds.height() / scale;
+ float availableWidth = surfaceWidth
+ - (thumbnailClipHint.left + thumbnailClipHint.right);
+ float availableHeight = surfaceHeight
+ - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+ float canvasAspect = canvasWidth / (float) canvasHeight;
+ float availableAspect = isRotated
+ ? availableHeight / availableWidth
+ : availableWidth / availableHeight;
+ boolean isAspectLargelyDifferent =
+ Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+ availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
+ if (isRotated && isAspectLargelyDifferent) {
+ // Do not rotate thumbnail if it would not improve fit
+ isRotated = false;
+ isOrientationDifferent = false;
+ }
+
+ if (isAspectLargelyDifferent) {
+ // Crop letterbox insets if insets isn't already clipped
+ thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
+ thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+ thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+ thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
+ availableWidth = surfaceWidth
+ - (thumbnailClipHint.left + thumbnailClipHint.right);
+ availableHeight = surfaceHeight
+ - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+ }
+
+ final float targetW, targetH;
+ if (isOrientationDifferent) {
+ targetW = canvasHeight;
+ targetH = canvasWidth;
+ } else {
+ targetW = canvasWidth;
+ targetH = canvasHeight;
+ }
+ float targetAspect = targetW / targetH;
+
+ // Update the clipHint such that
+ // > the final clipped position has same aspect ratio as requested by canvas
+ // > first fit the width and crop the extra height
+ // > if that will leave empty space, fit the height and crop the width instead
+ float croppedWidth = availableWidth;
+ float croppedHeight = croppedWidth / targetAspect;
+ if (croppedHeight > availableHeight) {
+ croppedHeight = availableHeight;
+ if (croppedHeight < targetH) {
+ croppedHeight = Math.min(targetH, surfaceHeight);
+ }
+ croppedWidth = croppedHeight * targetAspect;
+
+ // One last check in case the task aspect radio messed up something
+ if (croppedWidth > surfaceWidth) {
+ croppedWidth = surfaceWidth;
+ croppedHeight = croppedWidth / targetAspect;
+ }
+ }
+
+ // Update the clip hints. Align to 0,0, crop the remaining.
+ if (isRtl) {
+ thumbnailClipHint.left += availableWidth - croppedWidth;
+ if (thumbnailClipHint.right < 0) {
+ thumbnailClipHint.left += thumbnailClipHint.right;
+ thumbnailClipHint.right = 0;
+ }
+ } else {
+ thumbnailClipHint.right += availableWidth - croppedWidth;
+ if (thumbnailClipHint.left < 0) {
+ thumbnailClipHint.right += thumbnailClipHint.left;
+ thumbnailClipHint.left = 0;
+ }
+ }
+ thumbnailClipHint.bottom += availableHeight - croppedHeight;
+ if (thumbnailClipHint.top < 0) {
+ thumbnailClipHint.bottom += thumbnailClipHint.top;
+ thumbnailClipHint.top = 0;
+ } else if (thumbnailClipHint.bottom < 0) {
+ thumbnailClipHint.top += thumbnailClipHint.bottom;
+ thumbnailClipHint.bottom = 0;
+ }
+
+ thumbnailScale = targetW / (croppedWidth * scale);
+ }
+
+ if (!isRotated) {
+ mMatrix.setTranslate(
+ -thumbnailClipHint.left * scale,
+ -thumbnailClipHint.top * scale);
+ } else {
+ setThumbnailRotation(deltaRotate, thumbnailBounds);
+ }
+
+ mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
+
+ mMatrix.postScale(thumbnailScale, thumbnailScale);
+ mIsOrientationChanged = isOrientationDifferent;
+ }
+
+ private int getRotationDelta(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ /**
+ * @param deltaRotation the number of 90 degree turns from the current orientation
+ * @return {@code true} if the change in rotation results in a shift from landscape to
+ * portrait or vice versa, {@code false} otherwise
+ */
+ private boolean isOrientationChange(int deltaRotation) {
+ return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ }
+
+ private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
+ float translateX = 0;
+ float translateY = 0;
+
+ mMatrix.setRotate(90 * deltaRotate);
+ switch (deltaRotate) { /* Counter-clockwise */
+ case Surface.ROTATION_90:
+ translateX = thumbnailPosition.height();
+ break;
+ case Surface.ROTATION_270:
+ translateY = thumbnailPosition.width();
+ break;
+ case Surface.ROTATION_180:
+ translateX = thumbnailPosition.width();
+ translateY = thumbnailPosition.height();
+ break;
+ }
+ mMatrix.postTranslate(translateX, translateY);
+ }
+
+ public RectF getClippedInsets() {
+ return mClippedInsets;
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 56326e3..77a13bd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -62,6 +62,16 @@
return false; // Default
}
+ /**
+ * Compares the ratio of two quantities and returns whether that ratio is greater than the
+ * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+ * of a percentage.
+ */
+ public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+ float bound) {
+ return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+ }
+
/** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
public static float computeContrastBetweenColors(int bg, int fg) {
float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 85278dd..f2742b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,27 +43,8 @@
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
- // See IPip.aidl
- public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
- // See ISplitScreen.aidl
- public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
- // See IOneHanded.aidl
- public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
- // See IShellTransitions.aidl
- public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
- "extra_shell_shell_transitions";
- // See IStartingWindow.aidl
- public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
- "extra_shell_starting_window";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
- // See IRecentTasks.aidl
- public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
- public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IDesktopMode.aidl
- public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 74bd9c6..bb3df8f 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Handler
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
@@ -27,6 +28,7 @@
import javax.inject.Named
@Module(includes = [
+ FeatureFlagsDebugStartableModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -46,5 +48,15 @@
@Provides
@Named(ALL_FLAGS)
fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object: Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 38b5c9a..0f7e732 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,11 +16,29 @@
package com.android.systemui.flags
+import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
+import dagger.Provides
-@Module(includes = [ServerFlagReaderModule::class])
+@Module(includes = [
+ FeatureFlagsReleaseStartableModule::class,
+ ServerFlagReaderModule::class
+])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object: Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index b444f4c..9151238 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -27,7 +27,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
import com.android.systemui.statusbar.policy.BatteryController
@@ -44,18 +44,18 @@
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
open class ClockEventController @Inject constructor(
- private val statusBarStateController: StatusBarStateController,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val batteryController: BatteryController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val configurationController: ConfigurationController,
- @Main private val resources: Resources,
- private val context: Context,
- @Main private val mainExecutor: Executor,
- @Background private val bgExecutor: Executor,
- private val featureFlags: FeatureFlags
+ private val statusBarStateController: StatusBarStateController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val batteryController: BatteryController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val configurationController: ConfigurationController,
+ @Main private val resources: Resources,
+ private val context: Context,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgExecutor: Executor,
+ private val featureFlags: FeatureFlags,
) {
- var clock: Clock? = null
+ var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
@@ -69,47 +69,50 @@
private var isCharging = false
private var dozeAmount = 0f
- private var isKeyguardShowing = false
+ private var isKeyguardVisible = false
private val regionSamplingEnabled =
featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
- private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
- override fun updateColors() {
- if (regionSamplingEnabled) {
- smallClockIsDark = smallRegionSamplingInstance.currentRegionDarkness().isDark
- largeClockIsDark = largeRegionSamplingInstance.currentRegionDarkness().isDark
- } else {
- val isLightTheme = TypedValue()
- context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
- smallClockIsDark = isLightTheme.data == 0
- largeClockIsDark = isLightTheme.data == 0
- }
- clock?.events?.onColorPaletteChanged(resources, smallClockIsDark, largeClockIsDark)
+ private fun updateColors() {
+ if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
+ smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
+ largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+ } else {
+ val isLightTheme = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+ smallClockIsDark = isLightTheme.data == 0
+ largeClockIsDark = isLightTheme.data == 0
}
+
+ clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
+ clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
}
- fun updateRegionSamplers(currentClock: Clock?) {
- smallRegionSamplingInstance = createRegionSampler(
- currentClock?.smallClock,
+ private fun updateRegionSamplers(currentClock: ClockController?) {
+ smallRegionSampler?.stopRegionSampler()
+ largeRegionSampler?.stopRegionSampler()
+
+ smallRegionSampler = createRegionSampler(
+ currentClock?.smallClock?.view,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun
+ ::updateColors
)
- largeRegionSamplingInstance = createRegionSampler(
- currentClock?.largeClock,
+ largeRegionSampler = createRegionSampler(
+ currentClock?.largeClock?.view,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun
+ ::updateColors
)
- smallRegionSamplingInstance.startRegionSampler()
- largeRegionSamplingInstance.startRegionSampler()
+ smallRegionSampler!!.startRegionSampler()
+ largeRegionSampler!!.startRegionSampler()
- updateFun.updateColors()
+ updateColors()
}
protected open fun createRegionSampler(
@@ -117,25 +120,29 @@
mainExecutor: Executor?,
bgExecutor: Executor?,
regionSamplingEnabled: Boolean,
- updateFun: RegionSamplingInstance.UpdateColorCallback
+ updateColors: () -> Unit
): RegionSamplingInstance {
return RegionSamplingInstance(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun)
+ object : RegionSamplingInstance.UpdateColorCallback {
+ override fun updateColors() {
+ updateColors()
+ }
+ })
}
- lateinit var smallRegionSamplingInstance: RegionSamplingInstance
- lateinit var largeRegionSamplingInstance: RegionSamplingInstance
+ var smallRegionSampler: RegionSamplingInstance? = null
+ var largeRegionSampler: RegionSamplingInstance? = null
private var smallClockIsDark = true
private var largeClockIsDark = true
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
- updateFun.updateColors()
+ clock?.events?.onColorPaletteChanged(resources)
}
override fun onDensityOrFontScaleChanged() {
@@ -145,7 +152,7 @@
private val batteryCallback = object : BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardShowing && !isCharging && charging) {
+ if (isKeyguardVisible && !isCharging && charging) {
clock?.animations?.charge()
}
isCharging = charging
@@ -168,9 +175,9 @@
}
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
- isKeyguardShowing = showing
- if (!isKeyguardShowing) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ isKeyguardVisible = visible
+ if (!isKeyguardVisible) {
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
@@ -204,8 +211,8 @@
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
statusBarStateController.addCallback(statusBarStateListener)
- smallRegionSamplingInstance.startRegionSampler()
- largeRegionSamplingInstance.startRegionSampler()
+ smallRegionSampler?.startRegionSampler()
+ largeRegionSampler?.startRegionSampler()
}
fun unregisterListeners() {
@@ -214,8 +221,8 @@
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
statusBarStateController.removeCallback(statusBarStateListener)
- smallRegionSamplingInstance.stopRegionSampler()
- largeRegionSamplingInstance.stopRegionSampler()
+ smallRegionSampler?.stopRegionSampler()
+ largeRegionSampler?.stopRegionSampler()
}
/**
@@ -224,8 +231,8 @@
fun dump(pw: PrintWriter) {
pw.println(this)
clock?.dump(pw)
- smallRegionSamplingInstance.dump(pw)
- largeRegionSamplingInstance.dump(pw)
+ smallRegionSampler?.dump(pw)
+ largeRegionSampler?.dump(pw)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d7cd1d0..d03ef98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -17,7 +17,7 @@
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -94,7 +94,7 @@
onDensityOrFontScaleChanged();
}
- void setClock(Clock clock, int statusBarState) {
+ void setClock(ClockController clock, int statusBarState) {
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
@@ -105,11 +105,14 @@
}
// Attach small and big clock views to hierarchy.
- mSmallClockFrame.addView(clock.getSmallClock());
- mLargeClockFrame.addView(clock.getLargeClock());
+ Log.i(TAG, "Attached new clock views to switch");
+ mSmallClockFrame.addView(clock.getSmallClock().getView());
+ mLargeClockFrame.addView(clock.getLargeClock().getView());
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
+ Log.i(TAG, "updateClockViews; useLargeClock=" + useLargeClock + "; animate=" + animate
+ + "; mChildrenAreLaidOut=" + mChildrenAreLaidOut);
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 2165099..b450ec3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,7 +40,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
@@ -262,7 +262,7 @@
mCurrentClockSize = clockSize;
- Clock clock = getClock();
+ ClockController clock = getClock();
boolean appeared = mView.switchToClock(clockSize, animate);
if (clock != null && animate && appeared && clockSize == LARGE) {
clock.getAnimations().enter();
@@ -273,7 +273,7 @@
* Animates the clock view between folded and unfolded states
*/
public void animateFoldToAod(float foldFraction) {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock != null) {
clock.getAnimations().fold(foldFraction);
}
@@ -286,7 +286,7 @@
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock != null) {
clock.getEvents().onTimeTick();
}
@@ -319,17 +319,17 @@
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock == null) {
return 0;
}
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getLargeClock().getHeight();
+ int clockHeight = clock.getLargeClock().getView().getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- int clockHeight = clock.getSmallClock().getHeight();
+ int clockHeight = clock.getSmallClock().getView().getHeight();
return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
}
}
@@ -338,15 +338,15 @@
* Get the height of the currently visible clock on the keyguard.
*/
int getClockHeight() {
- Clock clock = getClock();
+ ClockController clock = getClock();
if (clock == null) {
return 0;
}
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- return clock.getLargeClock().getHeight();
+ return clock.getLargeClock().getView().getHeight();
} else {
- return clock.getSmallClock().getHeight();
+ return clock.getSmallClock().getView().getHeight();
}
}
@@ -361,12 +361,12 @@
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClock(Clock clock) {
+ private void setClock(ClockController clock) {
mClockEventController.setClock(clock);
mView.setClock(clock, mStatusBarStateController.getState());
}
- private Clock getClock() {
+ private ClockController getClock() {
return mClockEventController.getClock();
}
@@ -398,7 +398,8 @@
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- Clock clock = getClock();
+ mView.dump(pw, args);
+ ClockController clock = getClock();
if (clock != null) {
clock.dump(pw);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 453072b..5d86ccd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -176,6 +176,8 @@
@Override
public void startAppearAnimation() {
+ setAlpha(1f);
+ setTranslationY(0);
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1e5c53d..2cc5ccdc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,7 +24,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,8 +106,6 @@
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 5b22324..9871645 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,9 +330,6 @@
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..c46e33d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,7 +22,6 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -124,8 +123,6 @@
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bdb1b8..353c369 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -538,7 +538,7 @@
}
/**
- * Runs after a succsssful authentication only
+ * Runs after a successful authentication only
*/
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
@@ -1063,6 +1063,7 @@
mPopup.dismiss();
mPopup = null;
}
+ setupUserSwitcher();
}
@Override
@@ -1073,6 +1074,11 @@
android.R.attr.textColorPrimary));
header.setBackground(mView.getContext().getDrawable(
R.drawable.bouncer_user_switcher_header_bg));
+ Drawable keyDownDrawable =
+ ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId(
+ R.id.user_switcher_key_down);
+ keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(),
+ android.R.attr.textColorPrimary));
}
}
@@ -1096,7 +1102,6 @@
return;
}
- mView.setAlpha(1f);
mUserSwitcherViewGroup.setAlpha(0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
1f);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57058b7..d448f40 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -427,6 +427,7 @@
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
+ mView.setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 9d0a8ac..ac00e94 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,12 +61,6 @@
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
- * Some auth is required because the trustagent expired either from timeout or manually by
- * the user
- */
- int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
-
- /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..e9f06ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -212,9 +212,9 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
- if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
+ if (DEBUG) Slog.v(TAG, "refresh statusview visible:true");
refreshTime();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 7d6f377..f974e27 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -49,13 +49,14 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
- ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
ViewIdToTranslate(
- R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
- ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
+ R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+ ViewIdToTranslate(
+ R.id.notification_stack_scroller, END, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.start_button, START, filterNever),
+ ViewIdToTranslate(R.id.end_button, END, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6eef3b3..cd9089e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -69,8 +69,7 @@
import android.annotation.AnyThread;
import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
@@ -113,7 +112,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
@@ -126,6 +124,9 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
@@ -281,9 +282,9 @@
private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
- private final StatusBarStateController mStatusBarStateController;
private final UiEventLogger mUiEventLogger;
private final Set<Integer> mFaceAcquiredInfoIgnoreList;
+ private final PackageManager mPackageManager;
private int mStatusBarState;
private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
new StatusBarStateController.StateListener() {
@@ -304,10 +305,11 @@
};
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
- HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
+ HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
private int mPhoneState;
- private boolean mKeyguardIsVisible;
+ private boolean mKeyguardShowing;
+ private boolean mKeyguardOccluded;
private boolean mCredentialAttempted;
private boolean mKeyguardGoingAway;
private boolean mGoingToSleep;
@@ -317,7 +319,6 @@
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
private boolean mAssistantVisible;
- private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
private boolean mOccludingAppRequestingFace;
private boolean mSecureCameraLaunched;
@@ -336,34 +337,41 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
- private ContentObserver mTimeFormatChangeObserver;
+ private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
- private SubscriptionManager mSubscriptionManager;
+ private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
- private List<SubscriptionInfo> mSubscriptionInfo;
- private TrustManager mTrustManager;
- private UserManager mUserManager;
- private KeyguardBypassController mKeyguardBypassController;
- private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- private LockPatternUtils mLockPatternUtils;
- private final IDreamManager mDreamManager;
- private boolean mIsDreaming;
+ private final TrustManager mTrustManager;
+ private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
+ private final StatusBarStateController mStatusBarStateController;
+ private final Executor mBackgroundExecutor;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+ private final ActiveUnlockConfig mActiveUnlockConfig;
+ private final PowerManager mPowerManager;
+ private final IDreamManager mDreamManager;
+ private final TelephonyManager mTelephonyManager;
+ @Nullable
+ private final FingerprintManager mFpm;
+ @Nullable
+ private final FaceManager mFaceManager;
+ private final LockPatternUtils mLockPatternUtils;
+ private final boolean mWakeOnFingerprintAcquiredStart;
+
+ private KeyguardBypassController mKeyguardBypassController;
+ private List<SubscriptionInfo> mSubscriptionInfo;
+ private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ private boolean mIsDreaming;
private boolean mLogoutEnabled;
private boolean mIsFaceEnrolled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private final Executor mBackgroundExecutor;
- private SensorPrivacyManager mSensorPrivacyManager;
- private final ActiveUnlockConfig mActiveUnlockConfig;
- private final PowerManager mPowerManager;
- private final boolean mWakeOnFingerprintAcquiredStart;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -390,12 +398,10 @@
}
private final Handler mHandler;
- private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
- private BiometricManager mBiometricManager;
- private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
+ private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
new IBiometricEnabledOnKeyguardCallback.Stub() {
@Override
- public void onChanged(boolean enabled, int userId) throws RemoteException {
+ public void onChanged(boolean enabled, int userId) {
mHandler.post(() -> {
mBiometricEnabledForUser.put(userId, enabled);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -414,7 +420,7 @@
}
};
- private OnSubscriptionsChangedListener mSubscriptionListener =
+ private final OnSubscriptionsChangedListener mSubscriptionListener =
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
@@ -433,11 +439,12 @@
}
}
- private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
- private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
- private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
- private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
- private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
+ private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
+ private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
+ private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
+ private final SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
+ private final SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
+ private final Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<>();
@VisibleForTesting
SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
@@ -598,7 +605,7 @@
}
if (sil == null) {
// getCompleteActiveSubscriptionInfoList was null callers expect an empty list.
- mSubscriptionInfo = new ArrayList<SubscriptionInfo>();
+ mSubscriptionInfo = new ArrayList<>();
} else {
mSubscriptionInfo = sil;
}
@@ -674,14 +681,42 @@
}
/**
- * Updates KeyguardUpdateMonitor's internal state to know if keyguard is occluded
+ * Updates KeyguardUpdateMonitor's internal state to know if keyguard is showing and if
+ * its occluded. The keyguard is considered visible if its showing and NOT occluded.
*/
- public void setKeyguardOccluded(boolean occluded) {
- mKeyguardOccluded = occluded;
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
- }
+ public void setKeyguardShowing(boolean showing, boolean occluded) {
+ final boolean occlusionChanged = mKeyguardOccluded != occluded;
+ final boolean showingChanged = mKeyguardShowing != showing;
+ if (!occlusionChanged && !showingChanged) {
+ return;
+ }
+ final boolean wasKeyguardVisible = isKeyguardVisible();
+ mKeyguardShowing = showing;
+ mKeyguardOccluded = occluded;
+ final boolean isKeyguardVisible = isKeyguardVisible();
+ mLogger.logKeyguardShowingChanged(showing, occluded, isKeyguardVisible);
+
+ if (isKeyguardVisible != wasKeyguardVisible) {
+ if (isKeyguardVisible) {
+ mSecureCameraLaunched = false;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardVisibilityChanged(isKeyguardVisible);
+ }
+ }
+ }
+
+ if (occlusionChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED);
+ } else if (showingChanged) {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ }
+ }
/**
* Request to listen for face authentication when an app is occluding keyguard.
@@ -733,7 +768,7 @@
* If the device is dreaming, awakens the device
*/
public void awakenFromDream() {
- if (mIsDreaming && mDreamManager != null) {
+ if (mIsDreaming) {
try {
mDreamManager.awaken();
} catch (RemoteException e) {
@@ -778,12 +813,8 @@
}
private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
- mBackgroundExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
- }
- });
+ mBackgroundExecutor.execute(
+ () -> mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId));
}
private void handleFingerprintAuthFailed() {
@@ -866,7 +897,8 @@
}
}
- private Runnable mRetryFingerprintAuthentication = new Runnable() {
+ private final Runnable mRetryFingerprintAuthentication = new Runnable() {
+ @SuppressLint("MissingPermission")
@Override
public void run() {
mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
@@ -910,7 +942,7 @@
boolean lockedOutStateChanged = false;
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
- lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
+ lockedOutStateChanged = !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
mLogger.d("Fingerprint locked out - requiring strong auth");
mLockPatternUtils.requireStrongAuth(
@@ -955,9 +987,9 @@
// that the events will arrive in a particular order. Add a delay here in case
// an unlock is in progress. In this is a normal unlock the extra delay won't
// be noticeable.
- mHandler.postDelayed(() -> {
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
- }, getBiometricLockoutDelay());
+ mHandler.postDelayed(
+ () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
+ getBiometricLockoutDelay());
} else {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -1091,7 +1123,7 @@
}
}
- private Runnable mRetryFaceAuthentication = new Runnable() {
+ private final Runnable mRetryFaceAuthentication = new Runnable() {
@Override
public void run() {
mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
@@ -1117,12 +1149,8 @@
// Error is always the end of authentication lifecycle
mFaceCancelSignal = null;
- boolean cameraPrivacyEnabled = false;
- if (mSensorPrivacyManager != null) {
- cameraPrivacyEnabled = mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
- SensorPrivacyManager.Sensors.CAMERA);
- }
+ boolean cameraPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, SensorPrivacyManager.Sensors.CAMERA);
if (msgId == FaceManager.FACE_ERROR_CANCELED
&& mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1173,10 +1201,8 @@
mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
- mHandler.postDelayed(() -> {
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET);
- }, getBiometricLockoutDelay());
+ mHandler.postDelayed(() -> updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
if (changed) {
notifyLockedOutStateChanged(BiometricSourceType.FACE);
@@ -1215,28 +1241,24 @@
return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
}
- private boolean isTrustDisabled(int userId) {
+ private boolean isTrustDisabled() {
// Don't allow trust agent if device is secured with a SIM PIN. This is here
// mainly because there's no other way to prompt the user to enter their SIM PIN
// once they get past the keyguard screen.
- final boolean disabledBySimPin = isSimPinSecure();
- return disabledBySimPin;
+ return isSimPinSecure(); // Disabled by SIM PIN
}
private boolean isFingerprintDisabled(int userId) {
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
- & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
+ return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+ & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
|| isSimPinSecure();
}
private boolean isFaceDisabled(int userId) {
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
// TODO(b/140035044)
- return whitelistIpcs(() -> dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
- & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
+ return whitelistIpcs(() ->
+ (mDevicePolicyManager.getKeyguardDisabledFeatures(null, userId)
+ & DevicePolicyManager.KEYGUARD_DISABLE_FACE) != 0
|| isSimPinSecure());
}
@@ -1258,7 +1280,7 @@
}
public boolean getUserHasTrust(int userId) {
- return !isTrustDisabled(userId) && mUserHasTrust.get(userId);
+ return !isTrustDisabled() && mUserHasTrust.get(userId);
}
/**
@@ -1290,7 +1312,7 @@
}
public boolean getUserTrustIsManaged(int userId) {
- return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
+ return mUserTrustIsManaged.get(userId) && !isTrustDisabled();
}
private void updateSecondaryLockscreenRequirement(int userId) {
@@ -1308,7 +1330,7 @@
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
.setPackage(supervisorComponent.getPackageName());
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
+ ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0);
if (resolveInfo != null && resolveInfo.serviceInfo != null) {
Intent launchIntent =
new Intent().setComponent(resolveInfo.serviceInfo.getComponentName());
@@ -1677,22 +1699,19 @@
CancellationSignal mFingerprintCancelSignal;
@VisibleForTesting
CancellationSignal mFaceCancelSignal;
- private FingerprintManager mFpm;
- private FaceManager mFaceManager;
private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private boolean mFingerprintLockedOut;
private boolean mFingerprintLockedOutPermanent;
private boolean mFaceLockedOutPermanent;
- private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
- private TelephonyManager mTelephonyManager;
+ private final HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
/**
* When we receive a
* {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
* and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
* we need a single object to pass to the handler. This class helps decode
- * the intent and provide a {@link SimCard.State} result.
+ * the intent and provide a {@link SimData} result.
*/
private static class SimData {
public int simState;
@@ -1913,9 +1932,20 @@
UiEventLogger uiEventLogger,
// This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
Provider<SessionTracker> sessionTrackerProvider,
- PowerManager powerManager) {
+ PowerManager powerManager,
+ TrustManager trustManager,
+ SubscriptionManager subscriptionManager,
+ UserManager userManager,
+ IDreamManager dreamManager,
+ DevicePolicyManager devicePolicyManager,
+ SensorPrivacyManager sensorPrivacyManager,
+ TelephonyManager telephonyManager,
+ PackageManager packageManager,
+ @Nullable FaceManager faceManager,
+ @Nullable FingerprintManager fingerprintManager,
+ @Nullable BiometricManager biometricManager) {
mContext = context;
- mSubscriptionManager = SubscriptionManager.from(context);
+ mSubscriptionManager = subscriptionManager;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
@@ -1929,12 +1959,20 @@
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
mLogger = logger;
mUiEventLogger = uiEventLogger;
mSessionTrackerProvider = sessionTrackerProvider;
mPowerManager = powerManager;
+ mTrustManager = trustManager;
+ mUserManager = userManager;
+ mDreamManager = dreamManager;
+ mTelephonyManager = telephonyManager;
+ mDevicePolicyManager = devicePolicyManager;
+ mPackageManager = packageManager;
+ mFpm = fingerprintManager;
+ mFaceManager = faceManager;
mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mWakeOnFingerprintAcquiredStart = context.getResources()
.getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
@@ -2079,8 +2117,7 @@
// listener now with the service state from the default sub.
mBackgroundExecutor.execute(() -> {
int subId = SubscriptionManager.getDefaultSubscriptionId();
- ServiceState serviceState = mContext.getSystemService(TelephonyManager.class)
- .getServiceStateForSubscriber(subId);
+ ServiceState serviceState = mTelephonyManager.getServiceStateForSubscriber(subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
});
@@ -2102,26 +2139,21 @@
e.rethrowAsRuntimeException();
}
- mTrustManager = context.getSystemService(TrustManager.class);
mTrustManager.registerTrustListener(this);
setStrongAuthTracker(mStrongAuthTracker);
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.getService(DreamService.DREAM_SERVICE));
-
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+ if (mFpm != null) {
mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
+ mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
}
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
- mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+ if (mFaceManager != null) {
mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
+ mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
}
- if (mFpm != null || mFaceManager != null) {
- mBiometricManager = context.getSystemService(BiometricManager.class);
- mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+ if (biometricManager != null) {
+ biometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
}
// in case authenticators aren't registered yet at this point:
@@ -2139,19 +2171,11 @@
}
});
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
- if (mFpm != null) {
- mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
- }
- if (mFaceManager != null) {
- mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
- }
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
- mUserManager = context.getSystemService(UserManager.class);
mIsPrimaryUser = mUserManager.isPrimaryUser();
int user = ActivityManager.getCurrentUser();
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
List<UserInfo> allUsers = mUserManager.getUsers();
@@ -2161,22 +2185,8 @@
}
updateAirplaneModeState();
- mTelephonyManager =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (mTelephonyManager != null) {
- mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
- // Set initial sim states values.
- for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
- int state = mTelephonyManager.getSimState(slot);
- int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
- if (subIds != null) {
- for (int subId : subIds) {
- mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
- .sendToTarget();
- }
- }
- }
- }
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
+ initializeSimState();
mTimeFormatChangeObserver = new ContentObserver(mHandler) {
@Override
@@ -2193,6 +2203,20 @@
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
+ private void initializeSimState() {
+ // Set initial sim states values.
+ for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+ int state = mTelephonyManager.getSimState(slot);
+ int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+ if (subIds != null) {
+ for (int subId : subIds) {
+ mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+ .sendToTarget();
+ }
+ }
+ }
+ }
+
private void updateFaceEnrolled(int userId) {
mIsFaceEnrolled = whitelistIpcs(
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
@@ -2235,7 +2259,7 @@
}
@Override
- public void onUserSwitchComplete(int newUserId) throws RemoteException {
+ public void onUserSwitchComplete(int newUserId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
newUserId, 0));
}
@@ -2446,7 +2470,7 @@
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
- || (mKeyguardIsVisible && !mGoingToSleep
+ || (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
// Gates:
@@ -2522,7 +2546,7 @@
final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
final boolean shouldListenKeyguardState =
- mKeyguardIsVisible
+ isKeyguardVisible()
|| !mDeviceInteractive
|| (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
|| mGoingToSleep
@@ -2571,7 +2595,7 @@
mFingerprintLockedOut,
mGoingToSleep,
mKeyguardGoingAway,
- mKeyguardIsVisible,
+ isKeyguardVisible(),
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
@@ -2593,7 +2617,7 @@
}
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
- final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+ final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
final int user = getCurrentUser();
final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2639,7 +2663,7 @@
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
- (mBouncerFullyShown && !mGoingToSleep
+ (mBouncerFullyShown
|| mAuthInterruptActive
|| mOccludingAppRequestingFace
|| awakeKeyguard
@@ -2651,6 +2675,7 @@
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
+ && !mGoingToSleep
&& !fpOrFaceIsLockedOut;
// Aggregate relevant fields for debug logging.
@@ -2794,6 +2819,7 @@
return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
}
+ @SuppressLint("MissingPermission")
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
@@ -2924,6 +2950,7 @@
try {
reply.sendResult(null);
} catch (RemoteException e) {
+ mLogger.logException(e, "Ignored exception while userSwitching");
}
}
@@ -3147,32 +3174,18 @@
callbacksRefreshCarrierInfo();
}
+ /**
+ * Whether the keyguard is showing and not occluded.
+ */
public boolean isKeyguardVisible() {
- return mKeyguardIsVisible;
+ return isKeyguardShowing() && !mKeyguardOccluded;
}
/**
- * Notifies that the visibility state of Keyguard has changed.
- *
- * <p>Needs to be called from the main thread.
+ * Whether the keyguard is showing. It may still be occluded and not visible.
*/
- public void onKeyguardVisibilityChanged(boolean showing) {
- Assert.isMainThread();
- mLogger.logKeyguardVisibilityChanged(showing);
- mKeyguardIsVisible = showing;
-
- if (showing) {
- mSecureCameraLaunched = false;
- }
-
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onKeyguardVisibilityChangedRaw(showing);
- }
- }
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED);
+ public boolean isKeyguardShowing() {
+ return mKeyguardShowing;
}
/**
@@ -3190,7 +3203,7 @@
return false;
}
Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent,
+ ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
0 /* flags */, getCurrentUser());
if (resolveInfo == null) {
@@ -3320,11 +3333,7 @@
}
// change in battery overheat
- if (current.health != old.health) {
- return true;
- }
-
- return false;
+ return current.health != old.health;
}
/**
@@ -3375,10 +3384,8 @@
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
// Since this comes in on a binder thread, we need to post if first
- mHandler.post(() -> {
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_USER_SWITCHING);
- });
+ mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_USER_SWITCHING));
}
private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
@@ -3387,7 +3394,7 @@
callback.onTimeChanged();
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
- callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
+ callback.onKeyguardVisibilityChanged(isKeyguardVisible());
callback.onTelephonyCapable(mTelephonyCapable);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
@@ -3487,7 +3494,7 @@
/**
* If any SIM cards are currently secure.
*
- * @see #isSimPinSecure(State)
+ * @see #isSimPinSecure(int)
*/
public boolean isSimPinSecure() {
// True if any SIM is pin secure
@@ -3534,10 +3541,7 @@
* @return true if and only if the state has changed for the specified {@code slotId}
*/
private boolean refreshSimState(int subId, int slotId) {
- final TelephonyManager tele =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- int state = (tele != null) ?
- tele.getSimState(slotId) : TelephonyManager.SIM_STATE_UNKNOWN;
+ int state = mTelephonyManager.getSimState(slotId);
SimData data = mSimDatas.get(subId);
final boolean changed;
if (data == null) {
@@ -3680,13 +3684,8 @@
* Unregister all listeners.
*/
public void destroy() {
- // TODO: inject these dependencies:
- TelephonyManager telephony =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephony != null) {
- mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
- }
-
+ mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
+ mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
if (mDeviceProvisionedObserver != null) {
@@ -3716,8 +3715,9 @@
mHandler.removeCallbacksAndMessages(null);
}
+ @SuppressLint("MissingPermission")
@Override
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("KeyguardUpdateMonitor state:");
pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
pw.println(" getUserUnlockedWithBiometric()="
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7a42803..bc5ab88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
import android.hardware.biometrics.BiometricSourceType;
-import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
@@ -32,10 +31,6 @@
*/
public class KeyguardUpdateMonitorCallback {
- private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
- private long mVisibilityChangedCalled;
- private boolean mShowing;
-
/**
* Called when the battery status changes, e.g. when plugged in or unplugged, charge
* level, etc. changes.
@@ -75,21 +70,6 @@
public void onPhoneStateChanged(int phoneState) { }
/**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- public void onKeyguardVisibilityChanged(boolean showing) { }
-
- public void onKeyguardVisibilityChangedRaw(boolean showing) {
- final long now = SystemClock.elapsedRealtime();
- if (showing == mShowing
- && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
- onKeyguardVisibilityChanged(showing);
- mVisibilityChangedCalled = now;
- mShowing = showing;
- }
-
- /**
* Called when the keyguard enters or leaves bouncer mode.
* @param bouncerIsOrWillBeShowing if true, keyguard is showing the bouncer or transitioning
* from/to bouncer mode.
@@ -97,6 +77,12 @@
public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) { }
/**
+ * Called when the keyguard visibility changes.
+ * @param visible whether the keyguard is showing and is NOT occluded
+ */
+ public void onKeyguardVisibilityChanged(boolean visible) { }
+
+ /**
* Called when the keyguard fully transitions to the bouncer or is no longer the bouncer
* @param bouncerIsFullyShowing if true, keyguard is fully showing the bouncer
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index efa5558..b793fd2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -66,10 +66,13 @@
listView.setDividerHeight(mContext.getResources().getDimensionPixelSize(
R.dimen.bouncer_user_switcher_popup_divider_height));
- int height = mContext.getResources().getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_popup_header_height);
- listView.addHeaderView(createSpacer(height), null, false);
- listView.addFooterView(createSpacer(height), null, false);
+ if (listView.getTag(R.id.header_footer_views_added_tag_key) == null) {
+ int height = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_header_height);
+ listView.addHeaderView(createSpacer(height), null, false);
+ listView.addFooterView(createSpacer(height), null, false);
+ listView.setTag(R.id.header_footer_views_added_tag_key, new Object());
+ }
listView.setOnTouchListener((v, ev) -> {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2a36676..c41b752 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -447,14 +447,6 @@
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- // reset mIsBouncerShowing state in case it was preemptively set
- // onLongPress
- mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
- updateVisibility();
- }
-
- @Override
public void onKeyguardBouncerStateChanged(boolean bouncer) {
mIsBouncerShowing = bouncer;
updateVisibility();
@@ -507,6 +499,11 @@
// If biometrics were removed, local vars mCanDismissLockScreen and
// mUserUnlockedWithBiometric may not be updated.
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+
+ // reset mIsBouncerShowing state in case it was preemptively set
+ // onLongPress
+ mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
+
updateKeyguardShowing();
if (mIsKeyguardShowing) {
mUserUnlockedWithBiometric =
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
new file mode 100644
index 0000000..50012a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.dagger.KeyguardLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardLog"
+
+/**
+ * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
+ * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
+ * an overkill.
+ */
+class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
+
+ private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
+ buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarCalculatedAlpha(alpha: Float) {
+ debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarExplicitAlpha(alpha: Float) {
+ debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+ }
+
+ // TODO: remove after b/237743330 is fixed
+ fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
+ debugLog(
+ {
+ int1 = visibility
+ double1 = alpha.toDouble()
+ str1 = state
+ },
+ { "changing visibility to $int1 with alpha $double1 in state: $str1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 7a00cd9..54cec71 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
- fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+ fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
@@ -170,8 +170,14 @@
logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
}
- fun logKeyguardVisibilityChanged(showing: Boolean) {
- logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ fun logKeyguardShowingChanged(showing: Boolean, occluded: Boolean, visible: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = showing
+ bool2 = occluded
+ bool3 = visible
+ }, {
+ "keyguardShowingChanged(showing=$bool1 occluded=$bool2 visible=$bool3)"
+ })
}
fun logMissingSupervisorAppError(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index fe6dbe5..873a695 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -38,6 +38,8 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -66,7 +68,7 @@
private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
- static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+ public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
// beyond which swipe progress->0
public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
@@ -235,7 +237,11 @@
return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
}
- private float getSwipeAlpha(float progress) {
+ /**
+ * Returns the alpha value depending on the progress of the swipe.
+ */
+ @VisibleForTesting
+ public float getSwipeAlpha(float progress) {
if (mFadeDependingOnAmountSwiped) {
// The more progress has been fade, the lower the alpha value so that the view fades.
return Math.max(1 - progress, 0);
@@ -260,7 +266,7 @@
animView.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
- animView.setAlpha(getSwipeAlpha(swipeProgress));
+ updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
}
}
invalidateGlobalRegion(animView);
@@ -561,6 +567,14 @@
mCallback.onChildSnappedBack(animView, targetLeft);
}
+
+ /**
+ * Called to update the content alpha while the view is swiped
+ */
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ animView.setAlpha(alpha);
+ }
+
/**
* Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
* to tell us what to do
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
index 2ba2bb6..ed6fbec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -45,7 +45,7 @@
private boolean mShouldSetTouchStart;
@Nullable private MoveWindowTask mMoveWindowTask;
- private PointF mLastDrag = new PointF();
+ private final PointF mLastDrag = new PointF();
private final Handler mHandler;
SimpleMirrorWindowControl(Context context, Handler handler) {
@@ -92,8 +92,7 @@
}
private Point findOffset(View v, int moveFrameAmount) {
- final Point offset = mTmpPoint;
- offset.set(0, 0);
+ mTmpPoint.set(0, 0);
if (v.getId() == R.id.left_control) {
mTmpPoint.x = -moveFrameAmount;
} else if (v.getId() == R.id.up_control) {
@@ -184,7 +183,7 @@
private final int mYOffset;
private final Handler mHandler;
/** Time in milliseconds between successive task executions.*/
- private long mPeriod;
+ private final long mPeriod;
private boolean mCancel;
MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index ae73e34..8ded268 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -94,7 +94,7 @@
private final Context mContext;
private final Resources mResources;
private final Handler mHandler;
- private Rect mWindowBounds;
+ private final Rect mWindowBounds;
private final int mDisplayId;
@Surface.Rotation
@VisibleForTesting
@@ -174,16 +174,16 @@
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private final MagnificationGestureDetector mGestureDetector;
private final int mBounceEffectDuration;
- private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
+ private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
private Locale mLocale;
private NumberFormat mPercentFormat;
private float mBounceEffectAnimationScale;
- private SysUiState mSysUiState;
+ private final SysUiState mSysUiState;
// Set it to true when the view is overlapped with the gesture insets at the bottom.
private boolean mOverlapWithGestureInsets;
@Nullable
- private MirrorWindowControl mMirrorWindowControl;
+ private final MirrorWindowControl mMirrorWindowControl;
WindowMagnificationController(@UiContext Context context, @NonNull Handler handler,
@NonNull WindowMagnificationAnimationController animationController,
@@ -489,9 +489,7 @@
/** Returns the rotation degree change of two {@link Surface.Rotation} */
private int getDegreeFromRotation(@Surface.Rotation int newRotation,
@Surface.Rotation int oldRotation) {
- final int rotationDiff = oldRotation - newRotation;
- final int degree = (rotationDiff + 4) % 4 * 90;
- return degree;
+ return (oldRotation - newRotation + 4) % 4 * 90;
}
private void createMirrorWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 11353f6..403941f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -61,8 +61,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mIsKeyguardVisible = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mIsKeyguardVisible = visible;
handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb7..46c12b2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -875,6 +875,7 @@
static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DIM_BEHIND;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4fee083..4363b88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -117,7 +117,7 @@
}
fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
- if (!(keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.isDreaming) ||
+ if (!keyguardStateController.isShowing ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
index ca36375..379c819 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -52,7 +52,7 @@
private val userId: Int,
private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
private val unregisterAction: BroadcastReceiver.() -> Unit,
- private val bgExecutor: Executor,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val testPendingRemovalAction: (BroadcastReceiver, Int) -> Boolean
) : BroadcastReceiver(), Dumpable {
@@ -112,7 +112,7 @@
val id = index.getAndIncrement()
logger.logBroadcastReceived(id, userId, intent)
// Immediately return control to ActivityManager
- bgExecutor.execute {
+ workerExecutor.execute {
receiverDatas.forEach {
if (it.filter.matchCategories(intent.categories) == null &&
!testPendingRemovalAction(it.receiver, userId)) {
@@ -138,4 +138,4 @@
println("Categories: ${activeCategories.joinToString(", ")}")
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index eb8cb47..537cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -34,7 +34,8 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
@@ -55,7 +56,6 @@
private const val MSG_REMOVE_RECEIVER = 1
private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
private const val TAG = "BroadcastDispatcher"
-private const val DEBUG = true
/**
* SystemUI master Broadcast Dispatcher.
@@ -73,15 +73,16 @@
@SysUISingleton
open class BroadcastDispatcher @Inject constructor(
private val context: Context,
- @Background private val bgLooper: Looper,
- @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ @BroadcastRunning private val broadcastLooper: Looper,
+ @BroadcastRunning private val broadcastExecutor: Executor,
private val dumpManager: DumpManager,
private val logger: BroadcastDispatcherLogger,
private val userTracker: UserTracker,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
fun initialize() {
@@ -148,7 +149,7 @@
val data = ReceiverData(
receiver,
filter,
- executor ?: context.mainExecutor,
+ executor ?: mainExecutor,
user ?: context.user,
permission
)
@@ -181,7 +182,7 @@
registerReceiver(
receiver,
filter,
- bgExecutor,
+ broadcastExecutor,
user,
flags,
permission,
@@ -246,8 +247,8 @@
UserBroadcastDispatcher(
context,
userId,
- bgLooper,
- bgExecutor,
+ broadcastLooper,
+ broadcastExecutor,
logger,
removalPendingStore
)
@@ -265,7 +266,7 @@
ipw.decreaseIndent()
}
- private val handler = object : Handler(bgLooper) {
+ private val handler = object : Handler(broadcastLooper) {
override fun handleMessage(msg: Message) {
when (msg.what) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 6b15188..22dc94a 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -16,6 +16,7 @@
package com.android.systemui.broadcast
+import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Handler
@@ -46,8 +47,8 @@
open class UserBroadcastDispatcher(
private val context: Context,
private val userId: Int,
- private val bgLooper: Looper,
- private val bgExecutor: Executor,
+ private val workerLooper: Looper,
+ private val workerExecutor: Executor,
private val logger: BroadcastDispatcherLogger,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {
@@ -66,9 +67,11 @@
val permission: String?
)
- private val bgHandler = Handler(bgLooper)
+ private val wrongThreadErrorMsg = "This method should only be called from the worker thread " +
+ "(which is expected to be the BroadcastRunning thread)"
+ private val workerHandler = Handler(workerLooper)
- // Only modify in BG thread
+ // Only modify in BroadcastRunning thread
@VisibleForTesting
internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
@@ -97,8 +100,7 @@
}
private fun handleRegisterReceiver(receiverData: ReceiverData, flags: Int) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
receiverToActions
.getOrPut(receiverData.receiver, { ArraySet() })
@@ -113,6 +115,7 @@
logger.logReceiverRegistered(userId, receiverData.receiver, flags)
}
+ @SuppressLint("RegisterReceiverViaContextDetector")
@VisibleForTesting
internal open fun createActionReceiver(
action: String,
@@ -128,7 +131,7 @@
UserHandle.of(userId),
it,
permission,
- bgHandler,
+ workerHandler,
flags
)
logger.logContextReceiverRegistered(userId, flags, it)
@@ -143,15 +146,14 @@
IllegalStateException(e))
}
},
- bgExecutor,
+ workerExecutor,
logger,
removalPendingStore::isPendingRemoval
)
}
private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
- Preconditions.checkState(bgLooper.isCurrentThread,
- "This method should only be called from BG thread")
+ Preconditions.checkState(workerLooper.isCurrentThread, wrongThreadErrorMsg)
if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
actionsToActionsReceivers.forEach { (key, value) ->
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index c292296..701df89 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -45,6 +45,7 @@
public static final int QS_SWIPE_SIDE = 15;
public static final int BACK_GESTURE = 16;
public static final int QS_SWIPE_NESTED = 17;
+ public static final int MEDIA_SEEKBAR = 18;
@IntDef({
QUICK_SETTINGS,
@@ -65,7 +66,8 @@
LOCK_ICON,
QS_SWIPE_SIDE,
QS_SWIPE_NESTED,
- BACK_GESTURE
+ BACK_GESTURE,
+ MEDIA_SEEKBAR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InteractionType {}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 5e4f149..f8ee49a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -23,6 +23,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_NESTED;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
@@ -153,6 +154,7 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER
+ || interactionType == MEDIA_SEEKBAR
|| interactionType == SHADE_DRAG
|| interactionType == QS_COLLAPSE
|| interactionType == Classifier.UDFPS_AUTHENTICATION
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 07f94e7..e8c83b1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -18,6 +18,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -119,7 +120,8 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER
- || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE) {
+ || interactionType == QS_COLLAPSE || interactionType == QS_SWIPE_SIDE
+ || interactionType == MEDIA_SEEKBAR) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index 776bc88..f576a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -20,6 +20,7 @@
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
@@ -93,6 +94,10 @@
case QS_SWIPE_NESTED:
wrongDirection = !vertical;
break;
+ case MEDIA_SEEKBAR:
+ confidence = 0;
+ wrongDirection = vertical;
+ break;
default:
wrongDirection = true;
break;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index de2bdf7..840982c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -22,6 +22,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
import android.graphics.Point;
@@ -91,6 +92,7 @@
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER
+ || interactionType == MEDIA_SEEKBAR
|| interactionType == SHADE_DRAG
|| interactionType == LOCK_ICON) {
return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
new file mode 100644
index 0000000..f95a8ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+class LaunchableImageView : ImageView, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ superSetTransitionVisibility = { super.setTransitionVisibility(it) },
+ )
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+
+ override fun setTransitionVisibility(visibility: Int) {
+ delegate.setTransitionVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
deleted file mode 100644
index d6a059d..0000000
--- a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.containeddrawable
-
-import android.graphics.drawable.Drawable
-import androidx.annotation.DrawableRes
-
-/** Convenience container for [Drawable] or a way to load it later. */
-sealed class ContainedDrawable {
- data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
- data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 4096ed4..139a8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -46,6 +46,7 @@
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.camera2.CameraManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -237,22 +238,39 @@
@Singleton
static IDreamManager provideIDreamManager() {
return IDreamManager.Stub.asInterface(
- ServiceManager.checkService(DreamService.DREAM_SERVICE));
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
}
@Provides
@Singleton
@Nullable
static FaceManager provideFaceManager(Context context) {
- return context.getSystemService(FaceManager.class);
-
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ return context.getSystemService(FaceManager.class);
+ }
+ return null;
}
@Provides
@Singleton
@Nullable
static FingerprintManager providesFingerprintManager(Context context) {
- return context.getSystemService(FingerprintManager.class);
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return context.getSystemService(FingerprintManager.class);
+ }
+ return null;
+ }
+
+ /**
+ * @return null if both faceManager and fingerprintManager are null.
+ */
+ @Provides
+ @Singleton
+ @Nullable
+ static BiometricManager providesBiometricManager(Context context,
+ @Nullable FaceManager faceManager, @Nullable FingerprintManager fingerprintManager) {
+ return faceManager == null && fingerprintManager == null ? null :
+ context.getSystemService(BiometricManager.class);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a996699..48bef97 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -22,25 +22,19 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.os.PowerManager;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -62,8 +56,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -97,6 +90,7 @@
* SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
+ AospPolicyModule.class,
GestureModule.class,
MediaModule.class,
PowerModule.class,
@@ -121,30 +115,6 @@
@Provides
@SysUISingleton
- static BatteryController provideBatteryController(
- Context context,
- EnhancedEstimates enhancedEstimates,
- PowerManager powerManager,
- BroadcastDispatcher broadcastDispatcher,
- DemoModeController demoModeController,
- DumpManager dumpManager,
- @Main Handler mainHandler,
- @Background Handler bgHandler) {
- BatteryController bC = new BatteryControllerImpl(
- context,
- enhancedEstimates,
- powerManager,
- broadcastDispatcher,
- demoModeController,
- dumpManager,
- mainHandler,
- bgHandler);
- bC.init();
- return bC;
- }
-
- @Provides
- @SysUISingleton
static SensorPrivacyController provideSensorPrivacyController(
SensorPrivacyManager sensorPrivacyManager) {
SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 443d277..d70b971 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,7 @@
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
-import com.android.systemui.media.dagger.MediaProjectionModule;
+import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.people.PeopleModule;
@@ -81,6 +81,7 @@
import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.statusbar.window.StatusBarWindowModule;
+import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
@@ -145,6 +146,7 @@
StatusBarWindowModule.class,
SysUIConcurrencyModule.class,
SysUIUnfoldModule.class,
+ TelephonyRepositoryModule.class,
TunerModule.class,
UserModule.class,
UtilModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
new file mode 100644
index 0000000..5f8e540
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BroadcastRunning {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index b598554..4c4aa5c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -33,6 +33,18 @@
boolean isProvisioned();
/**
+ * Whether there's a pulse that's been requested but hasn't started transitioning to pulsing
+ * states yet.
+ */
+ boolean isPulsePending();
+
+ /**
+ * @param isPulsePending whether a pulse has been requested but hasn't started transitioning
+ * to the pulse state yet
+ */
+ void setPulsePending(boolean isPulsePending);
+
+ /**
* Makes a current pulse last for twice as long.
* @param reason why we're extending it.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4161cf6..2e51b51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -280,8 +280,8 @@
/**
* Appends pulse dropped event to logs
*/
- public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
- mLogger.logPulseDropped(pulsePending, state, blocked);
+ public void tracePulseDropped(String from, DozeMachine.State state) {
+ mLogger.logPulseDropped(from, state);
}
/**
@@ -292,6 +292,13 @@
}
/**
+ * Appends pulsing event to logs.
+ */
+ public void tracePulseEvent(String pulseEvent, boolean dozing, int pulseReason) {
+ mLogger.logPulseEvent(pulseEvent, dozing, DozeLog.reasonToString(pulseReason));
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
@@ -424,8 +431,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- traceKeyguard(showing);
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ traceKeyguard(visible);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4b279ec..cc57662 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -155,11 +155,11 @@
})
}
- fun logKeyguardVisibilityChange(isShowing: Boolean) {
+ fun logKeyguardVisibilityChange(isVisible: Boolean) {
buffer.log(TAG, INFO, {
- bool1 = isShowing
+ bool1 = isVisible
}, {
- "Keyguard visibility change, isShowing=$bool1"
+ "Keyguard visibility change, isVisible=$bool1"
})
}
@@ -224,13 +224,12 @@
})
}
- fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
+ fun logPulseDropped(from: String, state: DozeMachine.State) {
buffer.log(TAG, INFO, {
- bool1 = pulsePending
- str1 = state.name
- bool2 = blocked
+ str1 = from
+ str2 = state.name
}, {
- "Pulse dropped, pulsePending=$bool1 state=$str1 blocked=$bool2"
+ "Pulse dropped, cannot pulse from=$str1 state=$str2"
})
}
@@ -243,6 +242,16 @@
})
}
+ fun logPulseEvent(pulseEvent: String, dozing: Boolean, pulseReason: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = pulseEvent
+ bool1 = dozing
+ str2 = pulseReason
+ }, {
+ "Pulse-$str1 dozing=$bool1 pulseReason=$str2"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00ac8bc..ef454ff 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -102,7 +102,6 @@
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
- private boolean mPulsePending;
private Runnable mAodInterruptRunnable;
/** see {@link #onProximityFar} prox for callback */
@@ -303,8 +302,8 @@
null /* onPulseSuppressedListener */);
}
} else {
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// In pocket, drop event.
mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
@@ -410,8 +409,8 @@
sWakeDisplaySensorState = wake;
if (wake) {
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// In pocket, drop event.
return;
}
@@ -537,24 +536,44 @@
return;
}
- if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
- if (mAllowPulseTriggers) {
- mDozeLog.tracePulseDropped(mPulsePending, dozeState, mDozeHost.isPulsingBlocked());
+ if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+ if (!mAllowPulseTriggers) {
+ mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
+ } else if (mDozeHost.isPulsePending()) {
+ mDozeLog.tracePulseDropped("requestPulse - pulsePending");
+ } else if (!canPulse()) {
+ mDozeLog.tracePulseDropped("requestPulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
}
- mPulsePending = true;
- proximityCheckThenCall((result) -> {
- if (result != null && result) {
+ mDozeHost.setPulsePending(true);
+ proximityCheckThenCall((isNear) -> {
+ if (isNear != null && isNear) {
// in pocket, abort pulse
- mDozeLog.tracePulseDropped("inPocket");
- mPulsePending = false;
+ mDozeLog.tracePulseDropped("requestPulse - inPocket");
+ mDozeHost.setPulsePending(false);
runIfNotNull(onPulseSuppressedListener);
} else {
// not in pocket, continue pulsing
- continuePulseRequest(reason);
+ final boolean isPulsePending = mDozeHost.isPulsePending();
+ mDozeHost.setPulsePending(false);
+ if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+ if (!isPulsePending) {
+ mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+ + " pending, pulse was cancelled before it could start"
+ + " transitioning to pulsing state.");
+ } else if (mDozeHost.isPulsingBlocked()) {
+ mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
+ } else if (!canPulse()) {
+ mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+ }
+ runIfNotNull(onPulseSuppressedListener);
+ return;
+ }
+
+ mMachine.requestPulse(reason);
}
}, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
@@ -569,16 +588,6 @@
|| mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
}
- private void continuePulseRequest(int reason) {
- mPulsePending = false;
- if (mDozeHost.isPulsingBlocked() || !canPulse()) {
- mDozeLog.tracePulseDropped(mPulsePending, mMachine.getState(),
- mDozeHost.isPulsingBlocked());
- return;
- }
- mMachine.requestPulse(reason);
- }
-
@Nullable
private InstanceId getKeyguardSessionId() {
return mSessionTracker.getSessionId(SESSION_KEYGUARD);
@@ -591,7 +600,7 @@
pw.print(" notificationPulseTime=");
pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
- pw.println(" pulsePending=" + mPulsePending);
+ pw.println(" DozeHost#isPulsePending=" + mDozeHost.isPulsePending());
pw.println("DozeSensors:");
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 9cd149b..5694f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -18,7 +18,7 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION;
-import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
import android.animation.Animator;
@@ -67,7 +67,7 @@
private final Parent mParent;
@Complication.Category
private final int mCategory;
- private final int mMargin;
+ private final int mDefaultMargin;
/**
* Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
@@ -75,7 +75,7 @@
*/
ViewEntry(View view, ComplicationLayoutParams layoutParams,
TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
- int margin) {
+ int defaultMargin) {
mView = view;
// Views that are generated programmatically do not have a unique id assigned to them
// at construction. A new id is assigned here to enable ConstraintLayout relative
@@ -86,7 +86,7 @@
mTouchInsetSession = touchSession;
mCategory = category;
mParent = parent;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
touchSession.addViewToTracking(mView);
}
@@ -195,18 +195,19 @@
}
if (!isRoot) {
+ final int margin = mLayoutParams.getMargin(mDefaultMargin);
switch(direction) {
case ComplicationLayoutParams.DIRECTION_DOWN:
- params.setMargins(0, mMargin, 0, 0);
+ params.setMargins(0, margin, 0, 0);
break;
case ComplicationLayoutParams.DIRECTION_UP:
- params.setMargins(0, 0, 0, mMargin);
+ params.setMargins(0, 0, 0, margin);
break;
case ComplicationLayoutParams.DIRECTION_END:
- params.setMarginStart(mMargin);
+ params.setMarginStart(margin);
break;
case ComplicationLayoutParams.DIRECTION_START:
- params.setMarginEnd(mMargin);
+ params.setMarginEnd(margin);
break;
}
}
@@ -263,7 +264,7 @@
private final ComplicationLayoutParams mLayoutParams;
private final int mCategory;
private Parent mParent;
- private int mMargin;
+ private int mDefaultMargin;
Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
ComplicationLayoutParams lp, @Complication.Category int category) {
@@ -302,8 +303,8 @@
* Sets the margin that will be applied in the direction the complication is laid out
* towards.
*/
- Builder setMargin(int margin) {
- mMargin = margin;
+ Builder setDefaultMargin(int margin) {
+ mDefaultMargin = margin;
return this;
}
@@ -312,7 +313,7 @@
*/
ViewEntry build() {
return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
- mMargin);
+ mDefaultMargin);
}
}
@@ -472,7 +473,7 @@
}
private final ConstraintLayout mLayout;
- private final int mMargin;
+ private final int mDefaultMargin;
private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
private final TouchInsetManager.TouchInsetSession mSession;
@@ -483,12 +484,12 @@
/** */
@Inject
public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
- @Named(COMPLICATION_MARGIN) int margin,
+ @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin,
TouchInsetManager.TouchInsetSession session,
@Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration,
@Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) {
mLayout = layout;
- mMargin = margin;
+ mDefaultMargin = defaultMargin;
mSession = session;
mFadeInDuration = fadeInDuration;
mFadeOutDuration = fadeOutDuration;
@@ -537,7 +538,7 @@
}
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
- .setMargin(mMargin);
+ .setDefaultMargin(mDefaultMargin);
// Add position group if doesn't already exist
final int position = lp.getPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 8e8cb72..a21eb19 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -51,6 +51,8 @@
private static final int FIRST_POSITION = POSITION_TOP;
private static final int LAST_POSITION = POSITION_END;
+ private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
DIRECTION_UP,
@@ -77,6 +79,8 @@
private final int mWeight;
+ private final int mMargin;
+
private final boolean mSnapToGuide;
// Do not allow specifying opposite positions
@@ -106,7 +110,24 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
- this(width, height, position, direction, weight, false);
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin) {
+ this(width, height, position, direction, weight, margin, false);
}
/**
@@ -127,6 +148,28 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight, boolean snapToGuide) {
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+ * will be automatically set to align with a predetermined guide for that
+ * side. For example, if the complication is aligned to the top end and
+ * direction is down, then the width of the complication will be set to span
+ * from the end of the parent to the guide.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -142,6 +185,8 @@
mWeight = weight;
+ mMargin = margin;
+
mSnapToGuide = snapToGuide;
}
@@ -153,6 +198,7 @@
mPosition = source.mPosition;
mDirection = source.mDirection;
mWeight = source.mWeight;
+ mMargin = source.mMargin;
mSnapToGuide = source.mSnapToGuide;
}
@@ -215,6 +261,14 @@
}
/**
+ * Returns the margin to apply between complications, or the given default if no margin is
+ * specified.
+ */
+ public int getMargin(int defaultMargin) {
+ return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin;
+ }
+
+ /**
* Returns whether the complication's dimension perpendicular to direction should be
* automatically set.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 2503d3c..821e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -28,6 +28,9 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -158,17 +161,38 @@
private final Context mContext;
private final ControlsComponent mControlsComponent;
+ private final UiEventLogger mUiEventLogger;
+
+ @VisibleForTesting
+ public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+ DREAM_HOME_CONTROLS_TAPPED(1212);
+
+ private final int mId;
+
+ DreamOverlayEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
@Inject
DreamHomeControlsChipViewController(
@Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
ActivityStarter activityStarter,
Context context,
- ControlsComponent controlsComponent) {
+ ControlsComponent controlsComponent,
+ UiEventLogger uiEventLogger) {
super(view);
mActivityStarter = activityStarter;
mContext = context;
mControlsComponent = controlsComponent;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -184,6 +208,8 @@
private void onClickHomeControls(View v) {
if (DEBUG) Log.d(TAG, "home controls complication tapped");
+ mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+
final Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(ControlsUiController.EXTRA_ANIMATE, true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 11d89d2..c9fecc9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -37,7 +37,7 @@
@Module
public abstract class ComplicationHostViewModule {
public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
- public static final String COMPLICATION_MARGIN = "complication_margin";
+ public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default";
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
@@ -58,7 +58,7 @@
}
@Provides
- @Named(COMPLICATION_MARGIN)
+ @Named(COMPLICATION_MARGIN_DEFAULT)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationPadding(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 759d6ec..7d2ce51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -59,11 +59,11 @@
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
static ComplicationLayoutParams provideClockTimeLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
}
/**
@@ -73,12 +73,12 @@
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
}
/**
@@ -103,11 +103,12 @@
@Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
static ComplicationLayoutParams provideSmartspaceLayoutParams() {
return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
- true /*snapToGuide*/);
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ 0,
+ true /*snapToGuide*/);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index dfa3bcd..fb4fc92 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -16,12 +16,14 @@
package com.android.systemui.flags
+import android.util.Dumpable
+
/**
* Class to manage simple DeviceConfig-based feature flags.
*
* See [Flags] for instructions on defining new flags.
*/
-interface FeatureFlags : FlagListenable {
+interface FeatureFlags : FlagListenable, Dumpable {
/** Returns a boolean value for the given flag. */
fun isEnabled(flag: UnreleasedFlag): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 00c1a99..b983e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -30,29 +30,21 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.commandline.Command;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
-import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
@@ -75,10 +67,9 @@
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
-public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
- private static final String TAG = "SysUIFlags";
+public class FeatureFlagsDebug implements FeatureFlags {
+ static final String TAG = "SysUIFlags";
static final String ALL_FLAGS = "all_flags";
- private static final String FLAG_COMMAND = "flag";
private final FlagManager mFlagManager;
private final SecureSettings mSecureSettings;
@@ -89,7 +80,7 @@
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
- private final IStatusBarService mBarService;
+ private final Restarter mRestarter;
@Inject
public FeatureFlagsDebug(
@@ -98,12 +89,10 @@
SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
- DumpManager dumpManager,
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- CommandRegistry commandRegistry,
- IStatusBarService barService) {
+ Restarter barService) {
mFlagManager = flagManager;
mSecureSettings = secureSettings;
mResources = resources;
@@ -111,7 +100,7 @@
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mBarService = barService;
+ mRestarter = barService;
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
@@ -120,8 +109,6 @@
flagManager.setClearCacheAction(this::removeFromCache);
context.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- dumpManager.registerDumpable(TAG, this);
- commandRegistry.registerCommand(FLAG_COMMAND, FlagCommand::new);
}
@Override
@@ -266,7 +253,7 @@
mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
}
- private <T> void eraseFlag(Flag<T> flag) {
+ <T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) {
mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
@@ -319,13 +306,10 @@
return;
}
Log.i(TAG, "Restarting Android");
- try {
- mBarService.restart();
- } catch (RemoteException e) {
- }
+ mRestarter.restart();
}
- private void setBooleanFlagInternal(Flag<?> flag, boolean value) {
+ void setBooleanFlagInternal(Flag<?> flag, boolean value) {
if (flag instanceof BooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
@@ -342,7 +326,7 @@
}
}
- private void setStringFlagInternal(Flag<?> flag, String value) {
+ void setStringFlagInternal(Flag<?> flag, String value) {
if (flag instanceof StringFlag) {
setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceStringFlag) {
@@ -476,154 +460,4 @@
+ ": [length=" + value.length() + "] \"" + value + "\""));
}
- class FlagCommand implements Command {
- private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
- private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
-
- @Override
- public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
- if (args.size() == 0) {
- pw.println("Error: no flag id supplied");
- help(pw);
- pw.println();
- printKnownFlags(pw);
- return;
- }
-
- if (args.size() > 2) {
- pw.println("Invalid number of arguments.");
- help(pw);
- return;
- }
-
- int id = 0;
- try {
- id = Integer.parseInt(args.get(0));
- if (!mAllFlags.containsKey(id)) {
- pw.println("Unknown flag id: " + id);
- pw.println();
- printKnownFlags(pw);
- return;
- }
- } catch (NumberFormatException e) {
- id = flagNameToId(args.get(0));
- if (id == 0) {
- pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
- return;
- }
- }
- Flag<?> flag = mAllFlags.get(id);
-
- String cmd = "";
- if (args.size() == 2) {
- cmd = args.get(1).toLowerCase();
- }
-
- if ("erase".equals(cmd) || "reset".equals(cmd)) {
- eraseFlag(flag);
- return;
- }
-
- boolean newValue = true;
- if (args.size() == 1 || "toggle".equals(cmd)) {
- boolean enabled = isBooleanFlagEnabled(flag);
-
- if (args.size() == 1) {
- pw.println("Flag " + id + " is " + enabled);
- return;
- }
-
- newValue = !enabled;
- } else {
- newValue = mOnCommands.contains(cmd);
- if (!newValue && !mOffCommands.contains(cmd)) {
- pw.println("Invalid on/off argument supplied");
- help(pw);
- return;
- }
- }
-
- pw.flush(); // Next command will restart sysui, so flush before we do so.
- setBooleanFlagInternal(flag, newValue);
- }
-
- @Override
- public void help(PrintWriter pw) {
- pw.println(
- "Usage: adb shell cmd statusbar flag <id> "
- + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
- pw.println("The id can either be a numeric integer or the corresponding field name");
- pw.println(
- "If no argument is supplied after the id, the flags runtime value is output");
- }
-
- private boolean isBooleanFlagEnabled(Flag<?> flag) {
- if (flag instanceof ReleasedFlag) {
- return isEnabled((ReleasedFlag) flag);
- } else if (flag instanceof UnreleasedFlag) {
- return isEnabled((UnreleasedFlag) flag);
- } else if (flag instanceof ResourceBooleanFlag) {
- return isEnabled((ResourceBooleanFlag) flag);
- } else if (flag instanceof SysPropFlag) {
- return isEnabled((SysPropBooleanFlag) flag);
- }
-
- return false;
- }
-
- private int flagNameToId(String flagName) {
- List<Field> fields = Flags.getFlagFields();
- for (Field field : fields) {
- if (flagName.equals(field.getName())) {
- return fieldToId(field);
- }
- }
-
- return 0;
- }
-
- private int fieldToId(Field field) {
- try {
- Flag<?> flag = (Flag<?>) field.get(null);
- return flag.getId();
- } catch (IllegalAccessException e) {
- // no-op
- }
-
- return 0;
- }
-
- private void printKnownFlags(PrintWriter pw) {
- List<Field> fields = Flags.getFlagFields();
-
- int longestFieldName = 0;
- for (Field field : fields) {
- longestFieldName = Math.max(longestFieldName, field.getName().length());
- }
-
- pw.println("Known Flags:");
- pw.print("Flag Name");
- for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
- pw.print(" ");
- }
- pw.println("ID Enabled?");
- for (int i = 0; i < longestFieldName; i++) {
- pw.print("=");
- }
- pw.println(" ==== ========");
- for (Field field : fields) {
- int id = fieldToId(field);
- if (id == 0 || !mAllFlags.containsKey(id)) {
- continue;
- }
- pw.print(field.getName());
- int fieldWidth = field.getName().length();
- for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
- pw.print(" ");
- }
- pw.printf("%-4d ", id);
- pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
new file mode 100644
index 0000000..c0e3021
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.flags
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsDebugStartable
+@Inject
+constructor(
+ @Application context: Context,
+ dumpManager: DumpManager,
+ private val commandRegistry: CommandRegistry,
+ private val flagCommand: FlagCommand,
+ featureFlags: FeatureFlags
+) : CoreStartable(context) {
+
+ init {
+ dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+ featureFlags.dump(pw, args)
+ }
+ }
+
+ override fun start() {
+ commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+ }
+}
+
+@Module
+abstract class FeatureFlagsDebugStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureFlagsDebugStartable::class)
+ abstract fun bind(impl: FeatureFlagsDebugStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 049b17d..40a8a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -24,10 +24,8 @@
import androidx.annotation.NonNull;
-import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.DeviceConfigProxy;
import org.jetbrains.annotations.NotNull;
@@ -44,27 +42,26 @@
* how to set flags.
*/
@SysUISingleton
-public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
+public class FeatureFlagsRelease implements FeatureFlags {
+ static final String TAG = "SysUIFlags";
+
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
- private boolean mInited;
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader,
- DumpManager dumpManager) {
+ ServerFlagReader serverFlagReader) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
- dumpManager.registerDumpable("SysUIFlags", this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
new file mode 100644
index 0000000..f138f1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.flags
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+
+class FeatureFlagsReleaseStartable
+@Inject
+constructor(@Application context: Context, dumpManager: DumpManager, featureFlags: FeatureFlags) :
+ CoreStartable(context) {
+
+ init {
+ dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+ featureFlags.dump(pw, args)
+ }
+ }
+
+ override fun start() {
+ // no-op
+ }
+}
+
+@Module
+abstract class FeatureFlagsReleaseStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureFlagsReleaseStartable::class)
+ abstract fun bind(impl: FeatureFlagsReleaseStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
new file mode 100644
index 0000000..4d25431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -0,0 +1,196 @@
+/*
+ * 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.flags;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.commandline.Command;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * A {@link Command} used to flip flags in SystemUI.
+ */
+public class FlagCommand implements Command {
+ public static final String FLAG_COMMAND = "flag";
+
+ private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
+ private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
+ private final FeatureFlagsDebug mFeatureFlags;
+ private final Map<Integer, Flag<?>> mAllFlags;
+
+ @Inject
+ FlagCommand(
+ FeatureFlagsDebug featureFlags,
+ @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ ) {
+ mFeatureFlags = featureFlags;
+ mAllFlags = allFlags;
+ }
+
+ @Override
+ public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
+ if (args.size() == 0) {
+ pw.println("Error: no flag id supplied");
+ help(pw);
+ pw.println();
+ printKnownFlags(pw);
+ return;
+ }
+
+ if (args.size() > 2) {
+ pw.println("Invalid number of arguments.");
+ help(pw);
+ return;
+ }
+
+ int id = 0;
+ try {
+ id = Integer.parseInt(args.get(0));
+ if (!mAllFlags.containsKey(id)) {
+ pw.println("Unknown flag id: " + id);
+ pw.println();
+ printKnownFlags(pw);
+ return;
+ }
+ } catch (NumberFormatException e) {
+ id = flagNameToId(args.get(0));
+ if (id == 0) {
+ pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
+ return;
+ }
+ }
+ Flag<?> flag = mAllFlags.get(id);
+
+ String cmd = "";
+ if (args.size() == 2) {
+ cmd = args.get(1).toLowerCase();
+ }
+
+ if ("erase".equals(cmd) || "reset".equals(cmd)) {
+ mFeatureFlags.eraseFlag(flag);
+ return;
+ }
+
+ boolean newValue = true;
+ if (args.size() == 1 || "toggle".equals(cmd)) {
+ boolean enabled = isBooleanFlagEnabled(flag);
+
+ if (args.size() == 1) {
+ pw.println("Flag " + id + " is " + enabled);
+ return;
+ }
+
+ newValue = !enabled;
+ } else {
+ newValue = mOnCommands.contains(cmd);
+ if (!newValue && !mOffCommands.contains(cmd)) {
+ pw.println("Invalid on/off argument supplied");
+ help(pw);
+ return;
+ }
+ }
+
+ pw.flush(); // Next command will restart sysui, so flush before we do so.
+ mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+ }
+
+ @Override
+ public void help(PrintWriter pw) {
+ pw.println(
+ "Usage: adb shell cmd statusbar flag <id> "
+ + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
+ pw.println("The id can either be a numeric integer or the corresponding field name");
+ pw.println(
+ "If no argument is supplied after the id, the flags runtime value is output");
+ }
+
+ private boolean isBooleanFlagEnabled(Flag<?> flag) {
+ if (flag instanceof ReleasedFlag) {
+ return mFeatureFlags.isEnabled((ReleasedFlag) flag);
+ } else if (flag instanceof UnreleasedFlag) {
+ return mFeatureFlags.isEnabled((UnreleasedFlag) flag);
+ } else if (flag instanceof ResourceBooleanFlag) {
+ return mFeatureFlags.isEnabled((ResourceBooleanFlag) flag);
+ } else if (flag instanceof SysPropFlag) {
+ return mFeatureFlags.isEnabled((SysPropBooleanFlag) flag);
+ }
+
+ return false;
+ }
+
+ private int flagNameToId(String flagName) {
+ List<Field> fields = Flags.getFlagFields();
+ for (Field field : fields) {
+ if (flagName.equals(field.getName())) {
+ return fieldToId(field);
+ }
+ }
+
+ return 0;
+ }
+
+ private int fieldToId(Field field) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ return flag.getId();
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+
+ return 0;
+ }
+
+ private void printKnownFlags(PrintWriter pw) {
+ List<Field> fields = Flags.getFlagFields();
+
+ int longestFieldName = 0;
+ for (Field field : fields) {
+ longestFieldName = Math.max(longestFieldName, field.getName().length());
+ }
+
+ pw.println("Known Flags:");
+ pw.print("Flag Name");
+ for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
+ pw.print(" ");
+ }
+ pw.println("ID Enabled?");
+ for (int i = 0; i < longestFieldName; i++) {
+ pw.print("=");
+ }
+ pw.println(" ==== ========");
+ for (Field field : fields) {
+ int id = fieldToId(field);
+ if (id == 0 || !mAllFlags.containsKey(id)) {
+ continue;
+ }
+ pw.print(field.getName());
+ int fieldWidth = field.getName().length();
+ for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
+ pw.print(" ");
+ }
+ pw.printf("%-4d ", id);
+ pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 93f13eb..2067e67 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -68,7 +68,12 @@
public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
- // next id: 112
+ public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
+ false);
+
+ public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
+
+ // next id: 114
/***************************************/
// 200 - keyguard/lockscreen
@@ -104,9 +109,26 @@
public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
new ReleasedFlag(209, true);
- /** Whether the new implementation of UserSwitcherController should be used. */
- public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
- new UnreleasedFlag(210, false);
+ /**
+ * Whether the user interactor and repository should use `UserSwitcherController`.
+ *
+ * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
+ * the framework APIs.
+ */
+ public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
+ new UnreleasedFlag(210, true);
+
+ /**
+ * Whether `UserSwitcherController` should use the user interactor.
+ *
+ * <p>When this is {@code true}, the controller does not directly access framework APIs.
+ * Instead, it goes through the interactor.
+ *
+ * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
+ * {@code true} as it would created a cycle between controller -> interactor -> controller.
+ */
+ public static final UnreleasedFlag USER_CONTROLLER_USES_INTERACTOR =
+ new UnreleasedFlag(211, false);
/***************************************/
// 300 - power menu
@@ -152,7 +174,7 @@
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
- public static final UnreleasedFlag NEW_FOOTER_ACTIONS = new UnreleasedFlag(507, true);
+ public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
/***************************************/
// 600- status bar
@@ -189,7 +211,7 @@
public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
// 802 - wallpaper rendering
- public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802);
+ public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
// 803 - screen contents translation
public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
@@ -208,7 +230,8 @@
new ReleasedFlag(1000);
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
+ public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
+ new UnreleasedFlag(1002, /* teamfood= */ true);
// 1100 - windowing
@Keep
@@ -247,6 +270,17 @@
public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
+ @Keep
+ public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
+ new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
+ @Keep
+ public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
+ new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
+
+ @Keep
+ public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+ new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
new file mode 100644
index 0000000..8f095a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.flags
+
+interface Restarter {
+ fun restart()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 38b98eb..da0b910 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -512,9 +511,9 @@
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
synchronized (KeyguardViewMediator.this) {
- if (!showing && mPendingPinLock) {
+ if (!visible && mPendingPinLock) {
Log.i(TAG, "PIN lock requested, starting keyguard");
// Bring the keyguard back in order to show the PIN lock
@@ -804,9 +803,6 @@
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
- } else if (trustAgentsEnabled
- && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
- return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
@@ -987,9 +983,11 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- if (mUnoccludeAnimator != null) {
- mUnoccludeAnimator.cancel();
- }
+ mContext.getMainExecutor().execute(() -> {
+ if (mUnoccludeAnimator != null) {
+ mUnoccludeAnimator.cancel();
+ }
+ });
setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
@@ -1808,7 +1806,6 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mUpdateMonitor.setKeyguardOccluded(isOccluded);
mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 840a4b2..45b668e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -85,6 +85,15 @@
*/
val dozeAmount: Flow<Float>
+ /**
+ * Returns `true` if the keyguard is showing; `false` otherwise.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+ * the z-order (which is not really above the system UI window, but rather - the lock-screen
+ * becomes invisible to reveal the "occluding activity").
+ */
+ fun isKeyguardShowing(): Boolean
+
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
@@ -103,7 +112,7 @@
@Inject
constructor(
statusBarStateController: StatusBarStateController,
- keyguardStateController: KeyguardStateController,
+ private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
@@ -148,7 +157,11 @@
}
}
dozeHost.addCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+ trySendWithFailureLogging(
+ statusBarStateController.isDozing,
+ TAG,
+ "initial isDozing",
+ )
awaitClose { dozeHost.removeCallback(callback) }
}
@@ -168,6 +181,10 @@
awaitClose { statusBarStateController.removeCallback(callback) }
}
+ override fun isKeyguardShowing(): Boolean {
+ return keyguardStateController.isShowing
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index dccc941..192919e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -29,7 +29,7 @@
class KeyguardInteractor
@Inject
constructor(
- repository: KeyguardRepository,
+ private val repository: KeyguardRepository,
) {
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -40,4 +40,8 @@
val isDozing: Flow<Boolean> = repository.isDozing
/** Whether the keyguard is showing ot not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+
+ fun isKeyguardShowing(): Boolean {
+ return repository.isKeyguardShowing()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 95acc0b..01cd3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -104,7 +104,6 @@
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
icon = visibleState.icon,
- contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
)
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eff1469..eb6bb92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -17,8 +17,7 @@
package com.android.systemui.keyguard.domain.model
-import androidx.annotation.StringRes
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
@@ -35,11 +34,6 @@
/** Identifier for the affordance this is modeling. */
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
/** An icon for the affordance. */
- val icon: ContainedDrawable,
- /**
- * Resource ID for a string to use for the accessibility content description text of the
- * affordance.
- */
- @StringRes val contentDescriptionResourceId: Int,
+ val icon: Icon,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index ac2c9b1..89604f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.dagger.ControlsComponent
@@ -122,8 +123,14 @@
visibility == ControlsComponent.Visibility.AVAILABLE
) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithResource(iconResourceId),
- contentDescriptionResourceId = component.getTileTitleId(),
+ icon =
+ Icon.Resource(
+ res = iconResourceId,
+ contentDescription =
+ ContentDescription.Resource(
+ res = component.getTileTitleId(),
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8fb952c..8e1c6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,9 +18,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Intent
-import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -44,12 +43,7 @@
/** An affordance is visible. */
data class Visible(
/** An icon for the affordance. */
- val icon: ContainedDrawable,
- /**
- * Resource ID for a string to use for the accessibility content description text of the
- * affordance.
- */
- @StringRes val contentDescriptionResourceId: Int,
+ val icon: Icon,
) : State()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index c8e5e4a..d97deaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
@@ -76,8 +77,14 @@
private fun state(): KeyguardQuickAffordanceConfig.State {
return if (controller.isEnabledForLockScreenButton) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
- contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_qr_code_scanner,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_qr_code_scanner_button,
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 885af33..9196b09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -26,7 +26,8 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
@@ -100,8 +101,14 @@
): KeyguardQuickAffordanceConfig.State {
return if (isFeatureEnabled && hasCard && tileIcon != null) {
KeyguardQuickAffordanceConfig.State.Visible(
- icon = ContainedDrawable.WithDrawable(tileIcon),
- contentDescriptionResourceId = R.string.accessibility_wallet_button,
+ icon =
+ Icon.Loaded(
+ drawable = tileIcon,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ ),
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c4e3d4e..65b85c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -31,7 +31,7 @@
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -236,10 +236,7 @@
}
}
- when (viewModel.icon) {
- is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
- is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
- }
+ IconViewBinder.bind(viewModel.icon, view)
view.drawable.setTint(
Utils.getColorAttrDefaultColor(
@@ -250,7 +247,6 @@
view.backgroundTintList =
Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
- view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
view.setOnClickListener(OnClickListener(viewModel, falsingManager))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index e3ebac6..970ee4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -116,7 +116,6 @@
isVisible = true,
animateReveal = animateReveal,
icon = icon,
- contentDescriptionResourceId = contentDescriptionResourceId,
onClicked = { parameters ->
quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index b1de27d..0971f13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,9 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
@@ -28,8 +27,7 @@
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
- val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
- @StringRes val contentDescriptionResourceId: Int = 0,
+ val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
val onClicked: (OnClickedParameters) -> Unit = {},
val isClickable: Boolean = false,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
new file mode 100644
index 0000000..aef3471
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardLog.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A [com.android.systemui.log.LogBuffer] for keyguard-related stuff. Should be used mostly for
+ * adding temporary logs or logging from smaller classes when creating new separate log class might
+ * be an overkill.
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class KeyguardLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 29e2c1c..d298ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -43,7 +43,7 @@
@SysUISingleton
@DozeLog
public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
- return factory.create("DozeLog", 100);
+ return factory.create("DozeLog", 120);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -334,4 +334,14 @@
public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) {
return factory.create("BluetoothLog", 50);
}
+
+ /**
+ * Provides a {@link LogBuffer} for general keyguard-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardLog
+ public static LogBuffer provideKeyguardLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardLog", 250);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 8368792..2cd564f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -449,7 +449,7 @@
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying
+ val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(MediaViewHolder.create(
@@ -1042,15 +1042,6 @@
}
}
- fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? {
- mediaData.forEach { (key, value) ->
- if (value == mediaSortKey) {
- return mediaData[key]?.data
- }
- }
- return null
- }
-
fun getMediaPlayer(key: String): MediaControlPanel? {
return mediaData.get(key)?.let { mediaPlayers.get(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index c6bd777..1ac2a07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -17,6 +17,7 @@
import android.app.ActivityOptions
import android.content.Intent
+import android.content.res.Configuration
import android.media.projection.IMediaProjection
import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder
@@ -24,85 +25,73 @@
import android.os.IBinder
import android.os.ResultReceiver
import android.os.UserHandle
-import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView
import com.android.systemui.mediaprojection.appselector.data.RecentTask
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter
-import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.AsyncActivityLauncher
-import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
import javax.inject.Inject
class MediaProjectionAppSelectorActivity(
+ private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
- private val controller: MediaProjectionAppSelectorController,
- private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, RecentTaskClickListener {
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
@Inject
constructor(
+ componentFactory: MediaProjectionAppSelectorComponent.Factory,
activityLauncher: AsyncActivityLauncher,
- controller: MediaProjectionAppSelectorController,
- recentTasksAdapterFactory: RecentTasksAdapter.Factory,
- ) : this(activityLauncher, controller, recentTasksAdapterFactory, null)
+ ) : this(componentFactory, activityLauncher, null)
- private var recentsRoot: ViewGroup? = null
- private var recentsProgress: View? = null
- private var recentsRecycler: RecyclerView? = null
+ private lateinit var configurationController: ConfigurationController
+ private lateinit var controller: MediaProjectionAppSelectorController
+ private lateinit var recentsViewController: MediaProjectionRecentsViewController
- override fun getLayoutResource() =
- R.layout.media_projection_app_selector
+ override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- val queryIntent = Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
+ val component =
+ componentFactory.create(
+ activity = this,
+ view = this,
+ resultHandler = this
+ )
+
+ // Create a separate configuration controller for this activity as the configuration
+ // might be different from the global one
+ configurationController = component.configurationController
+ controller = component.controller
+ recentsViewController = component.recentsViewController
+
+ val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
// TODO(b/240939253): update copies
val title = getString(R.string.media_projection_dialog_service_title)
intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
- controller.init(this)
+ controller.init()
}
- private fun createRecentsView(parent: ViewGroup): ViewGroup {
- val recentsRoot = LayoutInflater.from(this)
- .inflate(R.layout.media_projection_recent_tasks, parent,
- /* attachToRoot= */ false) as ViewGroup
-
- recentsProgress = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_loader)
- recentsRecycler = recentsRoot.requireViewById(R.id.media_projection_recent_tasks_recycler)
- recentsRecycler?.layoutManager = LinearLayoutManager(
- this, LinearLayoutManager.HORIZONTAL,
- /* reverseLayout= */false
- )
-
- val itemDecoration = HorizontalSpacerItemDecoration(
- resources.getDimensionPixelOffset(
- R.dimen.media_projection_app_selector_recents_padding
- )
- )
- recentsRecycler?.addItemDecoration(itemDecoration)
-
- return recentsRoot
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ configurationController.onConfigurationChanged(newConfig)
}
- override fun appliedThemeResId(): Int =
- R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
@@ -124,9 +113,9 @@
// is typically very fast, so we don't show any loaders.
// We wait for the activity to be launched to make sure that the window of the activity
// is created and ready to be captured.
- val activityStarted = activityLauncher
- .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
- onTargetActivityLaunched(launchToken)
+ val activityStarted =
+ activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+ returnSelectedApp(launchToken)
}
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -160,44 +149,27 @@
}
override fun bind(recentTasks: List<RecentTask>) {
- val recents = recentsRoot ?: return
- val progress = recentsProgress ?: return
- val recycler = recentsRecycler ?: return
-
- if (recentTasks.isEmpty()) {
- recents.visibility = View.GONE
- return
- }
-
- progress.visibility = View.GONE
- recycler.visibility = View.VISIBLE
- recents.visibility = View.VISIBLE
-
- recycler.adapter = recentTasksAdapterFactory.create(recentTasks, this)
+ recentsViewController.bind(recentTasks)
}
- override fun onRecentClicked(task: RecentTask, view: View) {
- // TODO(b/240924732) Handle clicking on a recent task
- }
-
- private fun onTargetActivityLaunched(launchToken: IBinder) {
+ override fun returnSelectedApp(launchCookie: IBinder) {
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
// activity result, let's send the media projection to the result receiver
- val resultReceiver = intent
- .getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
- ResultReceiver::class.java) as ResultReceiver
- val captureRegion = MediaProjectionCaptureTarget(launchToken)
- val data = Bundle().apply {
- putParcelable(KEY_CAPTURE_TARGET, captureRegion)
- }
+ val resultReceiver =
+ intent.getParcelableExtra(
+ EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ ResultReceiver::class.java
+ ) as ResultReceiver
+ val captureRegion = MediaProjectionCaptureTarget(launchCookie)
+ val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
resultReceiver.send(RESULT_OK, data)
} else {
// Return the media projection instance as activity result
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
- projection.launchCookie = launchToken
+ projection.launchCookie = launchCookie
val intent = Intent()
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
@@ -214,15 +186,13 @@
override fun shouldShowContentPreview() = false
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
- recentsRoot ?: createRecentsView(parent).also {
- recentsRoot = it
- }
+ recentsViewController.createView(parent)
companion object {
/**
- * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
- * the activity will send the [CaptureRegion] to the result receiver
- * instead of returning media projection instance through activity result.
+ * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
+ * send the [CaptureRegion] to the result receiver instead of returning media projection
+ * instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
const val KEY_CAPTURE_TARGET = "capture_region"
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 0359c63..17ebfec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -30,7 +30,10 @@
import androidx.core.view.GestureDetectorCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
import javax.inject.Inject
@@ -72,7 +75,8 @@
/** ViewModel for seek bar in QS media player. */
class SeekBarViewModel @Inject constructor(
- @Background private val bgExecutor: RepeatableExecutor
+ @Background private val bgExecutor: RepeatableExecutor,
+ private val falsingManager: FalsingManager,
) {
private var _data = Progress(false, false, false, false, null, 0)
set(value) {
@@ -275,7 +279,7 @@
/** Gets a listener to attach to the seek bar to handle seeking. */
val seekBarListener: SeekBar.OnSeekBarChangeListener
get() {
- return SeekBarChangeListener(this)
+ return SeekBarChangeListener(this, falsingManager)
}
/** Attach touch handlers to the seek bar view. */
@@ -315,7 +319,8 @@
}
private class SeekBarChangeListener(
- val viewModel: SeekBarViewModel
+ val viewModel: SeekBarViewModel,
+ val falsingManager: FalsingManager,
) : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
@@ -328,6 +333,13 @@
}
override fun onStopTrackingTouch(bar: SeekBar) {
+ // in addition to the normal functionality of both functions.
+ // isFalseTouch returns true if there is a real/false tap since it is not a move.
+ // isFalseTap returns true if there is a real/false move since it is not a tap.
+ if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
+ falsingManager.isFalseTap(LOW_PENALTY)) {
+ viewModel.onSeekFalse()
+ }
viewModel.onSeek(bar.progress.toLong())
}
}
@@ -340,7 +352,7 @@
*/
private class SeekBarTouchListener(
private val viewModel: SeekBarViewModel,
- private val bar: SeekBar
+ private val bar: SeekBar,
) : View.OnTouchListener, GestureDetector.OnGestureListener {
// Gesture detector helps decide which touch events to intercept.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
deleted file mode 100644
index 185b4fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.media.dagger
-
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.MediaProjectionAppSelectorActivity
-import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
-import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
-import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import javax.inject.Qualifier
-
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class MediaProjectionAppSelector
-
-@Module
-abstract class MediaProjectionModule {
-
- @Binds
- @IntoMap
- @ClassKey(MediaProjectionAppSelectorActivity::class)
- abstract fun provideMediaProjectionAppSelectorActivity(
- activity: MediaProjectionAppSelectorActivity
- ): Activity
-
- @Binds
- abstract fun bindRecentTaskThumbnailLoader(
- impl: ActivityTaskManagerThumbnailLoader
- ): RecentTaskThumbnailLoader
-
- @Binds
- abstract fun bindRecentTaskListProvider(
- impl: ShellRecentTaskListProvider
- ): RecentTaskListProvider
-
- @Binds
- abstract fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
-
- companion object {
- @Provides
- fun provideController(
- recentTaskListProvider: RecentTaskListProvider,
- context: Context,
- @MediaProjectionAppSelector scope: CoroutineScope
- ): MediaProjectionAppSelectorController {
- val appSelectorComponentName =
- ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
-
- return MediaProjectionAppSelectorController(
- recentTaskListProvider,
- scope,
- appSelectorComponentName
- )
- }
-
- @MediaProjectionAppSelector
- @Provides
- fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
- CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 792ae7c..c3de94f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -76,29 +75,6 @@
isAppIcon = false
)
}
-
- /**
- * Sets an icon to be displayed by the given view.
- *
- * @param iconSize the size in pixels that the icon should be. If null, the size of
- * [appIconView] will not be adjusted.
- */
- fun setIcon(
- appIconView: CachingIconView,
- icon: Drawable,
- iconContentDescription: CharSequence,
- iconSize: Int? = null,
- ) {
- iconSize?.let { size ->
- val lp = appIconView.layoutParams
- lp.width = size
- lp.height = size
- appIconView.layoutParams = lp
- }
-
- appIconView.contentDescription = iconContentDescription
- appIconView.setImageDrawable(icon)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index dfd9e22..511c4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -30,6 +31,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -43,6 +45,7 @@
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
/**
@@ -62,6 +65,7 @@
powerManager: PowerManager,
@Main private val mainHandler: Handler,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
+ private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
@@ -82,7 +86,6 @@
height = WindowManager.LayoutParams.MATCH_PARENT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
fitInsetsTypes = 0 // Ignore insets from all system bars
- flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
@@ -139,31 +142,26 @@
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
- super.updateView(newInfo, currentView)
-
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
- val iconSize = context.resources.getDimensionPixelSize(
+ val iconPadding =
if (iconInfo.isAppIcon) {
- R.dimen.media_ttt_icon_size_receiver
+ 0
} else {
- R.dimen.media_ttt_generic_icon_size_receiver
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
}
- )
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconDrawable,
- iconContentDescription,
- iconSize,
- )
+ val iconView = currentView.getAppIconView()
+ iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
+ iconView.setImageDrawable(iconDrawable)
+ iconView.contentDescription = iconContentDescription
}
override fun animateViewIn(view: ViewGroup) {
- val appIconView = view.requireViewById<View>(R.id.app_icon)
+ val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
.setDuration(30.frames)
@@ -177,6 +175,12 @@
startRipple(view.requireViewById(R.id.ripple))
}
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ // Even though the app icon view isn't touchable, users might think it is. So, use it as the
+ // touchable region to ensure that touches don't get passed to the window below.
+ viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect)
+ }
+
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Int {
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
@@ -204,16 +208,19 @@
private fun layoutRipple(rippleView: ReceiverChipRippleView) {
val windowBounds = windowManager.currentWindowMetrics.bounds
- val height = windowBounds.height()
- val width = windowBounds.width()
+ val height = windowBounds.height().toFloat()
+ val width = windowBounds.width().toFloat()
- val maxDiameter = height / 2.5f
- rippleView.setMaxSize(maxDiameter, maxDiameter)
+ rippleView.setMaxSize(width / 2f, height / 2f)
// Center the ripple on the bottom of the screen in the middle.
- rippleView.setCenter(width * 0.5f, height.toFloat())
+ rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
rippleView.setColor(color, 70)
}
+
+ private fun View.getAppIconView(): CachingIconView {
+ return this.requireViewById(R.id.app_icon)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 6a505f0..e354a03 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.AttributeSet
+import com.android.systemui.ripple.RippleShader
import com.android.systemui.ripple.RippleView
/**
@@ -25,9 +26,9 @@
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
- // TODO: use RippleShape#ELLIPSE when calling setupShader.
- setupShader()
+ setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
+ setSparkleStrength(0f)
duration = 3000L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 4379d25..aae973d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -25,6 +25,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
/**
@@ -107,12 +108,15 @@
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
)
@@ -143,12 +147,15 @@
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
)
@@ -215,7 +222,8 @@
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: FalsingManager,
): View.OnClickListener? = null
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index e539f3f..a98158e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -18,29 +18,37 @@
import android.app.StatusBarManager
import android.content.Context
+import android.graphics.Rect
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
import android.view.Gravity
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.widget.CachingIconView
+import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import dagger.Lazy
import javax.inject.Inject
/**
@@ -48,7 +56,7 @@
* chip is shown when a user is transferring media to/from this device and a receiver device.
*/
@SysUISingleton
-class MediaTttChipControllerSender @Inject constructor(
+open class MediaTttChipControllerSender @Inject constructor(
commandQueue: CommandQueue,
context: Context,
@MediaTttSenderLogger logger: MediaTttLogger,
@@ -57,7 +65,12 @@
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger
+ private val uiEventLogger: MediaTttSenderUiEventLogger,
+ // Added Lazy<> to delay the time we create Falsing instances.
+ // And overcome performance issue, check [b/247817628] for details.
+ private val falsingManager: Lazy<FalsingManager>,
+ private val falsingCollector: Lazy<FalsingCollector>,
+ private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
@@ -70,6 +83,9 @@
MediaTttUtils.WINDOW_TITLE,
MediaTttUtils.WAKE_REASON,
) {
+
+ private lateinit var parent: MediaTttChipRootView
+
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
@@ -116,19 +132,24 @@
newInfo: ChipSenderInfo,
currentView: ViewGroup
) {
- super.updateView(newInfo, currentView)
-
val chipState = newInfo.state
+ // Detect falsing touches on the chip.
+ parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
+ parent.touchHandler = object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.get().onTouchEvent(ev)
+ return false
+ }
+ }
+
// App icon
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger
)
- MediaTttUtils.setIcon(
- currentView.requireViewById(R.id.app_icon),
- iconInfo.drawable,
- iconInfo.contentDescription
- )
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
+ iconView.setImageDrawable(iconInfo.drawable)
+ iconView.contentDescription = iconInfo.contentDescription
// Text
val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -142,7 +163,11 @@
// Undo
val undoView = currentView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
- this, newInfo.routeInfo, newInfo.undoCallback, uiEventLogger
+ this,
+ newInfo.routeInfo,
+ newInfo.undoCallback,
+ uiEventLogger,
+ falsingManager.get(),
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -171,10 +196,22 @@
)
}
- override fun removeView(removalReason: String) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ ViewHierarchyAnimator.animateRemoval(
+ view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_DURATION,
+ onAnimationEnd,
+ )
+ // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
+ // animateChipOut matches the animateChipIn.
+ }
+
+ override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {
// Don't remove the chip if we're in progress or succeeded, since the user should still be
// able to see the status of the transfer. (But do remove it if it's finally timed out.)
- val transferStatus = info?.state?.transferStatus
+ val transferStatus = info.state.transferStatus
if (
(transferStatus == TransferStatus.IN_PROGRESS ||
transferStatus == TransferStatus.SUCCEEDED) &&
@@ -183,9 +220,13 @@
logger.logRemovalBypass(
removalReason, bypassReason = "transferStatus=${transferStatus.name}"
)
- return
+ return true
}
- super.removeView(removalReason)
+ return false
+ }
+
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ viewUtil.setRectToViewWindowLocation(view, outRect)
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
new file mode 100644
index 0000000..3373159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.media.taptotransfer.sender
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events on chip. */
+class MediaTttChipRootView(
+ context: Context,
+ attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+ /** Assign this field to observe touch events. */
+ var touchHandler: Gefingerpoken? = null
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ touchHandler?.onTouchEvent(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
new file mode 100644
index 0000000..7fd100f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -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.systemui.mediaprojection.appselector
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
+import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Qualifier
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
+
+@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
+
+@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+interface MediaProjectionModule {
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionAppSelectorActivity::class)
+ fun provideMediaProjectionAppSelectorActivity(
+ activity: MediaProjectionAppSelectorActivity
+ ): Activity
+}
+
+/** Scoped values for [MediaProjectionAppSelectorComponent].
+ * We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
+ * could be reused. */
+@Module
+interface MediaProjectionAppSelectorModule {
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskThumbnailLoader(
+ impl: ActivityTaskManagerThumbnailLoader
+ ): RecentTaskThumbnailLoader
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
+
+ @Binds
+ @MediaProjectionAppSelectorScope
+ fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+
+ companion object {
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideAppSelectorComponentName(context: Context): ComponentName =
+ ComponentName(context, MediaProjectionAppSelectorActivity::class.java)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun bindConfigurationController(
+ activity: MediaProjectionAppSelectorActivity
+ ): ConfigurationController = ConfigurationControllerImpl(activity)
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
+ fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ }
+}
+
+@Subcomponent(modules = [MediaProjectionAppSelectorModule::class])
+@MediaProjectionAppSelectorScope
+interface MediaProjectionAppSelectorComponent {
+
+ /** Generates [MediaProjectionAppSelectorComponent]. */
+ @Subcomponent.Factory
+ interface Factory {
+ /**
+ * Create a factory to inject the activity into the graph
+ */
+ fun create(
+ @BindsInstance activity: MediaProjectionAppSelectorActivity,
+ @BindsInstance view: MediaProjectionAppSelectorView,
+ @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ ): MediaProjectionAppSelectorComponent
+ }
+
+ val controller: MediaProjectionAppSelectorController
+ val recentsViewController: MediaProjectionRecentsViewController
+
+ @MediaProjectionAppSelector val configurationController: ConfigurationController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 2b381a9..d744a40b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -17,20 +17,22 @@
package com.android.systemui.mediaprojection.appselector
import android.content.ComponentName
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class MediaProjectionAppSelectorController(
+@MediaProjectionAppSelectorScope
+class MediaProjectionAppSelectorController @Inject constructor(
private val recentTaskListProvider: RecentTaskListProvider,
+ private val view: MediaProjectionAppSelectorView,
@MediaProjectionAppSelector private val scope: CoroutineScope,
- private val appSelectorComponentName: ComponentName
+ @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
) {
- fun init(view: MediaProjectionAppSelectorView) {
+ fun init() {
scope.launch {
val tasks = recentTaskListProvider.loadRecentTasks().sortTasks()
view.bind(tasks)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
new file mode 100644
index 0000000..93c3bce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.mediaprojection.appselector
+
+import android.os.IBinder
+
+/**
+ * Interface that allows to continue the media projection flow and return the selected app
+ * result to the original caller.
+ */
+interface MediaProjectionAppSelectorResultHandler {
+ /**
+ * Return selected app to the original caller of the media projection app picker.
+ * @param launchCookie launch cookie of the launched activity of the target app
+ */
+ fun returnSelectedApp(launchCookie: IBinder)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
new file mode 100644
index 0000000..c816446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.graphics.Rect
+import android.os.Binder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import javax.inject.Inject
+
+/**
+ * Controller that handles view of the recent apps selector in the media projection activity.
+ * It is responsible for creating and updating recent apps view.
+ */
+@MediaProjectionAppSelectorScope
+class MediaProjectionRecentsViewController
+@Inject
+constructor(
+ private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
+ private val activityTaskManager: IActivityTaskManager,
+ private val resultHandler: MediaProjectionAppSelectorResultHandler,
+) : RecentTaskClickListener, TaskPreviewSizeListener {
+
+ private var views: Views? = null
+ private var lastBoundData: List<RecentTask>? = null
+
+ init {
+ taskViewSizeProvider.addCallback(this)
+ }
+
+ fun createView(parent: ViewGroup): ViewGroup =
+ views?.root ?: createRecentViews(parent).also {
+ views = it
+ lastBoundData?.let { recents -> bind(recents) }
+ }.root
+
+ fun bind(recentTasks: List<RecentTask>) {
+ views?.apply {
+ if (recentTasks.isEmpty()) {
+ root.visibility = View.GONE
+ return
+ }
+
+ progress.visibility = View.GONE
+ recycler.visibility = View.VISIBLE
+ root.visibility = View.VISIBLE
+
+ recycler.adapter =
+ recentTasksAdapterFactory.create(
+ recentTasks,
+ this@MediaProjectionRecentsViewController
+ )
+ }
+
+ lastBoundData = recentTasks
+ }
+
+ private fun createRecentViews(parent: ViewGroup): Views {
+ val recentsRoot =
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
+ as ViewGroup
+
+ val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+ container.setTaskHeightSize()
+
+ val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
+ val recycler =
+ recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler)
+ recycler.layoutManager =
+ LinearLayoutManager(
+ parent.context,
+ LinearLayoutManager.HORIZONTAL,
+ /* reverseLayout= */ false
+ )
+
+ val itemDecoration =
+ HorizontalSpacerItemDecoration(
+ parent.resources.getDimensionPixelOffset(
+ R.dimen.media_projection_app_selector_recents_padding
+ )
+ )
+ recycler.addItemDecoration(itemDecoration)
+
+ return Views(recentsRoot, container, progress, recycler)
+ }
+
+ override fun onRecentAppClicked(task: RecentTask, view: View) {
+ val launchCookie = Binder()
+ val activityOptions =
+ ActivityOptions.makeScaleUpAnimation(
+ view,
+ /* startX= */ 0,
+ /* startY= */ 0,
+ view.width,
+ view.height
+ )
+ activityOptions.launchCookie = launchCookie
+
+ activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
+ resultHandler.returnSelectedApp(launchCookie)
+ }
+
+ override fun onTaskSizeChanged(size: Rect) {
+ views?.recentsContainer?.setTaskHeightSize()
+ }
+
+ private fun View.setTaskHeightSize() {
+ val thumbnailHeight = taskViewSizeProvider.size.height()
+ val itemHeight =
+ thumbnailHeight +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_size
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_icon_margin
+ ) * 2
+
+ layoutParams = layoutParams.apply { height = itemHeight }
+ }
+
+ private class Views(
+ val root: ViewGroup,
+ val recentsContainer: View,
+ val progress: View,
+ val recycler: RecyclerView
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
new file mode 100644
index 0000000..b682bd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.graphics.BitmapShader
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowManager
+import androidx.core.content.getSystemService
+import androidx.core.content.res.use
+import com.android.internal.R as AndroidR
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+
+/**
+ * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
+ * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper].
+ */
+class MediaProjectionTaskView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ View(context, attrs, defStyleAttr) {
+
+ private val defaultBackgroundColor: Int
+
+ init {
+ val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating)
+ defaultBackgroundColor =
+ context.obtainStyledAttributes(backgroundColorAttribute).use {
+ it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK)
+ }
+ }
+
+ private val windowManager: WindowManager = context.getSystemService()!!
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val backgroundPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
+ private val cornerRadius =
+ context.resources.getDimensionPixelSize(
+ R.dimen.media_projection_app_selector_task_rounded_corners
+ )
+ private val previewPositionHelper = PreviewPositionHelper()
+ private val previewRect = Rect()
+
+ private var task: RecentTask? = null
+ private var thumbnailData: ThumbnailData? = null
+
+ private var bitmapShader: BitmapShader? = null
+
+ fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) {
+ this.task = task
+ this.thumbnailData = thumbnailData
+
+ // Strip alpha channel to make sure that the color is not semi-transparent
+ val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt()
+
+ paint.color = color
+ backgroundPaint.color = color
+
+ refresh()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ updateThumbnailMatrix()
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ // Always draw the background since the snapshots might be translucent or partially empty
+ // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
+ // split screen).
+ canvas.drawRoundRect(
+ 0f,
+ 1f,
+ width.toFloat(),
+ (height - 1).toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ backgroundPaint
+ )
+
+ val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null
+ if (drawBackgroundOnly) {
+ return
+ }
+
+ // Draw the task thumbnail using bitmap shader in the paint
+ canvas.drawRoundRect(
+ 0f,
+ 0f,
+ width.toFloat(),
+ height.toFloat(),
+ cornerRadius.toFloat(),
+ cornerRadius.toFloat(),
+ paint
+ )
+ }
+
+ private fun refresh() {
+ val thumbnailBitmap = thumbnailData?.thumbnail
+
+ if (thumbnailBitmap != null) {
+ thumbnailBitmap.prepareToDraw()
+ bitmapShader =
+ BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ paint.shader = bitmapShader
+ updateThumbnailMatrix()
+ } else {
+ bitmapShader = null
+ paint.shader = null
+ }
+
+ invalidate()
+ }
+
+ private fun updateThumbnailMatrix() {
+ previewPositionHelper.isOrientationChanged = false
+
+ val bitmapShader = bitmapShader ?: return
+ val thumbnailData = thumbnailData ?: return
+ val display = context.display ?: return
+ val windowMetrics = windowManager.maximumWindowMetrics
+
+ previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
+
+ val currentRotation: Int = display.rotation
+ val displayWidthPx = windowMetrics.bounds.width()
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ val isTablet = isTablet(context)
+ val taskbarSize =
+ if (isTablet) {
+ resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ } else {
+ 0
+ }
+
+ previewPositionHelper.updateThumbnailMatrix(
+ previewRect,
+ thumbnailData,
+ measuredWidth,
+ measuredHeight,
+ displayWidthPx,
+ taskbarSize,
+ isTablet,
+ currentRotation,
+ isRtl
+ )
+
+ bitmapShader.setLocalMatrix(previewPositionHelper.matrix)
+ paint.shader = bitmapShader
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index ec5abc7..15cfeee 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -16,15 +16,17 @@
package com.android.systemui.mediaprojection.appselector.view
+import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.media.dagger.MediaProjectionAppSelector
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -32,19 +34,27 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-class RecentTaskViewHolder @AssistedInject constructor(
- @Assisted root: ViewGroup,
+class RecentTaskViewHolder
+@AssistedInject
+constructor(
+ @Assisted private val root: ViewGroup,
private val iconLoader: AppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ private val taskViewSizeProvider: TaskPreviewSizeProvider,
@MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root) {
+) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
+ val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
private val iconView: ImageView = root.requireViewById(R.id.task_icon)
- private val thumbnailView: ImageView = root.requireViewById(R.id.task_thumbnail)
private var job: Job? = null
+ init {
+ updateThumbnailSize()
+ }
+
fun bind(task: RecentTask, onClick: (View) -> Unit) {
+ taskViewSizeProvider.addCallback(this)
job?.cancel()
job =
@@ -57,20 +67,33 @@
}
launch {
val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
- thumbnailView.setImageBitmap(thumbnail?.thumbnail)
+ thumbnailView.bindTask(task, thumbnail)
}
}
- thumbnailView.setOnClickListener(onClick)
+ root.setOnClickListener(onClick)
}
fun onRecycled() {
+ taskViewSizeProvider.removeCallback(this)
iconView.setImageDrawable(null)
- thumbnailView.setImageBitmap(null)
+ thumbnailView.bindTask(null, null)
job?.cancel()
job = null
}
+ override fun onTaskSizeChanged(size: Rect) {
+ updateThumbnailSize()
+ }
+
+ private fun updateThumbnailSize() {
+ thumbnailView.layoutParams =
+ thumbnailView.layoutParams.apply {
+ width = taskViewSizeProvider.size.width()
+ height = taskViewSizeProvider.size.height()
+ }
+ }
+
@AssistedFactory
fun interface Factory {
fun create(root: ViewGroup): RecentTaskViewHolder
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
index ec9cfa8..6af50a0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt
@@ -26,7 +26,9 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-class RecentTasksAdapter @AssistedInject constructor(
+class RecentTasksAdapter
+@AssistedInject
+constructor(
@Assisted private val items: List<RecentTask>,
@Assisted private val listener: RecentTaskClickListener,
private val viewHolderFactory: RecentTaskViewHolder.Factory
@@ -34,8 +36,8 @@
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentTaskViewHolder {
val taskItem =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.media_projection_task_item, null) as ViewGroup
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.media_projection_task_item, parent, false) as ViewGroup
return viewHolderFactory.create(taskItem)
}
@@ -43,7 +45,7 @@
override fun onBindViewHolder(holder: RecentTaskViewHolder, position: Int) {
val task = items[position]
holder.bind(task, onClick = {
- listener.onRecentClicked(task, holder.itemView)
+ listener.onRecentAppClicked(task, holder.itemView)
})
}
@@ -54,7 +56,7 @@
}
interface RecentTaskClickListener {
- fun onRecentClicked(task: RecentTask, view: View)
+ fun onRecentAppClicked(task: RecentTask, view: View)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
new file mode 100644
index 0000000..88d5eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.WindowManager
+import com.android.internal.R as AndroidR
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.shared.recents.utilities.Utilities.isTablet
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class TaskPreviewSizeProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val windowManager: WindowManager,
+ configurationController: ConfigurationController
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+
+ /** Returns the size of the task preview on the screen in pixels */
+ val size: Rect = calculateSize()
+
+ private val listeners = arrayListOf<TaskPreviewSizeListener>()
+
+ init {
+ configurationController.addCallback(this)
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ val newSize = calculateSize()
+ if (newSize != size) {
+ size.set(newSize)
+ listeners.forEach { it.onTaskSizeChanged(size) }
+ }
+ }
+
+ private fun calculateSize(): Rect {
+ val windowMetrics = windowManager.maximumWindowMetrics
+ val maximumWindowHeight = windowMetrics.bounds.height()
+ val width = windowMetrics.bounds.width()
+ var height = maximumWindowHeight
+
+ val isTablet = isTablet(context)
+ if (isTablet) {
+ val taskbarSize =
+ context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ height -= taskbarSize
+ }
+
+ val previewSize = Rect(0, 0, width, height)
+ val scale = (height / maximumWindowHeight.toFloat()) / SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO
+ previewSize.scale(scale)
+
+ return previewSize
+ }
+
+ override fun addCallback(listener: TaskPreviewSizeListener) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: TaskPreviewSizeListener) {
+ listeners -= listener
+ }
+
+ interface TaskPreviewSizeListener {
+ fun onTaskSizeChanged(size: Rect)
+ }
+}
+
+/**
+ * How many times smaller the task preview should be on the screen comparing to the height of the
+ * screen
+ */
+private const val SCREEN_HEIGHT_TO_TASK_HEIGHT_RATIO = 4f
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3789cbb..029cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -142,6 +142,9 @@
boolean isOldConfigTablet = mIsTablet;
mIsTablet = isTablet(mContext);
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+ if (mTaskbarDelegate.isInitialized()) {
+ mTaskbarDelegate.onConfigurationChanged(newConfig);
+ }
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9e0c496..73fc21e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -40,7 +40,6 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
-import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,7 +86,7 @@
@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- ComponentCallbacks, Dumpable {
+ Dumpable {
private static final String TAG = TaskbarDelegate.class.getSimpleName();
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -225,7 +224,6 @@
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
- mWindowContext.registerComponentCallbacks(this);
mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
// Set initial state for any listeners
updateSysuiFlags();
@@ -233,6 +231,7 @@
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
+ mEdgeBackGestureHandler.onConfigurationChanged(mContext.getResources().getConfiguration());
mInitialized = true;
}
@@ -247,10 +246,7 @@
mNavBarHelper.destroy();
mEdgeBackGestureHandler.onNavBarDetached();
mScreenPinningNotify = null;
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mWindowContext = null;
mAutoHideController.setNavigationBar(null);
mLightBarTransitionsController.destroy();
mLightBarController.setNavigationBar(null);
@@ -267,8 +263,9 @@
}
/**
- * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
- * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
+ * Returns {@code true} if this taskBar is {@link #init(int)}.
+ * Returns {@code false} if this taskbar has not yet been {@link #init(int)}
+ * or has been {@link #destroy()}.
*/
public boolean isInitialized() {
return mInitialized;
@@ -460,15 +457,11 @@
return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
- @Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
}
@Override
- public void onLowMemory() {}
-
- @Override
public void showPinningEnterExitToast(boolean entering) {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0f1338e..a8799c7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -57,7 +58,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
-import com.android.internal.util.LatencyTracker;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -199,7 +199,7 @@
private final Rect mNavBarOverlayExcludedBounds = new Rect();
private final Region mExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
- private final LatencyTracker mLatencyTracker;
+ private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
private final FeatureFlags mFeatureFlags;
@@ -339,7 +339,7 @@
IWindowManager windowManagerService,
Optional<Pip> pipOptional,
FalsingManager falsingManager,
- LatencyTracker latencyTracker,
+ Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
FeatureFlags featureFlags) {
super(broadcastDispatcher);
@@ -358,7 +358,7 @@
mWindowManagerService = windowManagerService;
mPipOptional = pipOptional;
mFalsingManager = falsingManager;
- mLatencyTracker = latencyTracker;
+ mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mFeatureFlags = featureFlags;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
@@ -583,8 +583,7 @@
setEdgeBackPlugin(
mBackPanelControllerFactory.create(mContext));
} else {
- setEdgeBackPlugin(
- new NavigationBarEdgePanel(mContext, mLatencyTracker));
+ setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
}
}
@@ -957,7 +956,7 @@
mStartingQuickstepRotation != rotation;
}
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (mStartingQuickstepRotation > -1) {
updateDisabledForQuickstep(newConfig);
}
@@ -1091,7 +1090,7 @@
private final IWindowManager mWindowManagerService;
private final Optional<Pip> mPipOptional;
private final FalsingManager mFalsingManager;
- private final LatencyTracker mLatencyTracker;
+ private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
private final FeatureFlags mFeatureFlags;
@@ -1111,7 +1110,7 @@
IWindowManager windowManagerService,
Optional<Pip> pipOptional,
FalsingManager falsingManager,
- LatencyTracker latencyTracker,
+ Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
FeatureFlags featureFlags) {
@@ -1129,7 +1128,7 @@
mWindowManagerService = windowManagerService;
mPipOptional = pipOptional;
mFalsingManager = falsingManager;
- mLatencyTracker = latencyTracker;
+ mNavBarEdgePanelProvider = navBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mFeatureFlags = featureFlags;
}
@@ -1152,7 +1151,7 @@
mWindowManagerService,
mPipOptional,
mFalsingManager,
- mLatencyTracker,
+ mNavBarEdgePanelProvider,
mBackGestureTfClassifierProviderProvider,
mFeatureFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 24efc76..1230708 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -52,9 +52,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +62,8 @@
import java.io.PrintWriter;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
private static final String TAG = "NavigationBarEdgePanel";
@@ -282,11 +284,16 @@
};
private BackCallback mBackCallback;
- public NavigationBarEdgePanel(Context context, LatencyTracker latencyTracker) {
+ @Inject
+ public NavigationBarEdgePanel(
+ Context context,
+ LatencyTracker latencyTracker,
+ VibratorHelper vibratorHelper,
+ @Background Executor backgroundExecutor) {
super(context);
mWindowManager = context.getSystemService(WindowManager.class);
- mVibratorHelper = Dependency.get(VibratorHelper.class);
+ mVibratorHelper = vibratorHelper;
mDensity = context.getResources().getDisplayMetrics().density;
@@ -358,7 +365,6 @@
setVisibility(GONE);
- Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 7b27cf4..1ef6426 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -60,6 +60,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -82,7 +83,7 @@
private static final String EXTRA_VISIBLE = "visible";
private final Rect mQsBounds = new Rect();
- private final StatusBarStateController mStatusBarStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
private final FalsingManager mFalsingManager;
private final KeyguardBypassController mBypassController;
private boolean mQsExpanded;
@@ -159,7 +160,7 @@
* Progress of pull down from the center of the lock screen.
* @see com.android.systemui.statusbar.LockscreenShadeTransitionController
*/
- private float mFullShadeProgress;
+ private float mLockscreenToShadeProgress;
private boolean mOverScrolling;
@@ -177,7 +178,7 @@
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
QSTileHost qsTileHost,
- StatusBarStateController statusBarStateController, CommandQueue commandQueue,
+ SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
@@ -442,20 +443,19 @@
}
private void updateQsState() {
- final boolean expanded = mQsExpanded || mInSplitShade;
- final boolean expandVisually = expanded || mStackScrollerOverscrolling
+ final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
|| mHeaderAnimating;
- mQSPanelController.setExpanded(expanded);
+ mQSPanelController.setExpanded(mQsExpanded);
boolean keyguardShowing = isKeyguardState();
- mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
+ mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard)
? View.VISIBLE
: View.INVISIBLE);
mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
- || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
+ || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
boolean qsPanelVisible = !mQsDisabled && expandVisually;
- boolean footerVisible = qsPanelVisible && (expanded || !keyguardShowing || mHeaderAnimating
- || mShowCollapsedOnKeyguard);
+ boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
+ || mHeaderAnimating || mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
if (mQSFooterActionController != null) {
mQSFooterActionController.setVisible(footerVisible);
@@ -463,7 +463,7 @@
mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
}
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
- || (expanded && !mStackScrollerOverscrolling));
+ || (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
if (DEBUG) {
Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible);
@@ -586,7 +586,7 @@
mTransitioningToFullShade = isTransitioningToFullShade;
updateShowCollapsedOnKeyguard();
}
- mFullShadeProgress = qsTransitionFraction;
+ mLockscreenToShadeProgress = qsTransitionFraction;
setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation,
isTransitioningToFullShade ? qsSquishinessFraction : mSquishinessFraction);
}
@@ -710,10 +710,13 @@
}
if (mInSplitShade) {
// Large screens in landscape.
- if (mTransitioningToFullShade || isKeyguardState()) {
+ // Need to check upcoming state as for unlocked -> AOD transition current state is
+ // not updated yet, but we're transitioning and UI should already follow KEYGUARD state
+ if (mTransitioningToFullShade || mStatusBarStateController.getCurrentOrUpcomingState()
+ == StatusBarState.KEYGUARD) {
// Always use "mFullShadeProgress" on keyguard, because
// "panelExpansionFractions" is always 1 on keyguard split shade.
- return mFullShadeProgress;
+ return mLockscreenToShadeProgress;
} else {
return panelExpansionFraction;
}
@@ -722,7 +725,7 @@
if (mTransitioningToFullShade) {
// Only use this value during the standard lock screen shade expansion. During the
// "quick" expansion from top, this value is 0.
- return mFullShadeProgress;
+ return mLockscreenToShadeProgress;
} else {
return panelExpansionFraction;
}
@@ -930,7 +933,7 @@
indentingPw.println("mLastHeaderTranslation: " + mLastHeaderTranslation);
indentingPw.println("mInSplitShade: " + mInSplitShade);
indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade);
- indentingPw.println("mFullShadeProgress: " + mFullShadeProgress);
+ indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress);
indentingPw.println("mOverScrolling: " + mOverScrolling);
indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing());
View view = getView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 184089f7..6517ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -105,6 +105,7 @@
private final Rect mClippingRect = new Rect();
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
+ private boolean mUsingCombinedHeaders = false;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -148,6 +149,10 @@
}
}
+ void setUsingCombinedHeaders(boolean usingCombinedHeaders) {
+ mUsingCombinedHeaders = usingCombinedHeaders;
+ }
+
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -371,7 +376,9 @@
protected void updatePadding() {
final Resources res = mContext.getResources();
- int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+ int paddingTop = res.getDimensionPixelSize(
+ mUsingCombinedHeaders ? R.dimen.qs_panel_padding_top_combined_headers
+ : R.dimen.qs_panel_padding_top);
int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
setPaddingRelative(getPaddingStart(),
paddingTop,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 18bd6b7..f6db775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE;
+import static com.android.systemui.flags.Flags.COMBINED_QS_HEADERS;
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
@@ -27,6 +28,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -79,7 +81,8 @@
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
@@ -93,6 +96,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mView.setUsingCombinedHeaders(featureFlags.isEnabled(COMBINED_QS_HEADERS));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ded466a..2727c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
import android.metrics.LogMaker;
-import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
@@ -75,6 +75,7 @@
@Nullable
private Consumer<Boolean> mMediaVisibilityChangedListener;
+ @Orientation
private int mLastOrientation;
private String mCachedSpecs = "";
@Nullable
@@ -88,21 +89,16 @@
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
+ mQSLogger.logOnConfigurationChanged(
+ /* lastOrientation= */ mLastOrientation,
+ /* newOrientation= */ newConfig.orientation,
+ /* containerName= */ mView.getDumpableTag());
+
mShouldUseSplitNotificationShade =
- LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- // Logging to aid the investigation of b/216244185.
- Log.d(TAG,
- "onConfigurationChange: "
- + "mShouldUseSplitNotificationShade="
- + mShouldUseSplitNotificationShade + ", "
- + "newConfig.windowConfiguration="
- + newConfig.windowConfiguration);
- mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
- mView.getDumpableTag());
- if (newConfig.orientation != mLastOrientation) {
- mLastOrientation = newConfig.orientation;
- switchTileLayout(false);
- }
+ LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ mLastOrientation = newConfig.orientation;
+
+ switchTileLayoutIfNeeded();
onConfigurationChanged();
}
};
@@ -334,6 +330,10 @@
}
}
+ private void switchTileLayoutIfNeeded() {
+ switchTileLayout(/* force= */ false);
+ }
+
boolean switchTileLayout(boolean force) {
/* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 264edb1..84d7e65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -410,9 +410,9 @@
// If forceExpanded (we are opening QS from lockscreen), the animators have been set to
// position = 1f.
if (forceExpanded) {
- setTranslationY(panelTranslationY);
+ setAlpha(expansionFraction);
} else {
- setTranslationY(0);
+ setAlpha(1);
}
mKeyguardExpansionFraction = keyguardExpansionFraction;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 28ddead..dd1ffcc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -222,6 +222,7 @@
private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
val buttonView = button.view
+ buttonView.id = model?.id ?: View.NO_ID
buttonView.isVisible = model != null
if (model == null) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 2ad0513..9b5f683 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -25,6 +25,7 @@
* power buttons.
*/
data class FooterActionsButtonViewModel(
+ val id: Int,
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index a935338..11d9555 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -138,6 +138,7 @@
/** The model for the settings button. */
val settings: FooterActionsButtonViewModel =
FooterActionsButtonViewModel(
+ id = R.id.settings_button_container,
Icon.Resource(
R.drawable.ic_settings,
ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
@@ -151,6 +152,7 @@
val power: FooterActionsButtonViewModel? =
if (showPowerButton) {
FooterActionsButtonViewModel(
+ id = R.id.pm_lite,
Icon.Resource(
android.R.drawable.ic_lock_power_off,
ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
@@ -256,6 +258,7 @@
}
return FooterActionsButtonViewModel(
+ id = R.id.multi_user_switch,
Icon.Loaded(
icon,
ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 97476b2..d2d5063 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -134,7 +134,7 @@
v.bind(name, drawable, item.info.id);
}
v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
+ v.setDisabledByAdmin(item.isDisabledByAdmin());
v.setEnabled(item.isSwitchToEnabled);
UserSwitcherController.setSelectableAlpha(v);
@@ -173,16 +173,16 @@
Trace.beginSection("UserDetailView.Adapter#onClick");
UserRecord userRecord =
(UserRecord) view.getTag();
- if (mController.isDisabledByAdmin(userRecord)) {
+ if (userRecord.isDisabledByAdmin()) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mController.getEnforcedAdmin(userRecord));
+ mContext, userRecord.enforcedAdmin);
mController.startActivity(intent);
} else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
if (!userRecord.isAddUser
&& !userRecord.isRestricted
- && !mController.isDisabledByAdmin(userRecord)) {
+ && !userRecord.isDisabledByAdmin()) {
if (mCurrentUserView != null) {
mCurrentUserView.setActivated(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 7e2a5c5..66be00d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,15 +25,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -110,16 +101,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -151,10 +133,8 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final Optional<Pip> mPipOptional;
+ private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -164,14 +144,8 @@
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
- private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
- private final ShellTransitions mShellTransitions;
- private final Optional<StartingSurface> mStartingSurface;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
- private final Optional<RecentTasks> mRecentTasks;
- private final Optional<BackAnimation> mBackAnimation;
- private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -342,14 +316,6 @@
}
@Override
- public void notifySwipeToHomeFinished() {
- verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
- mPipOptional.ifPresent(
- pip -> pip.setPinnedStackAnimationType(
- PipAnimationController.ANIM_TYPE_ALPHA)));
- }
-
- @Override
public void notifySwipeUpGestureStarted() {
verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
notifySwipeUpGestureStartedInternal());
@@ -464,36 +430,10 @@
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
- mPipOptional.ifPresent((pip) -> params.putBinder(
- KEY_EXTRA_SHELL_PIP,
- pip.createExternalInterface().asBinder()));
- mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
- KEY_EXTRA_SHELL_SPLIT_SCREEN,
- splitscreen.createExternalInterface().asBinder()));
- mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder(
- KEY_EXTRA_SHELL_FLOATING_TASKS,
- floatingTasks.createExternalInterface().asBinder()));
- mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
- KEY_EXTRA_SHELL_ONE_HANDED,
- onehanded.createExternalInterface().asBinder()));
- params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- mShellTransitions.createExternalInterface().asBinder());
- mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
- KEY_EXTRA_SHELL_STARTING_WINDOW,
- startingwindow.createExternalInterface().asBinder()));
- params.putBinder(
- KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+ params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
- mRecentTasks.ifPresent(recentTasks -> params.putBinder(
- KEY_EXTRA_RECENT_TASKS,
- recentTasks.createExternalInterface().asBinder()));
- mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
- KEY_EXTRA_SHELL_BACK_ANIMATION,
- backAnimation.createExternalInterface().asBinder()));
- mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
- KEY_EXTRA_SHELL_DESKTOP_MODE,
- desktopMode.createExternalInterface().asBinder())));
+ // Add all the interfaces exposed by the shell
+ mShellInterface.createExternalInterfaces(params);
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -567,21 +507,14 @@
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyService(Context context, CommandQueue commandQueue,
+ public OverviewProxyService(Context context,
+ CommandQueue commandQueue,
+ ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- Optional<Pip> pipOptional,
- Optional<SplitScreen> splitScreenOptional,
- Optional<FloatingTasks> floatingTasksOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<RecentTasks> recentTasks,
- Optional<BackAnimation> backAnimation,
- Optional<StartingSurface> startingSurface,
- Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
- ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -595,7 +528,7 @@
}
mContext = context;
- mPipOptional = pipOptional;
+ mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -610,11 +543,6 @@
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
- mOneHandedOptional = oneHandedOptional;
- mShellTransitions = shellTransitions;
- mRecentTasks = recentTasks;
- mBackAnimation = backAnimation;
- mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -644,9 +572,6 @@
});
mCommandQueue = commandQueue;
- mSplitScreenOptional = splitScreenOptional;
- mFloatingTasksOptional = floatingTasksOptional;
-
// Listen for user setup
startTracking();
@@ -655,7 +580,6 @@
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
- mStartingSurface = startingSurface;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
// Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 55602a9..e3658de 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -19,6 +19,7 @@
import static android.os.FileUtils.closeQuietly;
import android.annotation.IntRange;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.graphics.Bitmap;
@@ -29,6 +30,7 @@
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.provider.MediaStore;
import android.util.Log;
@@ -142,8 +144,9 @@
*
* @return a listenable future result
*/
- ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap) {
- return export(executor, requestId, bitmap, ZonedDateTime.now());
+ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ UserHandle owner) {
+ return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
}
/**
@@ -155,10 +158,10 @@
* @return a listenable future result
*/
ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
- ZonedDateTime captureTime) {
+ ZonedDateTime captureTime, UserHandle owner) {
final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
- mQuality, /* publish */ true);
+ mQuality, /* publish */ true, owner);
return CallbackToFutureAdapter.getFuture(
(completer) -> {
@@ -174,28 +177,6 @@
);
}
- /**
- * Delete the entry.
- *
- * @param executor the thread for execution
- * @param uri the uri of the image to publish
- *
- * @return a listenable future result
- */
- ListenableFuture<Result> delete(Executor executor, Uri uri) {
- return CallbackToFutureAdapter.getFuture((completer) -> {
- executor.execute(() -> {
- mResolver.delete(uri, null);
-
- Result result = new Result();
- result.uri = uri;
- result.deleted = true;
- completer.set(result);
- });
- return "ContentResolver#delete";
- });
- }
-
static class Result {
Uri uri;
UUID requestId;
@@ -203,7 +184,6 @@
long timestamp;
CompressFormat format;
boolean published;
- boolean deleted;
@Override
public String toString() {
@@ -214,7 +194,6 @@
sb.append(", timestamp=").append(timestamp);
sb.append(", format=").append(format);
sb.append(", published=").append(published);
- sb.append(", deleted=").append(deleted);
sb.append('}');
return sb.toString();
}
@@ -227,17 +206,19 @@
private final ZonedDateTime mCaptureTime;
private final CompressFormat mFormat;
private final int mQuality;
+ private final UserHandle mOwner;
private final String mFileName;
private final boolean mPublish;
Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
- CompressFormat format, int quality, boolean publish) {
+ CompressFormat format, int quality, boolean publish, UserHandle owner) {
mResolver = resolver;
mRequestId = requestId;
mBitmap = bitmap;
mCaptureTime = captureTime;
mFormat = format;
mQuality = quality;
+ mOwner = owner;
mFileName = createFilename(mCaptureTime, mFormat);
mPublish = publish;
}
@@ -253,7 +234,7 @@
start = Instant.now();
}
- uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName);
+ uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner);
throwIfInterrupted();
writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -297,15 +278,20 @@
}
private static Uri createEntry(ContentResolver resolver, CompressFormat format,
- ZonedDateTime time, String fileName) throws ImageExportException {
+ ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException {
Trace.beginSection("ImageExporter_createEntry");
try {
final ContentValues values = createMetadata(time, format, fileName);
- Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ if (UserHandle.myUserId() != owner.getIdentifier()) {
+ baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+ }
+ Uri uri = resolver.insert(baseUri, values);
if (uri == null) {
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
}
+ Log.d(TAG, "Inserted new URI: " + uri);
return uri;
} finally {
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index ba6e98e..8bf956b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -387,7 +388,9 @@
mOutputBitmap = renderBitmap(drawable, bounds);
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
- mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now());
+ mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
+ // TODO: Owner must match the owner of the captured window.
+ Process.myUserHandle());
exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index f248d69..077ad35 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -48,6 +48,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.google.common.util.concurrent.ListenableFuture;
@@ -71,6 +73,7 @@
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
private final Context mContext;
+ private FeatureFlags mFlags;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final ScreenshotController.SaveImageInBackgroundData mParams;
private final ScreenshotController.SavedImageData mImageData;
@@ -84,7 +87,10 @@
private final ImageExporter mImageExporter;
private long mImageTime;
- SaveImageInBackgroundTask(Context context, ImageExporter exporter,
+ SaveImageInBackgroundTask(
+ Context context,
+ FeatureFlags flags,
+ ImageExporter exporter,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotController.SaveImageInBackgroundData data,
Supplier<ActionTransition> sharedElementTransition,
@@ -92,6 +98,7 @@
screenshotNotificationSmartActionsProvider
) {
mContext = context;
+ mFlags = flags;
mScreenshotSmartActions = screenshotSmartActions;
mImageData = new ScreenshotController.SavedImageData();
mQuickShareData = new ScreenshotController.QuickShareData();
@@ -117,7 +124,8 @@
}
// TODO: move to constructor / from ScreenshotRequest
final UUID requestId = UUID.randomUUID();
- final UserHandle user = getUserHandleOfForegroundApplication(mContext);
+ final UserHandle user = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ ? mParams.owner : getUserHandleOfForegroundApplication(mContext);
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
@@ -133,8 +141,9 @@
// Call synchronously here since already on a background thread.
ListenableFuture<ImageExporter.Result> future =
- mImageExporter.export(Runnable::run, requestId, image);
+ mImageExporter.export(Runnable::run, requestId, image, mParams.owner);
ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
final Uri uri = result.uri;
mImageTime = result.timestamp;
@@ -157,6 +166,7 @@
}
mImageData.uri = uri;
+ mImageData.owner = user;
mImageData.smartActions = smartActions;
mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 3fee232..704e115 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,6 +34,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -57,7 +58,9 @@
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -90,6 +93,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.util.Assert;
@@ -151,6 +155,7 @@
public Consumer<Uri> finisher;
public ScreenshotController.ActionsReadyListener mActionsReadyListener;
public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener;
+ public UserHandle owner;
void clearImage() {
image = null;
@@ -167,6 +172,8 @@
public Notification.Action deleteAction;
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
+ public UserHandle owner;
+
/**
* POD for shared element transition.
@@ -242,6 +249,7 @@
private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private final WindowContext mContext;
+ private final FeatureFlags mFlags;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -288,6 +296,7 @@
@Inject
ScreenshotController(
Context context,
+ FeatureFlags flags,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController screenshotNotificationsController,
ScrollCaptureClient scrollCaptureClient,
@@ -331,6 +340,7 @@
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class);
+ mFlags = flags;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -377,7 +387,6 @@
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
Consumer<Uri> finisher, RequestCallback requestCallback) {
- // TODO: use task Id, userId, topComponent for smart handler
Assert.isMainThread();
if (screenshot == null) {
Log.e(TAG, "Got null bitmap from screenshot message");
@@ -395,25 +404,7 @@
}
mCurrentRequestCallback = requestCallback;
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
- showFlash);
- }
-
- /**
- * Displays a screenshot selector
- */
- @MainThread
- void takeScreenshotPartial(ComponentName topComponent,
- final Consumer<Uri> finisher, RequestCallback requestCallback) {
- Assert.isMainThread();
- mScreenshotView.reset();
- mCurrentRequestCallback = requestCallback;
-
- attachWindow();
- mWindow.setContentView(mScreenshotView);
- mScreenshotView.requestApplyInsets();
-
- mScreenshotView.takePartialScreenshot(
- rect -> takeScreenshotInternal(topComponent, finisher, rect));
+ showFlash, UserHandle.of(userId));
}
/**
@@ -543,14 +534,15 @@
return;
}
- saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
+ saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
+ Process.myUserHandle());
mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
ClipboardOverlayController.SELF_PERMISSION);
}
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
- Insets screenInsets, ComponentName topComponent, boolean showFlash) {
+ Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
withWindowAttached(() ->
mScreenshotView.announceForAccessibility(
mContext.getResources().getString(R.string.screenshot_saving_title)));
@@ -575,11 +567,11 @@
mScreenBitmap = screenshot;
- if (!isUserSetupComplete()) {
+ if (!isUserSetupComplete(owner)) {
Log.w(TAG, "User setup not complete, displaying toast only");
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
// and sharing shouldn't be exposed to the user.
- saveScreenshotAndToast(finisher);
+ saveScreenshotAndToast(owner, finisher);
return;
}
@@ -587,7 +579,7 @@
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
- saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
+ saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
this::showUiOnQuickShareActionReady);
// The window is focusable by default
@@ -853,11 +845,12 @@
* Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
* failure).
*/
- private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+ private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) {
// Play the shutter sound to notify that we've taken a screenshot
playCameraSound();
saveScreenshotInWorkerThread(
+ owner,
/* onComplete */ finisher,
/* actionsReadyListener */ imageData -> {
if (DEBUG_CALLBACK) {
@@ -925,9 +918,11 @@
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
- private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
- @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
- @Nullable ScreenshotController.QuickShareActionReadyListener
+ private void saveScreenshotInWorkerThread(
+ UserHandle owner,
+ @NonNull Consumer<Uri> finisher,
+ @Nullable ActionsReadyListener actionsReadyListener,
+ @Nullable QuickShareActionReadyListener
quickShareActionsReadyListener) {
ScreenshotController.SaveImageInBackgroundData
data = new ScreenshotController.SaveImageInBackgroundData();
@@ -935,13 +930,14 @@
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
+ data.owner = owner;
if (mSaveInBgTask != null) {
// just log success/failure for the pre-existing screenshot
mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
}
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
mScreenshotSmartActions, data, getActionTransitionSupplier(),
mScreenshotNotificationSmartActionsProvider);
mSaveInBgTask.execute();
@@ -960,6 +956,15 @@
mScreenshotHandler.resetTimeout();
if (imageData.uri != null) {
+ if (!imageData.owner.equals(Process.myUserHandle())) {
+ // TODO: Handle non-primary user ownership (e.g. Work Profile)
+ // This image is owned by another user. Special treatment will be
+ // required in the UI (badging) as well as sending intents which can
+ // correctly forward those URIs on to be read (actions).
+
+ Log.d(TAG, "*** Screenshot saved to a non-primary user ("
+ + imageData.owner + ") as " + imageData.uri);
+ }
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@@ -1033,9 +1038,9 @@
}
}
- private boolean isUserSetupComplete() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ private boolean isUserSetupComplete(UserHandle owner) {
+ return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index c2a5060..3a35286 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -68,7 +68,9 @@
}
override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
- return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+ val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+ Log.d(TAG, "isManagedProfile: $managed")
+ return managed
}
private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
deleted file mode 100644
index c793b5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.screenshot;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import java.util.function.Consumer;
-
-/**
- * Draws a selection rectangle while taking screenshot
- */
-public class ScreenshotSelectorView extends View {
- private Point mStartPoint;
- private Rect mSelectionRect;
- private final Paint mPaintSelection, mPaintBackground;
-
- private Consumer<Rect> mOnScreenshotSelected;
-
- public ScreenshotSelectorView(Context context) {
- this(context, null);
- }
-
- public ScreenshotSelectorView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mPaintBackground = new Paint(Color.BLACK);
- mPaintBackground.setAlpha(160);
- mPaintSelection = new Paint(Color.TRANSPARENT);
- mPaintSelection.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- setOnTouchListener((v, event) -> {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- startSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_MOVE:
- updateSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_UP:
- setVisibility(View.GONE);
- final Rect rect = getSelectionRect();
- if (mOnScreenshotSelected != null
- && rect != null
- && rect.width() != 0 && rect.height() != 0) {
- mOnScreenshotSelected.accept(rect);
- }
- stopSelection();
- return true;
- }
- return false;
- });
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground);
- if (mSelectionRect != null) {
- canvas.drawRect(mSelectionRect, mPaintSelection);
- }
- }
-
- void setOnScreenshotSelected(Consumer<Rect> onScreenshotSelected) {
- mOnScreenshotSelected = onScreenshotSelected;
- }
-
- void stop() {
- if (getSelectionRect() != null) {
- stopSelection();
- }
- }
-
- private void startSelection(int x, int y) {
- mStartPoint = new Point(x, y);
- mSelectionRect = new Rect(x, y, x, y);
- }
-
- private void updateSelection(int x, int y) {
- if (mSelectionRect != null) {
- mSelectionRect.left = Math.min(mStartPoint.x, x);
- mSelectionRect.right = Math.max(mStartPoint.x, x);
- mSelectionRect.top = Math.min(mStartPoint.y, y);
- mSelectionRect.bottom = Math.max(mStartPoint.y, y);
- invalidate();
- }
- }
-
- private Rect getSelectionRect() {
- return mSelectionRect;
- }
-
- private void stopSelection() {
- mStartPoint = null;
- mSelectionRect = null;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 360fc87..be41a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -93,7 +93,6 @@
import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;
-import java.util.function.Consumer;
/**
* Handles the visual elements and animations for the screenshot flow.
@@ -141,7 +140,6 @@
private boolean mOrientationPortrait;
private boolean mDirectionLTR;
- private ScreenshotSelectorView mScreenshotSelectorView;
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
private ImageView mScreenshotPreview;
@@ -361,7 +359,6 @@
mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
- mScreenshotSelectorView = requireNonNull(findViewById(R.id.screenshot_selector));
mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
@@ -377,8 +374,6 @@
mActionsContainerBackground.setTouchDelegate(actionsDelegate);
setFocusable(true);
- mScreenshotSelectorView.setFocusable(true);
- mScreenshotSelectorView.setFocusableInTouchMode(true);
mActionsContainer.setScrollX(0);
mNavMode = getResources().getInteger(
@@ -432,12 +427,6 @@
mCallbacks = callbacks;
}
- void takePartialScreenshot(Consumer<Rect> onPartialScreenshotSelected) {
- mScreenshotSelectorView.setOnScreenshotSelected(onPartialScreenshotSelected);
- mScreenshotSelectorView.setVisibility(View.VISIBLE);
- mScreenshotSelectorView.requestFocus();
- }
-
void setScreenshot(Bitmap bitmap, Insets screenInsets) {
mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
}
@@ -1031,7 +1020,6 @@
mQuickShareChip = null;
setAlpha(1);
mScreenshotStatic.setAlpha(1);
- mScreenshotSelectorView.stop();
}
private void startSharedTransition(ActionTransition transition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 83b60fb..30a0b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -78,6 +78,7 @@
static class LongScreenshot {
private final ImageTileSet mImageTileSet;
private final Session mSession;
+ // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
LongScreenshot(Session session, ImageTileSet imageTileSet) {
mSession = session;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 695a80b..a4a59ce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -249,12 +249,6 @@
}
mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
break;
- case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
- if (DEBUG_SERVICE) {
- Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
- }
- mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback);
- break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index a22fda7..6e9f859 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -25,6 +25,7 @@
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -76,6 +77,12 @@
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
// The brightness mirror container is INVISIBLE by default.
frame.setVisibility(View.VISIBLE);
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) frame.getLayoutParams();
+ int horizontalMargin =
+ getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+ lp.leftMargin = horizontalMargin;
+ lp.rightMargin = horizontalMargin;
+ frame.setLayoutParams(lp);
BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
controller.init();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index e0cd482..ba779c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -20,8 +20,8 @@
import android.view.ViewGroup
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
-import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -36,11 +36,11 @@
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
- ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
- ViewIdToTranslate(R.id.rightLayout, RIGHT),
- ViewIdToTranslate(R.id.clock, LEFT),
- ViewIdToTranslate(R.id.date, LEFT)),
+ ViewIdToTranslate(R.id.quick_settings_panel, START),
+ ViewIdToTranslate(R.id.notification_stack_scroller, END),
+ ViewIdToTranslate(R.id.rightLayout, END),
+ ViewIdToTranslate(R.id.clock, START),
+ ViewIdToTranslate(R.id.date, START)),
progressProvider = progressProvider)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d7e86b6..1110386 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -425,11 +425,10 @@
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsExpanding;
- private boolean mBlockTouches;
-
/**
* Determines if QS should be already expanded when expanding shade.
* Used for split shade, two finger gesture as well as accessibility shortcut to QS.
+ * It needs to be set when movement starts as it resets at the end of expansion/collapse.
*/
@VisibleForTesting
boolean mQsExpandImmediate;
@@ -1692,7 +1691,6 @@
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
- mBlockTouches = false;
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -1737,8 +1735,10 @@
}
private void setQsExpandImmediate(boolean expandImmediate) {
- mQsExpandImmediate = expandImmediate;
- mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ if (expandImmediate != mQsExpandImmediate) {
+ mQsExpandImmediate = expandImmediate;
+ mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ }
}
private void setShowShelfOnly(boolean shelfOnly) {
@@ -2479,17 +2479,23 @@
mDepthController.setQsPanelExpansion(qsExpansionFraction);
mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
- // updateQsExpansion will get called whenever mTransitionToFullShadeProgress or
- // mLockscreenShadeTransitionController.getDragProgress change.
- // When in lockscreen, getDragProgress indicates the true expanded fraction of QS
- float shadeExpandedFraction = mTransitioningToFullShadeProgress > 0
- ? mLockscreenShadeTransitionController.getQSDragProgress()
+ float shadeExpandedFraction = isOnKeyguard()
+ ? getLockscreenShadeDragProgress()
: getExpandedFraction();
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
}
+ private float getLockscreenShadeDragProgress() {
+ // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
+ // transition. If that's not the case we should follow QS expansion fraction for when
+ // user is pulling from the same top to go directly to expanded QS
+ return mTransitioningToFullShadeProgress > 0
+ ? mLockscreenShadeTransitionController.getQSDragProgress()
+ : computeQsExpansionFraction();
+ }
+
private void onStackYChanged(boolean shouldAnimate) {
if (mQs != null) {
if (shouldAnimate) {
@@ -3124,26 +3130,24 @@
}
if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
&& !mQsExpansionFromOverscroll)) {
- float t;
- if (mKeyguardShowing) {
-
+ float qsExpansionFraction;
+ if (mSplitShadeEnabled) {
+ qsExpansionFraction = 1;
+ } else if (mKeyguardShowing) {
// On Keyguard, interpolate the QS expansion linearly to the panel expansion
- t = expandedHeight / (getMaxPanelHeight());
+ qsExpansionFraction = expandedHeight / (getMaxPanelHeight());
} else {
// In Shade, interpolate linearly such that QS is closed whenever panel height is
// minimum QS expansion + minStackHeight
- float
- panelHeightQsCollapsed =
+ float panelHeightQsCollapsed =
mNotificationStackScrollLayoutController.getIntrinsicPadding()
+ mNotificationStackScrollLayoutController.getLayoutMinHeight();
float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
- t =
- (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
- - panelHeightQsCollapsed);
+ qsExpansionFraction = (expandedHeight - panelHeightQsCollapsed)
+ / (panelHeightQsExpanded - panelHeightQsCollapsed);
}
- float
- targetHeight =
- mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+ float targetHeight = mQsMinExpansionHeight
+ + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
setQsExpansion(targetHeight);
}
updateExpandedHeight(expandedHeight);
@@ -3329,7 +3333,11 @@
} else {
setListening(true);
}
- setQsExpandImmediate(false);
+ if (mBarState != SHADE) {
+ // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
+ // on keyguard panel state is always OPEN so we need to have that extra update
+ setQsExpandImmediate(false);
+ }
setShowShelfOnly(false);
mTwoFingerQsExpandPossible = false;
updateTrackingHeadsUp(null);
@@ -4187,7 +4195,7 @@
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
+ "," + event.getY() + ")");
}
- if (mBlockTouches || mQs.disallowPanelTouches()) {
+ if (mQs.disallowPanelTouches()) {
return false;
}
initDownStates(event);
@@ -4230,8 +4238,7 @@
}
- if (mBlockTouches || (mQsFullyExpanded && mQs != null
- && mQs.disallowPanelTouches())) {
+ if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) {
return false;
}
@@ -4678,12 +4685,19 @@
}
}
} else {
+ // this else branch means we are doing one of:
+ // - from KEYGUARD and SHADE (but not expanded shade)
+ // - from SHADE to KEYGUARD
+ // - from SHADE_LOCKED to SHADE
+ // - getting notified again about the current SHADE or KEYGUARD state
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
if (!animatingUnlockedShadeToKeyguard) {
// Only make the status bar visible if we're not animating the screen off, since
// we only want to be showing the clock/notifications during the animation.
+ mShadeLog.v("Updating keyguard status bar state to "
+ + (keyguardShowing ? "visible" : "invisible"));
mKeyguardStatusBarViewController.updateViewState(
/* alpha= */ 1f,
keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -4749,9 +4763,7 @@
@Override
public float getLockscreenShadeDragProgress() {
- return mTransitioningToFullShadeProgress > 0
- ? mLockscreenShadeTransitionController.getQSDragProgress()
- : computeQsExpansionFraction();
+ return NotificationPanelViewController.this.getLockscreenShadeDragProgress();
}
};
@@ -4988,6 +5000,7 @@
updateQSExpansionEnabledAmbient();
if (state == STATE_OPEN && mCurrentPanelState != state) {
+ setQsExpandImmediate(false);
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
if (state == STATE_OPENING) {
@@ -5000,6 +5013,7 @@
mCentralSurfaces.makeExpandedVisible(false);
}
if (state == STATE_CLOSED) {
+ setQsExpandImmediate(false);
// Close the status bar in the next frame so we can show the end of the
// animation.
mView.post(mMaybeHideExpandedRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064..ce730ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@
import android.widget.FrameLayout
/**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * A temporary base class that's shared between our old status bar connectivity view implementations
+ * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
+ * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
*
* Once our refactor is over, we should be able to delete this go-between class and the old view
* class.
*/
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8699441..c290ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -42,7 +42,6 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,6 +96,7 @@
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
+ private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -157,7 +157,7 @@
break;
case Intent.ACTION_USER_UNLOCKED:
// Start the overview connection to the launcher service
- Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
break;
case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
final IntentSender intentSender = intent.getParcelableExtra(
@@ -199,6 +199,7 @@
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
+ Lazy<OverviewProxyService> overviewProxyServiceLazy,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
@@ -214,6 +215,7 @@
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
+ mOverviewProxyServiceLazy = overviewProxyServiceLazy;
statusBarStateController.addCallback(this);
mLockPatternUtils = new LockPatternUtils(context);
mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c900c5a..4be5a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -43,7 +43,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -89,11 +88,9 @@
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final StatusBarStateController mStatusBarStateController
- = Dependency.get(StatusBarStateController.class);
- private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
+ private final StatusBarStateController mStatusBarStateController;
+ private final SysuiColorExtractor mColorExtractor;
+ private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
@@ -179,6 +176,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
@@ -192,6 +192,9 @@
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
+ mStatusBarStateController = statusBarStateController;
+ mColorExtractor = colorExtractor;
+ mKeyguardStateController = keyguardStateController;
setupNotifPipeline();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d67f94f..f961984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -189,22 +189,22 @@
viewState.copyFrom(lastViewState);
viewState.height = getIntrinsicHeight();
- viewState.zTranslation = ambientState.getBaseZHeight();
+ viewState.setZTranslation(ambientState.getBaseZHeight());
viewState.clipTopAmount = 0;
if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
float expansion = ambientState.getExpansionFraction();
if (ambientState.isBouncerInTransit()) {
- viewState.alpha = aboutToShowBouncerProgress(expansion);
+ viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
}
} else {
- viewState.alpha = 1f - ambientState.getHideAmount();
+ viewState.setAlpha(1f - ambientState.getHideAmount());
}
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
- viewState.xTranslation = getTranslationX();
+ viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.firstViewInShelf = algorithmState.firstViewInShelf;
if (mNotGoneIndex != -1) {
@@ -230,7 +230,7 @@
}
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
- viewState.yTranslation = stackEnd - viewState.height;
+ viewState.setYTranslation(stackEnd - viewState.height);
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -794,7 +794,7 @@
if (iconState == null) {
return;
}
- iconState.alpha = ICON_ALPHA_INTERPOLATOR.getInterpolation(transitionAmount);
+ iconState.setAlpha(ICON_ALPHA_INTERPOLATOR.getInterpolation(transitionAmount));
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
@@ -809,12 +809,12 @@
// Fade in icons at shelf start
// This is important for conversation icons, which are badged and need x reset
- iconState.xTranslation = mShelfIcons.getActualPaddingStart();
+ iconState.setXTranslation(mShelfIcons.getActualPaddingStart());
final boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
if (stayingInShelf) {
iconState.iconAppearAmount = 1.0f;
- iconState.alpha = 1.0f;
+ iconState.setAlpha(1.0f);
iconState.hidden = false;
}
int backgroundColor = getBackgroundColorWithoutTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 48c6e27..fdad101 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,7 +42,10 @@
import java.util.ArrayList;
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
StatusIconDisplayable {
private static final String TAG = "StatusBarMobileView";
@@ -101,11 +103,6 @@
super(context, attrs, defStyleAttr);
}
- public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
@Override
public void getDrawingRect(Rect outRect) {
super.getDrawingRect(outRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3e74d9..decc70d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@
/**
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
*/
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
private static final String TAG = "StatusBarWifiView";
/// Used to show etc dots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7cd79ca..11e3d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,6 +26,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -130,6 +131,9 @@
NotifCollection notifCollection,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
+ StatusBarStateController statusBarStateController,
+ SysuiColorExtractor colorExtractor,
+ KeyguardStateController keyguardStateController,
DumpManager dumpManager) {
return new NotificationMediaManager(
context,
@@ -142,6 +146,9 @@
notifCollection,
mainExecutor,
mediaDataManager,
+ statusBarStateController,
+ colorExtractor,
+ keyguardStateController,
dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3eaa988..e129ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1398,7 +1398,7 @@
throw exception;
}
- Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+ Log.wtf(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+ " consecutive reentrant notification pipeline rebuild(s).", exception);
mChoreographer.schedule();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8278b54..ccf6fec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -393,7 +393,7 @@
val posted = mPostedEntries.compute(entry.key) { _, value ->
value?.also { update ->
update.wasUpdated = true
- update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+ update.shouldHeadsUpEver = shouldHeadsUpEver
update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
update.isAlerting = isAlerting
update.isBinding = isBinding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 1aa0295..8e646a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -19,6 +19,8 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.ForegroundServiceNotificationListener
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -36,6 +38,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -68,6 +71,8 @@
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
private val fgsNotifListener: ForegroundServiceNotificationListener,
+ private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
+ private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -107,6 +112,9 @@
peopleSpaceWidgetManager.attach(notificationListener)
fgsNotifListener.init()
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
+ memoryMonitor.get().init()
+ }
}
// TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
new file mode 100644
index 0000000..832a739
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.statusbar.notification.logging
+
+/** Describes usage of a notification. */
+data class NotificationMemoryUsage(
+ val packageName: String,
+ val notificationId: String,
+ val objectUsage: NotificationObjectUsage,
+)
+
+/**
+ * Describes current memory usage of a [android.app.Notification] object.
+ *
+ * The values are in bytes.
+ */
+data class NotificationObjectUsage(
+ val smallIcon: Int,
+ val largeIcon: Int,
+ val extras: Int,
+ val style: String?,
+ val styleIcon: Int,
+ val bigPicture: Int,
+ val extender: Int,
+ val hasCustomView: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
new file mode 100644
index 0000000..958978e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -0,0 +1,243 @@
+/*
+ *
+ * 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.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.core.util.contains
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** This class monitors and logs current Notification memory use. */
+@SysUISingleton
+class NotificationMemoryMonitor
+@Inject
+constructor(
+ val notificationPipeline: NotifPipeline,
+ val dumpManager: DumpManager,
+) : Dumpable {
+
+ companion object {
+ private const val TAG = "NotificationMemMonitor"
+ private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+ private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+ private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ }
+
+ fun init() {
+ Log.d(TAG, "NotificationMemoryMonitor initialized.")
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+ }
+
+ @WorkerThread
+ fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
+ return notificationMemoryUse(notificationPipeline.allNotifs)
+ }
+
+ /** Returns a list of memory use entries for currently shown notifications. */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notifications: Collection<NotificationEntry>
+ ): List<NotificationMemoryUsage> {
+ return notifications
+ .asSequence()
+ .map { entry ->
+ val packageName = entry.sbn.packageName
+ val notificationObjectUsage =
+ computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
+ NotificationMemoryUsage(
+ packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationObjectUsage
+ )
+ }
+ .toList()
+ }
+
+ /**
+ * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+ * inspect Bitmaps in the object and provide summary of memory usage.
+ */
+ private fun computeNotificationObjectUse(
+ notification: Notification,
+ seenBitmaps: HashSet<Int>
+ ): NotificationObjectUsage {
+ val extras = notification.extras
+ val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+ val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+ // Collect memory usage of extra styles
+
+ // Big Picture
+ val bigPictureIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+ val bigPictureUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+ // People
+ val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+ val peopleUse =
+ peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+ // Calling
+ val callingPersonUse =
+ computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+ val verificationIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+ // Messages
+ val messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ )
+ val messagesUse =
+ messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+ val historicMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ )
+ val historyicMessagesUse =
+ historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+ // Extenders
+ val carExtender = extras.getBundle(CAR_EXTENSIONS)
+ val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+ val carExtenderIcon =
+ computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+ val tvExtender = extras.getBundle(TV_EXTENSIONS)
+ val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+ val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+ val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+ val wearExtenderBackground =
+ computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+ val style = notification.notificationStyle
+ val hasCustomView = notification.contentView != null || notification.bigContentView != null
+ val extrasSize = computeBundleSize(extras)
+
+ return NotificationObjectUsage(
+ smallIconUse,
+ largeIconUse,
+ extrasSize,
+ style?.simpleName,
+ bigPictureIconUse +
+ peopleUse +
+ callingPersonUse +
+ verificationIconUse +
+ messagesUse +
+ historyicMessagesUse,
+ bigPictureUse,
+ carExtenderSize +
+ carExtenderIcon +
+ tvExtenderSize +
+ wearExtenderSize +
+ wearExtenderBackground,
+ hasCustomView
+ )
+ }
+
+ /**
+ * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+ * bitmaps). Can be slow.
+ */
+ private fun computeBundleSize(extras: Bundle): Int {
+ val parcel = Parcel.obtain()
+ try {
+ extras.writeToParcel(parcel, 0)
+ return parcel.dataSize()
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ /**
+ * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+ * if the key does not exist in extras.
+ */
+ private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+ return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+ is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+ is Icon -> computeIconUse(parcelable, seenBitmaps)
+ is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+ else -> 0
+ }
+ }
+
+ /**
+ * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+ * defined via Uri or a resource.
+ *
+ * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+ */
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ when (icon?.type) {
+ Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+ else -> 0
+ }
+
+ /**
+ * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+ * seenBitmaps set, this method returns 0 to avoid double counting.
+ *
+ * @return memory usage of the bitmap in bytes
+ */
+ private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+ val refId = System.identityHashCode(bitmap)
+ if (seenBitmaps?.contains(refId) == true) {
+ return 0
+ }
+
+ seenBitmaps?.add(refId)
+ return bitmap.allocationByteCount
+ }
+
+ private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+ val refId = System.identityHashCode(icon.dataBytes)
+ if (seenBitmaps.contains(refId)) {
+ return 0
+ }
+
+ seenBitmaps.add(refId)
+ return icon.dataLength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 9faef1b..5ca13c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -45,11 +45,21 @@
void logPanelShown(boolean isLockscreen,
@Nullable List<NotificationEntry> visibleNotifications);
+ /**
+ * Log a NOTIFICATION_PANEL_REPORTED statsd event, with
+ * {@link NotificationPanelEvent#NOTIFICATION_DRAG} as the eventID.
+ *
+ * @param draggedNotification the notification that is being dragged
+ */
+ void logNotificationDrag(NotificationEntry draggedNotification);
+
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "Notification panel shown from status bar.")
NOTIFICATION_PANEL_OPEN_STATUS_BAR(200),
@UiEvent(doc = "Notification panel shown from lockscreen.")
- NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201);
+ NOTIFICATION_PANEL_OPEN_LOCKSCREEN(201),
+ @UiEvent(doc = "Notification was dragged")
+ NOTIFICATION_DRAG(1226);
private final int mId;
NotificationPanelEvent(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 75a6019..9a63228 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -16,12 +16,15 @@
package com.android.systemui.statusbar.notification.logging;
+import static com.android.systemui.statusbar.notification.logging.NotificationPanelLogger.NotificationPanelEvent.NOTIFICATION_DRAG;
+
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.google.protobuf.nano.MessageNano;
+import java.util.Collections;
import java.util.List;
/**
@@ -38,4 +41,14 @@
/* int num_notifications*/ proto.notifications.length,
/* byte[] notifications*/ MessageNano.toByteArray(proto));
}
+
+ @Override
+ public void logNotificationDrag(NotificationEntry draggedNotification) {
+ final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
+ Collections.singletonList(draggedNotification));
+ SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+ /* int event_id */ NOTIFICATION_DRAG.getId(),
+ /* int num_notifications*/ proto.notifications.length,
+ /* byte[] notifications*/ MessageNano.toByteArray(proto));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6138265..087dc71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1477,6 +1477,20 @@
}
}
+ /**
+ * Sets the alpha on the content, while leaving the background of the row itself as is.
+ *
+ * @param alpha alpha value to apply to the notification content
+ */
+ public void setContentAlpha(float alpha) {
+ for (NotificationContentView l : mLayouts) {
+ l.setAlpha(alpha);
+ }
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setAlpha(alpha);
+ }
+ }
+
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
mPrivateLayout.setIsLowPriority(isLowPriority);
@@ -3362,7 +3376,7 @@
private void handleFixedTranslationZ(ExpandableNotificationRow row) {
if (row.hasExpandingChild()) {
- zTranslation = row.getTranslationZ();
+ setZTranslation(row.getTranslationZ());
clipTopAmount = row.getClipTopAmount();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 4939a9c..64f87ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,12 +45,17 @@
import androidx.annotation.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import java.util.Collections;
+
import javax.inject.Inject;
/**
@@ -63,14 +68,17 @@
private final Context mContext;
private final HeadsUpManager mHeadsUpManager;
private final ShadeController mShadeController;
+ private NotificationPanelLogger mNotificationPanelLogger;
@Inject
public ExpandableNotificationRowDragController(Context context,
HeadsUpManager headsUpManager,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ NotificationPanelLogger notificationPanelLogger) {
mContext = context;
mHeadsUpManager = headsUpManager;
mShadeController = shadeController;
+ mNotificationPanelLogger = notificationPanelLogger;
init();
}
@@ -120,12 +128,16 @@
dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent);
dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
ClipData.Item item = new ClipData.Item(dragIntent);
+ InstanceId instanceId = new InstanceIdSequence(Integer.MAX_VALUE).newInstanceId();
+ item.getIntent().putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, instanceId);
ClipData dragData = new ClipData(clipDescription, item);
View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
view.setOnDragListener(getDraggedViewDragListener());
boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL
| View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
if (result) {
+ // Log notification drag only if it succeeds
+ mNotificationPanelLogger.logNotificationDrag(enr.getEntry());
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (enr.isPinned()) {
mHeadsUpManager.releaseAllImmediately();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 1e09b8a..38f0c55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -621,12 +621,12 @@
// initialize with the default values of the view
mViewState.height = getIntrinsicHeight();
mViewState.gone = getVisibility() == View.GONE;
- mViewState.alpha = 1f;
+ mViewState.setAlpha(1f);
mViewState.notGoneIndex = -1;
- mViewState.xTranslation = getTranslationX();
+ mViewState.setXTranslation(getTranslationX());
mViewState.hidden = false;
- mViewState.scaleX = getScaleX();
- mViewState.scaleY = getScaleY();
+ mViewState.setScaleX(getScaleX());
+ mViewState.setScaleY(getScaleY());
mViewState.inShelf = false;
mViewState.headsUpIsVisible = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index df81c0e..8de0365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1986,6 +1986,25 @@
public void setRemoteInputVisible(boolean remoteInputVisible) {
mRemoteInputVisible = remoteInputVisible;
setClipChildren(!remoteInputVisible);
+ setActionsImportanceForAccessibility(
+ remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
+ private void setActionsImportanceForAccessibility(int mode) {
+ if (mExpandedChild != null) {
+ setActionsImportanceForAccessibility(mode, mExpandedChild);
+ }
+ if (mHeadsUpChild != null) {
+ setActionsImportanceForAccessibility(mode, mHeadsUpChild);
+ }
+ }
+
+ private void setActionsImportanceForAccessibility(int mode, View child) {
+ View actionsCandidate = child.findViewById(com.android.internal.R.id.actions);
+ if (actionsCandidate != null) {
+ actionsCandidate.setImportantForAccessibility(mode);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index d77e03f..7b23a56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -583,24 +583,26 @@
ExpandableViewState childState = child.getViewState();
int intrinsicHeight = child.getIntrinsicHeight();
childState.height = intrinsicHeight;
- childState.yTranslation = yPosition + launchTransitionCompensation;
+ childState.setYTranslation(yPosition + launchTransitionCompensation);
childState.hidden = false;
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
- childState.zTranslation =
- (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
- ? parentState.zTranslation
- : 0;
+ if (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications) {
+ childState.setZTranslation(parentState.getZTranslation());
+ } else {
+ childState.setZTranslation(0);
+ }
childState.dimmed = parentState.dimmed;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
- childState.alpha = 0;
+ childState.setAlpha(0);
if (i < firstOverflowIndex) {
- childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
+ childState.setAlpha(showingAsLowPriority() ? expandFactor : 1.0f);
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
- childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
- childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
+ childState.setAlpha(
+ (mActualHeight - childState.getYTranslation()) / childState.height);
+ childState.setAlpha(Math.max(0.0f, Math.min(1.0f, childState.getAlpha())));
}
childState.location = parentState.location;
childState.inShelf = parentState.inShelf;
@@ -621,13 +623,16 @@
if (mirrorView.getVisibility() == GONE) {
mirrorView = alignView;
}
- mGroupOverFlowState.alpha = mirrorView.getAlpha();
- mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
+ mGroupOverFlowState.setAlpha(mirrorView.getAlpha());
+ float yTranslation = mGroupOverFlowState.getYTranslation()
+ + NotificationUtils.getRelativeYOffset(
mirrorView, overflowView);
+ mGroupOverFlowState.setYTranslation(yTranslation);
}
} else {
- mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
- mGroupOverFlowState.alpha = 0.0f;
+ mGroupOverFlowState.setYTranslation(
+ mGroupOverFlowState.getYTranslation() + mNotificationHeaderMargin);
+ mGroupOverFlowState.setAlpha(0.0f);
}
}
if (mNotificationHeader != null) {
@@ -635,11 +640,11 @@
mHeaderViewState = new ViewState();
}
mHeaderViewState.initFrom(mNotificationHeader);
- mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
- ? parentState.zTranslation
- : 0;
- mHeaderViewState.yTranslation = mCurrentHeaderTranslation;
- mHeaderViewState.alpha = mHeaderVisibleAmount;
+ mHeaderViewState.setZTranslation(childrenExpandedAndNotAnimating
+ ? parentState.getZTranslation()
+ : 0);
+ mHeaderViewState.setYTranslation(mCurrentHeaderTranslation);
+ mHeaderViewState.setAlpha(mHeaderVisibleAmount);
// The hiding is done automatically by the alpha, otherwise we'll pick it up again
// in the next frame with the initFrom call above and have an invisible header
mHeaderViewState.hidden = false;
@@ -711,14 +716,14 @@
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
- tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
- if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
+ tmpState.setYTranslation(viewState.getYTranslation() - mDividerHeight);
+ float alpha = mChildrenExpanded && viewState.getAlpha() != 0 ? mDividerAlpha : 0;
+ if (mUserLocked && !showingAsLowPriority() && viewState.getAlpha() != 0) {
alpha = NotificationUtils.interpolate(0, mDividerAlpha,
- Math.min(viewState.alpha, expandFraction));
+ Math.min(viewState.getAlpha(), expandFraction));
}
tmpState.hidden = !dividersVisible;
- tmpState.alpha = alpha;
+ tmpState.setAlpha(alpha);
tmpState.applyToView(divider);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
@@ -790,24 +795,24 @@
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
- tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
- if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
+ tmpState.setYTranslation(viewState.getYTranslation() - mDividerHeight);
+ float alpha = mChildrenExpanded && viewState.getAlpha() != 0 ? mDividerAlpha : 0;
+ if (mUserLocked && !showingAsLowPriority() && viewState.getAlpha() != 0) {
alpha = NotificationUtils.interpolate(0, mDividerAlpha,
- Math.min(viewState.alpha, expandFraction));
+ Math.min(viewState.getAlpha(), expandFraction));
}
tmpState.hidden = !dividersVisible;
- tmpState.alpha = alpha;
+ tmpState.setAlpha(alpha);
tmpState.animateTo(divider, properties);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
if (mOverflowNumber != null) {
if (mNeverAppliedGroupState) {
- float alpha = mGroupOverFlowState.alpha;
- mGroupOverFlowState.alpha = 0;
+ float alpha = mGroupOverFlowState.getAlpha();
+ mGroupOverFlowState.setAlpha(0);
mGroupOverFlowState.applyToView(mOverflowNumber);
- mGroupOverFlowState.alpha = alpha;
+ mGroupOverFlowState.setAlpha(alpha);
mNeverAppliedGroupState = false;
}
mGroupOverFlowState.animateTo(mOverflowNumber, properties);
@@ -949,7 +954,7 @@
child.setAlpha(start);
ViewState viewState = new ViewState();
viewState.initFrom(child);
- viewState.alpha = target;
+ viewState.setAlpha(target);
ALPHA_FADE_IN.setDelay(i * 50);
viewState.animateTo(child, ALPHA_FADE_IN);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index bc172ce..0b435fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -35,12 +35,12 @@
* bounds change.
*/
public class NotificationSection {
- private @PriorityBucket int mBucket;
- private View mOwningView;
- private Rect mBounds = new Rect();
- private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
- private Rect mStartAnimationRect = new Rect();
- private Rect mEndAnimationRect = new Rect();
+ private @PriorityBucket final int mBucket;
+ private final View mOwningView;
+ private final Rect mBounds = new Rect();
+ private final Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
+ private final Rect mStartAnimationRect = new Rect();
+ private final Rect mEndAnimationRect = new Rect();
private ObjectAnimator mTopAnimator = null;
private ObjectAnimator mBottomAnimator = null;
private ExpandableView mFirstVisibleChild;
@@ -277,7 +277,6 @@
}
}
}
- top = Math.max(minTopPosition, top);
ExpandableView lastView = getLastVisibleChild();
if (lastView != null) {
float finalTranslationY = ViewState.getFinalTranslationY(lastView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5fbaa51..836cacc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -135,7 +135,7 @@
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
// Delay in milli-seconds before shade closes for clear all.
- private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+ private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
@@ -152,7 +152,7 @@
private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
private boolean mKeyguardBypassEnabled;
- private ExpandHelper mExpandHelper;
+ private final ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
@@ -165,12 +165,7 @@
private VelocityTracker mVelocityTracker;
private OverScroller mScroller;
- /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
- private int mLastScrollerY;
- /**
- * True if the max position was set to a known position on the last call to {@link #mScroller}.
- */
- private boolean mIsScrollerBoundSet;
+
private Runnable mFinishScrollingCallback;
private int mTouchSlop;
private float mSlopMultiplier;
@@ -194,7 +189,6 @@
private int mContentHeight;
private float mIntrinsicContentHeight;
- private int mCollapsedSize;
private int mPaddingBetweenElements;
private int mMaxTopPadding;
private int mTopPadding;
@@ -210,15 +204,15 @@
private final StackScrollAlgorithm mStackScrollAlgorithm;
private final AmbientState mAmbientState;
- private GroupMembershipManager mGroupMembershipManager;
- private GroupExpansionManager mGroupExpansionManager;
- private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
- private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
- private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
- private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
- private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
- private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
- private ArrayList<View> mSwipedOutViews = new ArrayList<>();
+ private final GroupMembershipManager mGroupMembershipManager;
+ private final GroupExpansionManager mGroupExpansionManager;
+ private final HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
+ private final ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+ private final ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
+ private final ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
+ private final HashSet<View> mFromMoreCardAdditions = new HashSet<>();
+ private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+ private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
@@ -296,7 +290,7 @@
private boolean mDisallowDismissInThisMotion;
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
- private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
+ private final ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -309,17 +303,16 @@
};
private NotificationStackScrollLogger mLogger;
private CentralSurfaces mCentralSurfaces;
- private int[] mTempInt2 = new int[2];
+ private final int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
- private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
- private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
- private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
+ private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
+ private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
+ private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
- private boolean mTrackingHeadsUp;
private boolean mForceNoOverlappingRendering;
private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
private boolean mAnimationRunning;
- private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
+ private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -327,21 +320,21 @@
return true;
}
};
- private NotificationSection[] mSections;
+ private final NotificationSection[] mSections;
private boolean mAnimateNextBackgroundTop;
private boolean mAnimateNextBackgroundBottom;
private boolean mAnimateNextSectionBoundsChange;
private int mBgColor;
private float mDimAmount;
private ValueAnimator mDimAnimator;
- private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
+ private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDimAnimator = null;
}
};
- private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
+ private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
@@ -351,29 +344,23 @@
};
protected ViewGroup mQsHeader;
// Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
- private Rect mQsHeaderBound = new Rect();
+ private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
private boolean mContinuousBackgroundUpdate;
- private ViewTreeObserver.OnPreDrawListener mShadowUpdater
+ private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
= () -> {
updateViewShadows();
return true;
};
- private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
+ private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
updateBackground();
return true;
};
- private Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
+ private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
- if (endY < otherEndY) {
- return -1;
- } else if (endY > otherEndY) {
- return 1;
- } else {
- // The two notifications end at the same location
- return 0;
- }
+ // Return zero when the two notifications end at the same location
+ return Float.compare(endY, otherEndY);
};
private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
@Override
@@ -435,16 +422,14 @@
private int mUpcomingStatusBarState;
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
- private Runnable mReflingAndAnimateScroll = () -> {
- animateScroll();
- };
+ private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
private int mMinimumPaddings;
private int mQsTilePadding;
private boolean mSkinnyNotifsInLandscape;
private int mSidePaddings;
private final Rect mBackgroundAnimationRect = new Rect();
- private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
+ private final ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
private int mHeadsUpInset;
/**
@@ -479,8 +464,6 @@
private int mWaterfallTopInset;
private NotificationStackScrollLayoutController mController;
- private boolean mKeyguardMediaControllorVisible;
-
/**
* The clip path used to clip the view in a rounded way.
*/
@@ -501,7 +484,7 @@
private int mRoundedRectClippingTop;
private int mRoundedRectClippingBottom;
private int mRoundedRectClippingRight;
- private float[] mBgCornerRadii = new float[8];
+ private final float[] mBgCornerRadii = new float[8];
/**
* Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -527,7 +510,7 @@
/**
* Corner radii of the launched notification if it's clipped
*/
- private float[] mLaunchedNotificationRadii = new float[8];
+ private final float[] mLaunchedNotificationRadii = new float[8];
/**
* The notification that is being launched currently.
@@ -779,7 +762,7 @@
y = getLayoutHeight();
drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight() = " + y);
- y = (int) mMaxLayoutHeight;
+ y = mMaxLayoutHeight;
drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight = " + y);
// The space between mTopPadding and mKeyguardBottomPadding determines the available space
@@ -997,7 +980,6 @@
mOverflingDistance = configuration.getScaledOverflingDistance();
Resources res = context.getResources();
- mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mStackScrollAlgorithm.initView(context);
mAmbientState.reload(context);
@@ -1256,12 +1238,9 @@
private void clampScrollPosition() {
int scrollRange = getScrollRange();
if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
- boolean animateStackY = false;
- if (scrollRange < getScrollAmountToScrollBoundary()
- && mAnimateStackYForContentHeightChange) {
- // if the scroll boundary updates the position of the stack,
- animateStackY = true;
- }
+ // if the scroll boundary updates the position of the stack,
+ boolean animateStackY = scrollRange < getScrollAmountToScrollBoundary()
+ && mAnimateStackYForContentHeightChange;
setOwnScrollY(scrollRange, animateStackY);
}
}
@@ -1318,7 +1297,9 @@
+ mAmbientState.getOverExpansion()
- getCurrentOverScrollAmount(false /* top */);
float fraction = mAmbientState.getExpansionFraction();
- if (mAmbientState.isBouncerInTransit()) {
+ // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
+ // overlap. Otherwise, we maintain the normal fraction for smoothness.
+ if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
@@ -1504,7 +1485,6 @@
}
if (mAmbientState.isHiddenAtAll()) {
- clipToOutline = false;
invalidateOutline();
if (isFullyHidden()) {
setClipBounds(null);
@@ -1782,7 +1762,7 @@
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
- private Runnable mReclamp = new Runnable() {
+ private final Runnable mReclamp = new Runnable() {
@Override
public void run() {
int range = getScrollRange();
@@ -3084,11 +3064,8 @@
int currentIndex = indexOfChild(child);
if (currentIndex == -1) {
- boolean isTransient = false;
- if (child instanceof ExpandableNotificationRow
- && child.getTransientContainer() != null) {
- isTransient = true;
- }
+ boolean isTransient = child instanceof ExpandableNotificationRow
+ && child.getTransientContainer() != null;
Log.e(TAG, "Attempting to re-position "
+ (isTransient ? "transient" : "")
+ " view {"
@@ -3149,7 +3126,6 @@
private void generateHeadsUpAnimationEvents() {
for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
ExpandableNotificationRow row = eventPair.first;
- String key = row.getEntry().getKey();
boolean isHeadsUp = eventPair.second;
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
@@ -3212,10 +3188,8 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
- if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
- return false;
- }
- return true;
+ return viewState.getYTranslation() + viewState.height
+ >= mAmbientState.getMaxHeadsUpTranslation();
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4790,7 +4764,6 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setTrackingHeadsUp(ExpandableNotificationRow row) {
mAmbientState.setTrackedHeadsUpRow(row);
- mTrackingHeadsUp = row != null;
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6176,7 +6149,7 @@
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
+ private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
@Override
public ExpandableView getChildAtPosition(float touchX, float touchY) {
return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 843a9ff..5c09d61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -56,12 +56,13 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -178,6 +179,7 @@
@Nullable private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
+ private final FeatureFlags mFeatureFlags;
private View mLongPressedView;
@@ -639,7 +641,8 @@
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
- NotificationStackSizeCalculator notificationStackSizeCalculator) {
+ NotificationStackSizeCalculator notificationStackSizeCalculator,
+ FeatureFlags featureFlags) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -675,6 +678,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
updateResources();
}
@@ -739,9 +743,7 @@
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
- mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly
- mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
-
+ mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 2d2fbe5..ee57411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -185,6 +185,13 @@
return false;
}
+ @Override
+ protected void updateSwipeProgressAlpha(View animView, float alpha) {
+ if (animView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) animView).setContentAlpha(alpha);
+ }
+ }
+
@VisibleForTesting
protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eeed070..8d28f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -134,23 +134,23 @@
if (isHunGoingToShade) {
// Keep 100% opacity for heads up notification going to shade.
- viewState.alpha = 1f;
+ viewState.setAlpha(1f);
} else if (ambientState.isOnKeyguard()) {
// Adjust alpha for wakeup to lockscreen.
- viewState.alpha = 1f - ambientState.getHideAmount();
+ viewState.setAlpha(1f - ambientState.getHideAmount());
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
float expansion = ambientState.getExpansionFraction();
- viewState.alpha = ambientState.isBouncerInTransit()
+ viewState.setAlpha(ambientState.isBouncerInTransit()
? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
- : ShadeInterpolation.getContentAlpha(expansion);
+ : ShadeInterpolation.getContentAlpha(expansion));
}
// For EmptyShadeView if on keyguard, we need to control the alpha to create
// a nice transition when the user is dragging down the notification panel.
if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
final float fractionToShade = ambientState.getFractionToShade();
- viewState.alpha = ShadeInterpolation.getContentAlpha(fractionToShade);
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(fractionToShade));
}
NotificationShelf shelf = ambientState.getShelf();
@@ -166,10 +166,10 @@
continue;
}
- final float shelfTop = shelfState.yTranslation;
- final float viewTop = viewState.yTranslation;
+ final float shelfTop = shelfState.getYTranslation();
+ final float viewTop = viewState.getYTranslation();
if (viewTop >= shelfTop) {
- viewState.alpha = 0;
+ viewState.setAlpha(0);
}
}
}
@@ -277,7 +277,7 @@
if (!child.mustStayOnScreen() || state.headsUpIsVisible) {
clipStart = Math.max(drawStart, clipStart);
}
- float newYTranslation = state.yTranslation;
+ float newYTranslation = state.getYTranslation();
float newHeight = state.height;
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
@@ -322,7 +322,8 @@
childViewState.hideSensitive = hideSensitive;
boolean isActivatedChild = activatedChild == child;
if (dimmed && isActivatedChild) {
- childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
+ childViewState.setZTranslation(childViewState.getZTranslation()
+ + 2.0f * ambientState.getZDistanceBetweenElements());
}
}
}
@@ -527,12 +528,12 @@
// Must set viewState.yTranslation _before_ use.
// Incoming views have yTranslation=0 by default.
- viewState.yTranslation = algorithmState.mCurrentYPosition;
+ viewState.setYTranslation(algorithmState.mCurrentYPosition);
+ float viewEnd = viewState.getYTranslation() + viewState.height + ambientState.getStackY();
maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
- view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
- /* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
- /* hunMax */ ambientState.getMaxHeadsUpTranslation()
+ view.mustStayOnScreen(), /* topVisible */ viewState.getYTranslation() >= 0,
+ viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
);
if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded();
@@ -552,7 +553,7 @@
if (view instanceof EmptyShadeView) {
float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom
- ambientState.getStackY();
- viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
+ viewState.setYTranslation((fullHeight - getMaxAllowedChildHeight(view)) / 2f);
} else if (view != ambientState.getTrackedHeadsUpRow()) {
if (ambientState.isExpansionChanging()) {
// We later update shelf state, then hide views below the shelf.
@@ -591,13 +592,13 @@
+ mPaddingBetweenElements;
setLocation(view.getViewState(), algorithmState.mCurrentYPosition, i);
- viewState.yTranslation += ambientState.getStackY();
+ viewState.setYTranslation(viewState.getYTranslation() + ambientState.getStackY());
}
@VisibleForTesting
void updateViewWithShelf(ExpandableView view, ExpandableViewState viewState, float shelfStart) {
- viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
- if (viewState.yTranslation >= shelfStart) {
+ viewState.setYTranslation(Math.min(viewState.getYTranslation(), shelfStart));
+ if (viewState.getYTranslation() >= shelfStart) {
viewState.hidden = !view.isExpandAnimationRunning()
&& !view.hasExpandingChild();
viewState.inShelf = true;
@@ -690,9 +691,9 @@
if (trackedHeadsUpRow != null) {
ExpandableViewState childState = trackedHeadsUpRow.getViewState();
if (childState != null) {
- float endPosition = childState.yTranslation - ambientState.getStackTranslation();
- childState.yTranslation = MathUtils.lerp(
- headsUpTranslation, endPosition, ambientState.getAppearFraction());
+ float endPos = childState.getYTranslation() - ambientState.getStackTranslation();
+ childState.setYTranslation(MathUtils.lerp(
+ headsUpTranslation, endPos, ambientState.getAppearFraction()));
}
}
@@ -712,7 +713,7 @@
childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
}
boolean isTopEntry = topHeadsUpEntry == row;
- float unmodifiedEndLocation = childState.yTranslation + childState.height;
+ float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
if (mIsExpanded) {
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
&& !row.showingPulsing()) {
@@ -727,13 +728,14 @@
}
}
if (row.isPinned()) {
- childState.yTranslation = Math.max(childState.yTranslation, headsUpTranslation);
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
if (topState != null && !isTopEntry && (!mIsExpanded
- || unmodifiedEndLocation > topState.yTranslation + topState.height)) {
+ || unmodifiedEndLocation > topState.getYTranslation() + topState.height)) {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
childState.height = row.getIntrinsicHeight();
@@ -745,11 +747,12 @@
// heads up show full of row's content and any scroll y indicate that the
// translationY need to move up the HUN.
if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
- childState.yTranslation -= ambientState.getScrollY();
+ childState.setYTranslation(
+ childState.getYTranslation() - ambientState.getScrollY());
}
}
if (row.isHeadsUpAnimatingAway()) {
- childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset);
+ childState.setYTranslation(Math.max(childState.getYTranslation(), mHeadsUpInset));
childState.hidden = false;
}
}
@@ -765,13 +768,13 @@
ExpandableViewState viewState) {
final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
- viewState.yTranslation);
+ viewState.getYTranslation());
// Transition from collapsed pinned state to fully expanded state
// when the pinned HUN approaches its actual location (when scrolling back to top).
- final float distToRealY = newTranslation - viewState.yTranslation;
+ final float distToRealY = newTranslation - viewState.getYTranslation();
viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
- viewState.yTranslation = newTranslation;
+ viewState.setYTranslation(newTranslation);
}
// Pin HUN to bottom of expanded QS
@@ -784,10 +787,10 @@
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
- final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
+ final float newTranslation = Math.min(childState.getYTranslation(), bottomPosition);
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
- newTranslation);
- childState.yTranslation = newTranslation;
+ childState.setYTranslation(newTranslation);
// Animate pinned HUN bottom corners to and from original roundness.
final float originalCornerRadius =
@@ -859,17 +862,17 @@
float baseZ = ambientState.getBaseZHeight();
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
- && childViewState.yTranslation < ambientState.getTopPadding()
+ && childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
if (childrenOnTop != 0.0f) {
childrenOnTop++;
} else {
float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.yTranslation;
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
}
- childViewState.zTranslation = baseZ
- + childrenOnTop * zDistanceBetweenElements;
+ childViewState.setZTranslation(baseZ
+ + childrenOnTop * zDistanceBetweenElements);
} else if (shouldElevateHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
@@ -878,25 +881,28 @@
float shelfStart = ambientState.getInnerHeight()
- shelfHeight + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
- float notificationEnd = childViewState.yTranslation + child.getIntrinsicHeight()
+ float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
+ mPaddingBetweenElements;
if (shelfStart > notificationEnd) {
- childViewState.zTranslation = baseZ;
+ childViewState.setZTranslation(baseZ);
} else {
float factor = (notificationEnd - shelfStart) / shelfHeight;
+ if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+ factor = 1.0f;
+ }
factor = Math.min(factor, 1.0f);
- childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
+ childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
}
} else {
- childViewState.zTranslation = baseZ;
+ childViewState.setZTranslation(baseZ);
}
// We need to scrim the notification more from its surrounding content when we are pinned,
// and we therefore elevate it higher.
// We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
// expanding after which we have a normal elevation again.
- childViewState.zTranslation += (1.0f - child.getHeaderVisibleAmount())
- * mPinnedZTranslationExtra;
+ childViewState.setZTranslation(childViewState.getZTranslation()
+ + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
return childrenOnTop;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 174bf4c..ee72943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -169,9 +169,9 @@
adaptDurationWhenGoingToFullShade(child, viewState, wasAdded, animationStaggerCount);
mAnimationProperties.delay = 0;
if (wasAdded || mAnimationFilter.hasDelays
- && (viewState.yTranslation != child.getTranslationY()
- || viewState.zTranslation != child.getTranslationZ()
- || viewState.alpha != child.getAlpha()
+ && (viewState.getYTranslation() != child.getTranslationY()
+ || viewState.getZTranslation() != child.getTranslationZ()
+ || viewState.getAlpha() != child.getAlpha()
|| viewState.height != child.getActualHeight()
|| viewState.clipTopAmount != child.getClipTopAmount())) {
mAnimationProperties.delay = mCurrentAdditionalDelay
@@ -191,7 +191,7 @@
mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50
+ (long) (100 * longerDurationFactor);
}
- child.setTranslationY(viewState.yTranslation + startOffset);
+ child.setTranslationY(viewState.getYTranslation() + startOffset);
}
}
@@ -400,7 +400,7 @@
// travelled
ExpandableViewState viewState =
((ExpandableView) event.viewAfterChangingView).getViewState();
- translationDirection = ((viewState.yTranslation
+ translationDirection = ((viewState.getYTranslation()
- (ownPosition + actualHeight / 2.0f)) * 2 /
actualHeight);
translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
@@ -433,7 +433,7 @@
ExpandableViewState viewState = changingView.getViewState();
mTmpState.copyFrom(viewState);
if (event.headsUpFromBottom) {
- mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
+ mTmpState.setYTranslation(mHeadsUpAppearHeightBottom);
} else {
Runnable onAnimationEnd = null;
if (loggable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index 786de29..d07da38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -42,7 +43,7 @@
* A state of a view. This can be used to apply a set of view properties to a view with
* {@link com.android.systemui.statusbar.notification.stack.StackScrollState} or start
* animations with {@link com.android.systemui.statusbar.notification.stack.StackStateAnimator}.
-*/
+ */
public class ViewState implements Dumpable {
/**
@@ -51,6 +52,7 @@
*/
protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() {
AnimationFilter mAnimationFilter = new AnimationFilter();
+
@Override
public AnimationFilter getAnimationFilter() {
return mAnimationFilter;
@@ -68,6 +70,7 @@
private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
+ private static final String LOG_TAG = "StackViewState";
private static final AnimatableProperty SCALE_X_PROPERTY
= new AnimatableProperty() {
@@ -117,35 +120,127 @@
}
};
- public float alpha;
- public float xTranslation;
- public float yTranslation;
- public float zTranslation;
public boolean gone;
public boolean hidden;
- public float scaleX = 1.0f;
- public float scaleY = 1.0f;
+
+ private float mAlpha;
+ private float mXTranslation;
+ private float mYTranslation;
+ private float mZTranslation;
+ private float mScaleX = 1.0f;
+ private float mScaleY = 1.0f;
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ /**
+ * @param alpha View transparency.
+ */
+ public void setAlpha(float alpha) {
+ if (isValidFloat(alpha, "alpha")) {
+ this.mAlpha = alpha;
+ }
+ }
+
+ public float getXTranslation() {
+ return mXTranslation;
+ }
+
+ /**
+ * @param xTranslation x-axis translation value for the animation.
+ */
+ public void setXTranslation(float xTranslation) {
+ if (isValidFloat(xTranslation, "xTranslation")) {
+ this.mXTranslation = xTranslation;
+ }
+ }
+
+ public float getYTranslation() {
+ return mYTranslation;
+ }
+
+ /**
+ * @param yTranslation y-axis translation value for the animation.
+ */
+ public void setYTranslation(float yTranslation) {
+ if (isValidFloat(yTranslation, "yTranslation")) {
+ this.mYTranslation = yTranslation;
+ }
+ }
+
+ public float getZTranslation() {
+ return mZTranslation;
+ }
+
+
+ /**
+ * @param zTranslation z-axis translation value for the animation.
+ */
+ public void setZTranslation(float zTranslation) {
+ if (isValidFloat(zTranslation, "zTranslation")) {
+ this.mZTranslation = zTranslation;
+ }
+ }
+
+ public float getScaleX() {
+ return mScaleX;
+ }
+
+ /**
+ * @param scaleX x-axis scale property for the animation.
+ */
+ public void setScaleX(float scaleX) {
+ if (isValidFloat(scaleX, "scaleX")) {
+ this.mScaleX = scaleX;
+ }
+ }
+
+ public float getScaleY() {
+ return mScaleY;
+ }
+
+ /**
+ * @param scaleY y-axis scale property for the animation.
+ */
+ public void setScaleY(float scaleY) {
+ if (isValidFloat(scaleY, "scaleY")) {
+ this.mScaleY = scaleY;
+ }
+ }
+
+ /**
+ * Checks if {@code value} is a valid float value. If it is not, logs it (using {@code name})
+ * and returns false.
+ */
+ private boolean isValidFloat(float value, String name) {
+ if (Float.isNaN(value)) {
+ Log.wtf(LOG_TAG, "Cannot set property " + name + " to NaN");
+ return false;
+ }
+ return true;
+ }
public void copyFrom(ViewState viewState) {
- alpha = viewState.alpha;
- xTranslation = viewState.xTranslation;
- yTranslation = viewState.yTranslation;
- zTranslation = viewState.zTranslation;
+ mAlpha = viewState.mAlpha;
+ mXTranslation = viewState.mXTranslation;
+ mYTranslation = viewState.mYTranslation;
+ mZTranslation = viewState.mZTranslation;
gone = viewState.gone;
hidden = viewState.hidden;
- scaleX = viewState.scaleX;
- scaleY = viewState.scaleY;
+ mScaleX = viewState.mScaleX;
+ mScaleY = viewState.mScaleY;
}
public void initFrom(View view) {
- alpha = view.getAlpha();
- xTranslation = view.getTranslationX();
- yTranslation = view.getTranslationY();
- zTranslation = view.getTranslationZ();
+ mAlpha = view.getAlpha();
+ mXTranslation = view.getTranslationX();
+ mYTranslation = view.getTranslationY();
+ mZTranslation = view.getTranslationZ();
gone = view.getVisibility() == View.GONE;
hidden = view.getVisibility() == View.INVISIBLE;
- scaleX = view.getScaleX();
- scaleY = view.getScaleY();
+ mScaleX = view.getScaleX();
+ mScaleY = view.getScaleY();
}
/**
@@ -161,51 +256,51 @@
boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
if (animatingX) {
updateAnimationX(view);
- } else if (view.getTranslationX() != this.xTranslation){
- view.setTranslationX(this.xTranslation);
+ } else if (view.getTranslationX() != this.mXTranslation) {
+ view.setTranslationX(this.mXTranslation);
}
// apply yTranslation
boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y);
if (animatingY) {
updateAnimationY(view);
- } else if (view.getTranslationY() != this.yTranslation) {
- view.setTranslationY(this.yTranslation);
+ } else if (view.getTranslationY() != this.mYTranslation) {
+ view.setTranslationY(this.mYTranslation);
}
// apply zTranslation
boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z);
if (animatingZ) {
updateAnimationZ(view);
- } else if (view.getTranslationZ() != this.zTranslation) {
- view.setTranslationZ(this.zTranslation);
+ } else if (view.getTranslationZ() != this.mZTranslation) {
+ view.setTranslationZ(this.mZTranslation);
}
// apply scaleX
boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY);
if (animatingScaleX) {
- updateAnimation(view, SCALE_X_PROPERTY, scaleX);
- } else if (view.getScaleX() != scaleX) {
- view.setScaleX(scaleX);
+ updateAnimation(view, SCALE_X_PROPERTY, mScaleX);
+ } else if (view.getScaleX() != mScaleX) {
+ view.setScaleX(mScaleX);
}
// apply scaleY
boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY);
if (animatingScaleY) {
- updateAnimation(view, SCALE_Y_PROPERTY, scaleY);
- } else if (view.getScaleY() != scaleY) {
- view.setScaleY(scaleY);
+ updateAnimation(view, SCALE_Y_PROPERTY, mScaleY);
+ } else if (view.getScaleY() != mScaleY) {
+ view.setScaleY(mScaleY);
}
int oldVisibility = view.getVisibility();
- boolean becomesInvisible = this.alpha == 0.0f
+ boolean becomesInvisible = this.mAlpha == 0.0f
|| (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
if (animatingAlpha) {
updateAlphaAnimation(view);
- } else if (view.getAlpha() != this.alpha) {
+ } else if (view.getAlpha() != this.mAlpha) {
// apply layer type
- boolean becomesFullyVisible = this.alpha == 1.0f;
+ boolean becomesFullyVisible = this.mAlpha == 1.0f;
boolean becomesFaded = !becomesInvisible && !becomesFullyVisible;
if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED
&& view instanceof FadeOptimizedNotification) {
@@ -229,7 +324,7 @@
}
// apply alpha
- view.setAlpha(this.alpha);
+ view.setAlpha(this.mAlpha);
}
// apply visibility
@@ -274,54 +369,55 @@
/**
* Start an animation to this viewstate
- * @param child the view to animate
+ *
+ * @param child the view to animate
* @param animationProperties the properties of the animation
*/
public void animateTo(View child, AnimationProperties animationProperties) {
boolean wasVisible = child.getVisibility() == View.VISIBLE;
- final float alpha = this.alpha;
+ final float alpha = this.mAlpha;
if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
&& !this.gone && !this.hidden) {
child.setVisibility(View.VISIBLE);
}
float childAlpha = child.getAlpha();
- boolean alphaChanging = this.alpha != childAlpha;
+ boolean alphaChanging = this.mAlpha != childAlpha;
if (child instanceof ExpandableView) {
// We don't want views to change visibility when they are animating to GONE
alphaChanging &= !((ExpandableView) child).willBeGone();
}
// start translationX animation
- if (child.getTranslationX() != this.xTranslation) {
+ if (child.getTranslationX() != this.mXTranslation) {
startXTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X);
}
// start translationY animation
- if (child.getTranslationY() != this.yTranslation) {
+ if (child.getTranslationY() != this.mYTranslation) {
startYTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
}
// start translationZ animation
- if (child.getTranslationZ() != this.zTranslation) {
+ if (child.getTranslationZ() != this.mZTranslation) {
startZTranslationAnimation(child, animationProperties);
} else {
abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
}
// start scaleX animation
- if (child.getScaleX() != scaleX) {
- PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, scaleX, animationProperties);
+ if (child.getScaleX() != mScaleX) {
+ PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, mScaleX, animationProperties);
} else {
abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag());
}
// start scaleX animation
- if (child.getScaleY() != scaleY) {
- PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, scaleY, animationProperties);
+ if (child.getScaleY() != mScaleY) {
+ PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, mScaleY, animationProperties);
} else {
abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag());
}
@@ -329,7 +425,7 @@
// start alpha animation
if (alphaChanging) {
startAlphaAnimation(child, animationProperties);
- } else {
+ } else {
abortAnimation(child, TAG_ANIMATOR_ALPHA);
}
}
@@ -339,9 +435,9 @@
}
private void startAlphaAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
- Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
- final float newEndValue = this.alpha;
+ Float previousStartValue = getChildTag(child, TAG_START_ALPHA);
+ Float previousEndValue = getChildTag(child, TAG_END_ALPHA);
+ final float newEndValue = this.mAlpha;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -426,9 +522,9 @@
}
private void startZTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
- float newEndValue = this.zTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Z);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Z);
+ float newEndValue = this.mZTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -487,9 +583,9 @@
}
private void startXTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X);
- float newEndValue = this.xTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_X);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_X);
+ float newEndValue = this.mXTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -519,7 +615,7 @@
child.getTranslationX(), newEndValue);
Interpolator customInterpolator = properties.getCustomInterpolator(child,
View.TRANSLATION_X);
- Interpolator interpolator = customInterpolator != null ? customInterpolator
+ Interpolator interpolator = customInterpolator != null ? customInterpolator
: Interpolators.FAST_OUT_SLOW_IN;
animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
@@ -553,9 +649,9 @@
}
private void startYTranslationAnimation(final View child, AnimationProperties properties) {
- Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
- Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
- float newEndValue = this.yTranslation;
+ Float previousStartValue = getChildTag(child, TAG_START_TRANSLATION_Y);
+ Float previousEndValue = getChildTag(child, TAG_END_TRANSLATION_Y);
+ float newEndValue = this.mYTranslation;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
@@ -585,7 +681,7 @@
child.getTranslationY(), newEndValue);
Interpolator customInterpolator = properties.getCustomInterpolator(child,
View.TRANSLATION_Y);
- Interpolator interpolator = customInterpolator != null ? customInterpolator
+ Interpolator interpolator = customInterpolator != null ? customInterpolator
: Interpolators.FAST_OUT_SLOW_IN;
animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
@@ -644,7 +740,7 @@
/**
* Cancel the previous animator and get the duration of the new animation.
*
- * @param duration the new duration
+ * @param duration the new duration
* @param previousAnimator the animator which was running before
* @return the new duration
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index f37243a..fa7bfae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -416,6 +416,9 @@
void endAffordanceLaunch();
+ /** Should the keyguard be hidden immediately in response to a back press/gesture. */
+ boolean shouldKeyguardHideImmediately();
+
boolean onBackPressed();
boolean onSpacePressed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index a77a600..8487fac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3312,19 +3312,23 @@
mNotificationPanelViewController.onAffordanceLaunchEnded();
}
+ /**
+ * Returns whether the keyguard should hide immediately (as opposed to via an animation).
+ * Non-scrimmed bouncers have a special animation tied to the notification panel expansion.
+ * @return whether the keyguard should be immediately hidden.
+ */
@Override
- public boolean onBackPressed() {
+ public boolean shouldKeyguardHideImmediately() {
final boolean isScrimmedBouncer =
mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
final boolean isBouncerOverDream = isBouncerShowingOverDream();
+ return (isScrimmedBouncer || isBouncerOverDream);
+ }
- if (mStatusBarKeyguardViewManager.onBackPressed(
- isScrimmedBouncer || isBouncerOverDream /* hideImmediately */)) {
- if (isScrimmedBouncer || isBouncerOverDream) {
- mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
- } else {
- mNotificationPanelViewController.expandWithoutQs();
- }
+ @Override
+ public boolean onBackPressed() {
+ if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
+ mStatusBarKeyguardViewManager.onBackPressed(false /* unused */);
return true;
}
if (mNotificationPanelViewController.isQsCustomizing()) {
@@ -3339,7 +3343,7 @@
return true;
}
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
- && !isBouncerOverDream) {
+ && !isBouncerShowingOverDream()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
}
@@ -4454,10 +4458,11 @@
Trace.beginSection("CentralSurfaces#updateDozing");
mDozing = isDozing;
- // Collapse the notification panel if open
boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
&& mDozeParameters.shouldControlScreenOff();
- mNotificationPanelViewController.resetViews(dozingAnimated);
+ // resetting views is already done when going into doze, there's no need to
+ // reset them again when we're waking up
+ mNotificationPanelViewController.resetViews(dozingAnimated && isDozing);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 54d39fd..de7b152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -93,13 +93,13 @@
private boolean mControlScreenOffAnimation;
private boolean mIsQuickPickupEnabled;
- private boolean mKeyguardShowing;
+ private boolean mKeyguardVisible;
@VisibleForTesting
final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ mKeyguardVisible = visible;
updateControlScreenOff();
}
@@ -293,7 +293,7 @@
public void updateControlScreenOff() {
if (!getDisplayNeedsBlanking()) {
final boolean controlScreenOff =
- getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
+ getAlwaysOn() && (mKeyguardVisible || shouldControlUnlockedScreenOff());
setControlScreenOffAnimation(controlScreenOff);
}
}
@@ -348,7 +348,7 @@
}
private boolean willAnimateFromLockScreenToAod() {
- return getAlwaysOn() && mKeyguardShowing;
+ return getAlwaysOn() && mKeyguardVisible;
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 7de4668..0067316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.os.Handler;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -34,9 +33,6 @@
*/
@SysUISingleton
public class DozeScrimController implements StateListener {
- private static final String TAG = "DozeScrimController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
private final DozeLog mDozeLog;
private final DozeParameters mDozeParameters;
private final Handler mHandler = new Handler();
@@ -44,28 +40,26 @@
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private boolean mFullyPulsing;
private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
@Override
public void onDisplayBlanked() {
- if (DEBUG) {
- Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.reasonToString(mPulseReason));
- }
if (!mDozing) {
+ mDozeLog.tracePulseDropped("onDisplayBlanked - not dozing");
return;
}
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
+ if (mPulseCallback != null) {
+ // Signal that the pulse is ready to turn the screen on and draw.
+ mDozeLog.tracePulseStart(mPulseReason);
+ mPulseCallback.onPulseStarted();
+ }
}
@Override
public void onFinished() {
- if (DEBUG) {
- Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- }
+ mDozeLog.tracePulseEvent("scrimCallback-onFinished", mDozing, mPulseReason);
+
if (!mDozing) {
return;
}
@@ -78,7 +72,6 @@
mHandler.postDelayed(mPulseOutExtended,
mDozeParameters.getPulseVisibleDurationExtended());
}
- mFullyPulsing = true;
}
/**
@@ -118,19 +111,14 @@
}
if (!mDozing || mPulseCallback != null) {
- if (DEBUG) {
- Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
- + (mPulseCallback != null));
- }
// Pulse suppressed.
callback.onPulseFinished();
if (!mDozing) {
- mDozeLog.tracePulseDropped("device isn't dozing");
+ mDozeLog.tracePulseDropped("pulse - device isn't dozing");
} else {
- mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+ mDozeLog.tracePulseDropped("pulse - already has pulse callback mPulseCallback="
+ mPulseCallback);
}
-
return;
}
@@ -141,9 +129,7 @@
}
public void pulseOutNow() {
- if (mPulseCallback != null && mFullyPulsing) {
- mPulseOut.run();
- }
+ mPulseOut.run();
}
public boolean isPulsing() {
@@ -168,24 +154,16 @@
private void cancelPulsing() {
if (mPulseCallback != null) {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
- mFullyPulsing = false;
+ mDozeLog.tracePulseEvent("cancel", mDozing, mPulseReason);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
}
}
- private void pulseStarted() {
- mDozeLog.tracePulseStart(mPulseReason);
- if (mPulseCallback != null) {
- mPulseCallback.onPulseStarted();
- }
- }
-
private void pulseFinished() {
- mDozeLog.tracePulseFinish();
if (mPulseCallback != null) {
+ mDozeLog.tracePulseFinish();
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
@@ -202,10 +180,9 @@
private final Runnable mPulseOut = new Runnable() {
@Override
public void run() {
- mFullyPulsing = false;
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
- if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
+ mDozeLog.tracePulseEvent("out", mDozing, mPulseReason);
if (!mDozing) return;
pulseFinished();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 24ce5e9..5196e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -36,7 +36,6 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -48,6 +47,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
@@ -80,7 +80,6 @@
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private final KeyguardViewMediator mKeyguardViewMediator;
private final Lazy<AssistManager> mAssistManagerLazy;
private final DozeScrimController mDozeScrimController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -95,6 +94,7 @@
private View mAmbientIndicationContainer;
private CentralSurfaces mCentralSurfaces;
private boolean mAlwaysOnSuppressed;
+ private boolean mPulsePending;
@Inject
public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -104,7 +104,6 @@
HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
- KeyguardViewMediator keyguardViewMediator,
Lazy<AssistManager> assistManagerLazy,
DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
PulseExpansionHandler pulseExpansionHandler,
@@ -122,7 +121,6 @@
mBatteryController = batteryController;
mScrimController = scrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
- mKeyguardViewMediator = keyguardViewMediator;
mAssistManagerLazy = assistManagerLazy;
mDozeScrimController = dozeScrimController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -131,6 +129,7 @@
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
+ mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
}
// TODO: we should try to not pass status bar in here if we can avoid it.
@@ -246,7 +245,7 @@
mDozeScrimController.pulse(new PulseCallback() {
@Override
public void onPulseStarted() {
- callback.onPulseStarted();
+ callback.onPulseStarted(); // requestState(DozeMachine.State.DOZE_PULSING)
mCentralSurfaces.updateNotificationPanelTouchState();
setPulsing(true);
}
@@ -254,7 +253,7 @@
@Override
public void onPulseFinished() {
mPulsing = false;
- callback.onPulseFinished();
+ callback.onPulseFinished(); // requestState(DozeMachine.State.DOZE_PULSE_DONE)
mCentralSurfaces.updateNotificationPanelTouchState();
mScrimController.setWakeLockScreenSensorActive(false);
setPulsing(false);
@@ -338,9 +337,8 @@
@Override
public void stopPulsing() {
- if (mDozeScrimController.isPulsing()) {
- mDozeScrimController.pulseOutNow();
- }
+ setPulsePending(false); // prevent any pending pulses from continuing
+ mDozeScrimController.pulseOutNow();
}
@Override
@@ -451,6 +449,16 @@
}
}
+ @Override
+ public boolean isPulsePending() {
+ return mPulsePending;
+ }
+
+ @Override
+ public void setPulsePending(boolean isPulsePending) {
+ mPulsePending = isPulsePending;
+ }
+
/**
* Whether always-on-display is being suppressed. This does not affect wakeup gestures like
* pickup and tap.
@@ -458,4 +466,22 @@
public boolean isAlwaysOnSuppressed() {
return mAlwaysOnSuppressed;
}
+
+ final OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+ if (mStatusBarStateController.isDozing() && isHeadsUp) {
+ entry.setPulseSuppressed(false);
+ fireNotificationPulse(entry);
+ if (isPulsing()) {
+ mDozeScrimController.cancelPendingPulseTimeout();
+ }
+ }
+ if (!isHeadsUp && !mHeadsUpManagerPhone.hasNotifications()) {
+ // There are no longer any notifications to show. We should end the
+ // pulse now.
+ stopPulsing();
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index b58dbe2..e3e8572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -88,7 +88,7 @@
updateListeningState()
}
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
updateListeningState()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 0026b71..14cebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -40,6 +40,7 @@
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterViewController;
@@ -116,6 +117,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
private final Object mLock = new Object();
+ private final KeyguardLogger mLogger;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -185,8 +187,8 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (visible) {
updateUserSwitcher();
}
}
@@ -279,7 +281,8 @@
StatusBarUserInfoTracker statusBarUserInfoTracker,
SecureSettings secureSettings,
CommandQueue commandQueue,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ KeyguardLogger logger
) {
super(view);
mCarrierTextController = carrierTextController;
@@ -304,6 +307,7 @@
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
+ mLogger = logger;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -430,6 +434,7 @@
/** Animate the keyguard status bar in. */
public void animateKeyguardStatusBarIn() {
+ mLogger.d("animating status bar in");
if (mDisableStateTracker.isDisabled()) {
// If our view is disabled, don't allow us to animate in.
return;
@@ -445,6 +450,7 @@
/** Animate the keyguard status bar out. */
public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+ mLogger.d("animating status bar out");
ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setStartDelay(startDelay);
@@ -481,6 +487,9 @@
newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
* mKeyguardStatusBarAnimateAlpha
* (1.0f - mKeyguardHeadsUpShowingAmount);
+ if (newAlpha != mView.getAlpha() && (newAlpha == 0 || newAlpha == 1)) {
+ mLogger.logStatusBarCalculatedAlpha(newAlpha);
+ }
}
boolean hideForBypass =
@@ -503,6 +512,10 @@
if (mDisableStateTracker.isDisabled()) {
visibility = View.INVISIBLE;
}
+ if (visibility != mView.getVisibility()) {
+ mLogger.logStatusBarAlphaVisibility(visibility, alpha,
+ StatusBarState.toString(mStatusBarState));
+ }
mView.setAlpha(alpha);
mView.setVisibility(visibility);
}
@@ -596,6 +609,8 @@
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryListening: " + mBatteryListening);
pw.println(" mExplicitAlpha: " + mExplicitAlpha);
+ pw.println(" alpha: " + mView.getAlpha());
+ pw.println(" visibility: " + mView.getVisibility());
mView.dump(pw, args);
}
@@ -605,6 +620,10 @@
* @param alpha a value between 0 and 1. -1 if the value is to be reset/ignored.
*/
public void setAlpha(float alpha) {
+ if (mExplicitAlpha != alpha && (mExplicitAlpha == -1 || alpha == -1)) {
+ // logged if value changed to ignored or from ignored
+ mLogger.logStatusBarExplicitAlpha(alpha);
+ }
mExplicitAlpha = alpha;
updateViewState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 5a70d89..9767103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -211,11 +211,11 @@
canvas.drawLine(end, 0, end, height, paint);
paint.setColor(Color.GREEN);
- int lastIcon = (int) mLastVisibleIconState.xTranslation;
+ int lastIcon = (int) mLastVisibleIconState.getXTranslation();
canvas.drawLine(lastIcon, 0, lastIcon, height, paint);
if (mFirstVisibleIconState != null) {
- int firstIcon = (int) mFirstVisibleIconState.xTranslation;
+ int firstIcon = (int) mFirstVisibleIconState.getXTranslation();
canvas.drawLine(firstIcon, 0, firstIcon, height, paint);
}
@@ -413,7 +413,7 @@
View view = getChildAt(i);
ViewState iconState = mIconStates.get(view);
iconState.initFrom(view);
- iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f;
+ iconState.setAlpha(mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f);
iconState.hidden = false;
}
}
@@ -467,7 +467,7 @@
// We only modify the xTranslation if it's fully inside of the container
// since during the transition to the shelf, the translations are controlled
// from the outside
- iconState.xTranslation = translationX;
+ iconState.setXTranslation(translationX);
}
if (mFirstVisibleIconState == null) {
mFirstVisibleIconState = iconState;
@@ -501,7 +501,7 @@
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotDiameter + mDotPadding;
- iconState.xTranslation = translationX;
+ iconState.setXTranslation(translationX);
if (mNumDots < MAX_DOTS) {
if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
@@ -525,7 +525,8 @@
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
- iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
+ iconState.setXTranslation(
+ getWidth() - iconState.getXTranslation() - view.getWidth());
}
}
if (mIsolatedIcon != null) {
@@ -533,8 +534,8 @@
if (iconState != null) {
// Most of the time the icon isn't yet added when this is called but only happening
// later
- iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0]
- - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f;
+ iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]
+ - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f);
iconState.visibleState = StatusBarIconView.STATE_ICON;
}
}
@@ -609,8 +610,10 @@
return 0;
}
- int translation = (int) (isLayoutRtl() ? getWidth() - mLastVisibleIconState.xTranslation
- : mLastVisibleIconState.xTranslation + mIconSize);
+ int translation = (int) (isLayoutRtl()
+ ? getWidth() - mLastVisibleIconState.getXTranslation()
+ : mLastVisibleIconState.getXTranslation() + mIconSize);
+
// There's a chance that last translation goes beyond the edge maybe
return Math.min(getWidth(), translation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 4d1c361..9f93223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -1534,7 +1534,7 @@
private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index ae201e3..5512bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,8 +21,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -41,9 +39,6 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarStateController mStatusBarStateController;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
- private final NotificationsController mNotificationsController;
- private final DozeServiceHost mDozeServiceHost;
- private final DozeScrimController mDozeScrimController;
@Inject
StatusBarHeadsUpChangeListener(
@@ -53,10 +48,7 @@
KeyguardBypassController keyguardBypassController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController statusBarStateController,
- NotificationRemoteInputManager notificationRemoteInputManager,
- NotificationsController notificationsController,
- DozeServiceHost dozeServiceHost,
- DozeScrimController dozeScrimController) {
+ NotificationRemoteInputManager notificationRemoteInputManager) {
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
@@ -65,9 +57,6 @@
mHeadsUpManager = headsUpManager;
mStatusBarStateController = statusBarStateController;
mNotificationRemoteInputManager = notificationRemoteInputManager;
- mNotificationsController = notificationsController;
- mDozeServiceHost = dozeServiceHost;
- mDozeScrimController = dozeScrimController;
}
@Override
@@ -117,20 +106,4 @@
}
}
}
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- if (mStatusBarStateController.isDozing() && isHeadsUp) {
- entry.setPulseSuppressed(false);
- mDozeServiceHost.fireNotificationPulse(entry);
- if (mDozeServiceHost.isPulsing()) {
- mDozeScrimController.cancelPendingPulseTimeout();
- }
- }
- if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
- // There are no longer any notifications to show. We should end the
- //pulse now.
- mDozeScrimController.pulseOutNow();
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d6d021f..ece7ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import android.annotation.Nullable;
@@ -38,7 +39,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.util.Assert;
@@ -84,6 +89,12 @@
void setMobileIcons(String slot, List<MobileIconState> states);
/**
+ * This method completely replaces {@link #setMobileIcons} with the information from the new
+ * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+ * to worry about funneling MobileIconState objects through anymore.
+ */
+ void setNewMobileIconSubIds(List<Integer> subIds);
+ /**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -141,12 +152,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -207,6 +220,7 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
@@ -214,10 +228,12 @@
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
+ MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
+ mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
}
@@ -227,6 +243,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -244,11 +261,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
}
@@ -284,14 +304,18 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
+ mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -301,6 +325,7 @@
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider);
}
}
@@ -315,6 +340,8 @@
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileIconsViewModel mMobileIconsViewModel;
+
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -333,7 +360,9 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mGroup = group;
mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
@@ -342,6 +371,14 @@
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ // This starts the flow for the new pipeline, and will notify us of changes
+ mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+ MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+ } else {
+ mMobileIconsViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -394,6 +431,9 @@
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
+
+ case TYPE_MOBILE_NEW:
+ return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
@@ -410,7 +450,7 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarWifiView view;
+ final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -429,17 +469,47 @@
}
@VisibleForTesting
- protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+ protected StatusIconDisplayable addMobileIcon(
+ int index,
+ String slot,
+ MobileIconState state
+ ) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "pipeline is enabled is not supported");
+ }
+
// Use the `subId` field as a key to query for the correct context
- StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
- view.applyMobileState(state);
- mGroup.addView(view, index, onCreateLayoutParams());
+ StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+ mobileView.applyMobileState(state);
+ mGroup.addView(mobileView, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.addMobileView(state, mobileContext);
}
+ return mobileView;
+ }
+
+ protected StatusIconDisplayable addNewMobileIcon(
+ int index,
+ String slot,
+ int subId
+ ) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+ mGroup.addView(view, index, onCreateLayoutParams());
+
+ if (mIsInDemoMode) {
+ // TODO (b/249790009): demo mode should be handled at the data layer in the
+ // new pipeline
+ }
+
return view;
}
@@ -464,6 +534,15 @@
return view;
}
+ private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+ String slot, int subId) {
+ return ModernStatusBarMobileView
+ .constructAndBind(
+ mContext,
+ slot,
+ mMobileIconsViewModel.viewModelForSub(subId));
+ }
+
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
@@ -519,6 +598,10 @@
return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
+ return;
+ case TYPE_MOBILE_NEW:
+ // Nothing, the icon updates itself now
+ return;
default:
break;
}
@@ -542,9 +625,13 @@
}
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
- StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
- if (view != null) {
- view.applyMobileState(state);
+ View view = mGroup.getChildAt(viewIndex);
+ if (view instanceof StatusBarMobileView) {
+ ((StatusBarMobileView) view).applyMobileState(state);
+ } else {
+ // ModernStatusBarMobileView automatically updates via the ViewModel
+ throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+ + "the new pipeline");
}
if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 7c31366..e106b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@
private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconHideList = new ArraySet<>();
-
- private Context mContext;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final Context mContext;
/** */
@Inject
@@ -78,9 +79,12 @@
ConfigurationController configurationController,
TunerService tunerService,
DumpManager dumpManager,
- StatusBarIconList statusBarIconList) {
+ StatusBarIconList statusBarIconList,
+ StatusBarPipelineFlags statusBarPipelineFlags
+ ) {
mStatusBarIconList = statusBarIconList;
mContext = context;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
configurationController.addCallback(this);
commandQueue.addCallback(this);
@@ -220,6 +224,11 @@
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ + "pipeline frontend is enabled");
+ return;
+ }
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
-
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@
}
}
+ @Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring new pipeline callback, "
+ + "since the frontend is disabled");
+ return;
+ }
+ Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+ Collections.reverse(subIds);
+
+ for (Integer subId : subIds) {
+ StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+ if (holder == null) {
+ holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+ setIcon("mobile", holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+ }
+
/**
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
* @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@
}
}
-
-
private void handleSet(String slotName, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd..68a203e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@
public static final int TYPE_ICON = 0;
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place
+ * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+ * to inform the old view system about changes to the data set (the list of mobile icons). The
+ * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+ * the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_NEW = 3;
+
+ @IntDef({
+ TYPE_ICON,
+ TYPE_WIFI,
+ TYPE_MOBILE,
+ TYPE_MOBILE_NEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IconType {}
private StatusBarIcon mIcon;
private WifiIconState mWifiState;
private MobileIconState mMobileState;
- private int mType = TYPE_ICON;
+ private @IconType int mType = TYPE_ICON;
private int mTag = 0;
private StatusBarIconHolder() {
-
}
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@
}
/**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_MOBILE_NEW;
+ holder.mTag = subId;
+
+ return holder;
+ }
+
+ /**
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@
return holder;
}
- public int getType() {
+ public @IconType int getType() {
return mType;
}
@@ -134,8 +171,12 @@
return mWifiState.visible;
case TYPE_MOBILE:
return mMobileState.visible;
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ return true;
- default: return true;
+ default:
+ return true;
}
}
@@ -156,6 +197,10 @@
case TYPE_MOBILE:
mMobileState.visible = visible;
break;
+
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 9d5392a..6e1cf13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -31,12 +31,15 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -119,6 +122,7 @@
private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
private static String TAG = "StatusBarKeyguardViewManager";
+ private static final boolean DEBUG = false;
protected final Context mContext;
private final ConfigurationController mConfigurationController;
@@ -184,8 +188,25 @@
if (mAlternateAuthInterceptor != null) {
mAlternateAuthInterceptor.onBouncerVisibilityChanged();
}
+
+ /* Register predictive back callback when keyguard becomes visible, and unregister
+ when it's hidden. */
+ if (isVisible) {
+ registerBackCallback();
+ } else {
+ unregisterBackCallback();
+ }
}
};
+
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG) {
+ Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+ }
+ onBackPressed(false /* unused */);
+ };
+ private boolean mIsBackCallbackRegistered = false;
+
private final DockManager.DockEventListener mDockEventListener =
new DockManager.DockEventListener() {
@Override
@@ -378,6 +399,46 @@
}
}
+ /** Register a callback, to be invoked by the Predictive Back system. */
+ private void registerBackCallback() {
+ if (!mIsBackCallbackRegistered) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_OVERLAY, mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = true;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "view root was null, could not register back callback");
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "prevented registering back callback twice");
+ }
+ }
+ }
+
+ /** Unregister the callback formerly registered with the Predictive Back system. */
+ private void unregisterBackCallback() {
+ if (mIsBackCallbackRegistered) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
+ mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = false;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "view root was null, could not unregister back callback");
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "prevented unregistering back callback twice");
+ }
+ }
+ }
+
@Override
public void onDensityOrFontScaleChanged() {
hideBouncer(true /* destroyView */);
@@ -405,8 +466,9 @@
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
@@ -414,8 +476,9 @@
} else if (bouncerNeedsScrimming()) {
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else if (mShowing && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
@@ -423,8 +486,9 @@
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
+ } else {
+ mBouncerInteractor.setExpansion(fraction);
}
- mBouncerInteractor.setExpansion(fraction);
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
@@ -432,16 +496,18 @@
&& !bouncerIsAnimatingAway()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */false);
}
- mBouncerInteractor.show(/* isScrimmed= */false);
}
} else if (!mShowing && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ } else {
+ mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -487,8 +553,9 @@
mCentralSurfaces.hideKeyguard();
if (mBouncer != null) {
mBouncer.show(true /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(true);
}
- mBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
@@ -529,8 +596,9 @@
void hideBouncer(boolean destroyView) {
if (mBouncer != null) {
mBouncer.hide(destroyView);
+ } else {
+ mBouncerInteractor.hide();
}
- mBouncerInteractor.hide();
if (mShowing) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
@@ -551,8 +619,9 @@
if (mShowing && !isBouncerShowing()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+ } else {
+ mBouncerInteractor.show(scrimmed);
}
- mBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -588,9 +657,10 @@
if (mBouncer != null) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+ mKeyguardGoneCancelAction);
}
- mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
- mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
}
@@ -603,17 +673,21 @@
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- mBouncerInteractor.show(/* isScrimmed= */true);
- if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
+ if (mBouncer != null) {
+ mBouncer.show(false /* resetSecuritySelection */);
+ } else {
+ mBouncerInteractor.show(/* isScrimmed= */true);
+ }
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
- mBouncerInteractor.setDismissAction(
- mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mBouncerInteractor.show(/* isScrimmed= */true);
if (mBouncer != null) {
mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
+ } else {
+ mBouncerInteractor.setDismissAction(
+ mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+ mBouncerInteractor.show(/* isScrimmed= */true);
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
@@ -717,8 +791,9 @@
public void onFinishedGoingToSleep() {
if (mBouncer != null) {
mBouncer.onScreenTurnedOff();
+ } else {
+ mBouncerInteractor.onScreenTurnedOff();
}
- mBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -830,8 +905,9 @@
if (bouncerIsShowing()) {
if (mBouncer != null) {
mBouncer.startPreHideAnimation(finishRunnable);
+ } else {
+ mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mBouncerInteractor.startDisappearAnimation(finishRunnable);
mCentralSurfaces.onBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -1010,25 +1086,47 @@
}
/**
- * Notifies this manager that the back button has been pressed.
+ * Returns whether a back invocation can be handled, which depends on whether the keyguard
+ * is currently showing (which itself is derived from multiple states).
*
- * @param hideImmediately Hide bouncer when {@code true}, keep it around otherwise.
- * Non-scrimmed bouncers have a special animation tied to the expansion
- * of the notification panel.
- * @return whether the back press has been handled
+ * @return whether a back press can be handled right now.
*/
- public boolean onBackPressed(boolean hideImmediately) {
- if (bouncerIsShowing()) {
- mCentralSurfaces.endAffordanceLaunch();
- // The second condition is for SIM card locked bouncer
- if (bouncerIsScrimmed()
- && !needsFullscreenBouncer()) {
- hideBouncer(false);
- updateStates();
+ public boolean canHandleBackPressed() {
+ return mBouncer.isShowing();
+ }
+
+ /**
+ * Notifies this manager that the back button has been pressed.
+ */
+ // TODO(b/244635782): This "accept boolean and ignore it, and always return false" was done
+ // to make it possible to check this in *and* allow merging to master,
+ // where ArcStatusBarKeyguardViewManager inherits this class, and its
+ // build will break if we change this interface.
+ // So, overall, while this function refactors the behavior of onBackPressed,
+ // (it now handles the back press, and no longer returns *whether* it did so)
+ // its interface is not changing right now (but will, in a follow-up CL).
+ public boolean onBackPressed(boolean ignored) {
+ if (!canHandleBackPressed()) {
+ return false;
+ }
+
+ mCentralSurfaces.endAffordanceLaunch();
+ // The second condition is for SIM card locked bouncer
+ if (bouncerIsScrimmed() && needsFullscreenBouncer()) {
+ hideBouncer(false);
+ updateStates();
+ } else {
+ /* Non-scrimmed bouncers have a special animation tied to the expansion
+ * of the notification panel. We decide whether to kick this animation off
+ * by computing the hideImmediately boolean.
+ */
+ boolean hideImmediately = mCentralSurfaces.shouldKeyguardHideImmediately();
+ reset(hideImmediately);
+ if (hideImmediately) {
+ mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
} else {
- reset(hideImmediately);
+ mNotificationPanelViewController.expandWithoutQs();
}
- return true;
}
return false;
}
@@ -1102,13 +1200,15 @@
if (bouncerDismissible || !showing || remoteInputActive) {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(true);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(true);
}
- mBouncerInteractor.setBackButtonEnabled(true);
} else {
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(false);
+ } else {
+ mBouncerInteractor.setBackButtonEnabled(false);
}
- mBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1126,8 +1226,8 @@
if (occluded != mLastOccluded || mFirstUpdate) {
mKeyguardStateController.notifyKeyguardState(showing, occluded);
}
- if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
- mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
+ if (occluded != mLastOccluded || mShowing != showing || mFirstUpdate) {
+ mKeyguardUpdateManager.setKeyguardShowing(showing, occluded);
}
if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
|| bouncerShowing != mLastBouncerShowing) {
@@ -1274,8 +1374,9 @@
public void notifyKeyguardAuthenticated(boolean strongAuth) {
if (mBouncer != null) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ } else {
+ mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
resetAlternateAuth(false);
@@ -1292,14 +1393,23 @@
} else {
if (mBouncer != null) {
mBouncer.showMessage(message, colorState);
+ } else {
+ mBouncerInteractor.showMessage(message, colorState);
}
- mBouncerInteractor.showMessage(message, colorState);
}
}
@Override
public ViewRootImpl getViewRootImpl() {
- return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl();
+ ViewGroup viewGroup = mNotificationShadeWindowController.getNotificationShadeView();
+ if (viewGroup != null) {
+ return viewGroup.getViewRootImpl();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ViewGroup was null, cannot get ViewRootImpl");
+ }
+ return null;
+ }
}
public void launchPendingWakeupAction() {
@@ -1340,8 +1450,9 @@
public void updateResources() {
if (mBouncer != null) {
mBouncer.updateResources();
+ } else {
+ mBouncerInteractor.updateResources();
}
- mBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
@@ -1426,9 +1537,9 @@
public void updateKeyguardPosition(float x) {
if (mBouncer != null) {
mBouncer.updateKeyguardPosition(x);
+ } else {
+ mBouncerInteractor.setKeyguardPosition(x);
}
-
- mBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1470,9 +1581,9 @@
public boolean isBouncerInTransit() {
if (mBouncer != null) {
return mBouncer.inTransit();
+ } else {
+ return mBouncerInteractor.isInTransit();
}
-
- return mBouncerInteractor.isInTransit();
}
/**
@@ -1481,9 +1592,9 @@
public boolean bouncerIsShowing() {
if (mBouncer != null) {
return mBouncer.isShowing();
+ } else {
+ return mBouncerInteractor.isFullyShowing();
}
-
- return mBouncerInteractor.isFullyShowing();
}
/**
@@ -1492,9 +1603,9 @@
public boolean bouncerIsScrimmed() {
if (mBouncer != null) {
return mBouncer.isScrimmed();
+ } else {
+ return mBouncerInteractor.isScrimmed();
}
-
- return mBouncerInteractor.isScrimmed();
}
/**
@@ -1503,9 +1614,10 @@
public boolean bouncerIsAnimatingAway() {
if (mBouncer != null) {
return mBouncer.isAnimatingAway();
+ } else {
+ return mBouncerInteractor.isAnimatingAway();
}
- return mBouncerInteractor.isAnimatingAway();
}
/**
@@ -1514,9 +1626,9 @@
public boolean bouncerWillDismissWithAction() {
if (mBouncer != null) {
return mBouncer.willDismissWithAction();
+ } else {
+ return mBouncerInteractor.willDismissWithAction();
}
-
- return mBouncerInteractor.willDismissWithAction();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index d464acb..26c1767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -337,7 +337,7 @@
// without cutting off the child view.
translationX -= getViewTotalWidth(child);
childState.visibleState = STATE_ICON;
- childState.xTranslation = translationX;
+ childState.setXTranslation(translationX);
mLayoutStates.add(0, childState);
// Shift translationX over by mIconSpacing for the next view.
@@ -354,13 +354,13 @@
for (int i = totalVisible - 1; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
// Allow room for underflow if we found we need it in onMeasure
- if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
- (mShouldRestrictIcons && visible >= maxVisible)) {
+ if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+ || (mShouldRestrictIcons && (visible >= maxVisible))) {
firstUnderflowIndex = i;
break;
}
mUnderflowStart = (int) Math.max(
- contentStart, state.xTranslation - mUnderflowWidth - mIconSpacing);
+ contentStart, state.getXTranslation() - mUnderflowWidth - mIconSpacing);
visible++;
}
@@ -371,7 +371,7 @@
for (int i = firstUnderflowIndex; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
if (totalDots < MAX_DOTS) {
- state.xTranslation = dotOffset;
+ state.setXTranslation(dotOffset);
state.visibleState = STATE_DOT;
dotOffset -= dotWidth;
totalDots++;
@@ -386,7 +386,7 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
StatusIconState state = getViewStateFromChild(child);
- state.xTranslation = width - state.xTranslation - child.getWidth();
+ state.setXTranslation(width - state.getXTranslation() - child.getWidth());
}
}
}
@@ -410,7 +410,7 @@
}
vs.initFrom(child);
- vs.alpha = 1.0f;
+ vs.setAlpha(1.0f);
vs.hidden = false;
}
}
@@ -442,7 +442,7 @@
parentWidth = ((View) view.getParent()).getWidth();
}
- float currentDistanceToEnd = parentWidth - xTranslation;
+ float currentDistanceToEnd = parentWidth - getXTranslation();
if (!(view instanceof StatusIconDisplayable)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 9a7c3fa..06d5542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+ @Binds
+ abstract fun mobileSubscriptionRepository(
+ impl: MobileSubscriptionRepositoryImpl
+ ): MobileSubscriptionRepository
+
+ @Binds
+ abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 0000000..46ccf32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.model
+
+import android.annotation.IntRange
+import android.telephony.Annotation.DataActivityType
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Data class containing all of the relevant information for a particular line of service, known as
+ * a Subscription in the telephony world. These models are the result of a single telephony listener
+ * which has many callbacks which each modify some particular field on this object.
+ *
+ * The design goal here is to de-normalize fields from the system into our model fields below. So
+ * any new field that needs to be tracked should be copied into this data class rather than
+ * threading complex system objects through the pipeline.
+ */
+data class MobileSubscriptionModel(
+ /** From [ServiceStateListener.onServiceStateChanged] */
+ val isEmergencyOnly: Boolean = false,
+
+ /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+ val isGsm: Boolean = false,
+ @IntRange(from = 0, to = 4)
+ val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+ @IntRange(from = 0, to = 4)
+ val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+
+ /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ val dataConnectionState: Int? = null,
+
+ /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+ @DataActivityType val dataActivityDirection: Int? = null,
+
+ /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+ val carrierNetworkChangeActive: Boolean? = null,
+
+ /** From [DisplayInfoListener.onDisplayInfoChanged] */
+ val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 0000000..36de2a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Get or create an observable for the given subscription ID */
+ fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+ private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ /**
+ * Each mobile subscription needs its own flow, which comes from registering listeners on the
+ * system. Use this method to create those flows and cache them for reuse
+ */
+ override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+ return subIdFlowCache[subId]
+ ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+ }
+
+ @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+ private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ val phony = telephonyManager.createForSubscriptionId(subId)
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ ServiceStateListener,
+ SignalStrengthsListener,
+ DataConnectionStateListener,
+ DataActivityListener,
+ CarrierNetworkListener,
+ DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ state = state.copy(displayInfo = telephonyDisplayInfo)
+ trySend(state)
+ }
+ }
+ phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose {
+ phony.unregisterTelephonyCallback(callback)
+ // Release the cached flow
+ subIdFlowCache.remove(subId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..77de849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+ /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+ val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+ private val deviceProvisionedController: DeviceProvisionedController,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+) : UserSetupRepository {
+ /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+ override val isUserSetupFlow: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onUserSetupChanged() {
+ trySend(Unit)
+ }
+ }
+
+ deviceProvisionedController.addCallback(callback)
+
+ awaitClose { deviceProvisionedController.removeCallback(callback) }
+ }
+ .onStart { emit(Unit) }
+ .mapLatest { fetchUserSetupState() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private suspend fun fetchUserSetupState(): Boolean =
+ withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
new file mode 100644
index 0000000..40fe0f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+ /** Identifier for RAT type indicator */
+ val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** True if this line of service is emergency-only */
+ val isEmergencyOnly: Flow<Boolean>
+ /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+ val level: Flow<Int>
+ /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+ val numberOfLevels: Flow<Int>
+ /** True when we want to draw an icon that makes room for the exclamation mark */
+ val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+ mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+ override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+ override val level: Flow<Int> =
+ mobileStatusInfo.map { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+
+ /**
+ * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ * once it's wired up inside of [CarrierConfigTracker]
+ */
+ override val numberOfLevels: Flow<Int> = flowOf(4)
+
+ /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+ // TODO: find a better name for this?
+ override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 0000000..8e67e19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+ private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ userSetupRepo: UserSetupRepository,
+) {
+ private val activeMobileDataSubscriptionId =
+ mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+ private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ mobileSubscriptionRepo.subscriptionsFlow
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+ * and by checking which subscription is opportunistic, or which one is active.
+ */
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+ ->
+ // Based on the old logic,
+ if (unfilteredSubs.size != 2) {
+ return@combine unfilteredSubs
+ }
+
+ val info1 = unfilteredSubs[0]
+ val info2 = unfilteredSubs[1]
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return@combine unfilteredSubs
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ return@combine if (info1.subscriptionId == activeId) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+ /**
+ * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+ */
+ private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+ mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 0000000..380017c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+ interactor: MobileIconsInteractor,
+ private val iconController: StatusBarIconController,
+ private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ @Application scope: CoroutineScope,
+) {
+ private val mobileSubIds: Flow<List<Int>> =
+ interactor.filteredSubscriptions.mapLatest { infos ->
+ infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+ }
+
+ /**
+ * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+ * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+ * house the mobile infos.
+ *
+ * NOTE: this should go away as the view presenter learns more about this data pipeline
+ */
+ private val mobileSubIdsState: StateFlow<List<Int>> =
+ mobileSubIds
+ .onEach {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ /**
+ * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+ * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+ * the old view system.
+ */
+ fun createMobileIconsViewModel(): MobileIconsViewModel =
+ iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 0000000..1405b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+ /** Binds the view to the view-model, continuing to update the former based on the latter */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: MobileIconViewModel,
+ ) {
+ val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+ val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+ view.isVisible = true
+ iconView.isVisible = true
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Set the icon for the triangle
+ launch {
+ viewModel.iconId.distinctUntilChanged().collect { iconId ->
+ mobileDrawable.level = iconId
+ }
+ }
+
+ // Set the tint
+ launch {
+ viewModel.tint.collect { tint ->
+ iconView.imageTintList = ColorStateList.valueOf(tint)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 0000000..e7d5ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+ /**
+ * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+ * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+ * collecting, [MobileUiAdapter] will send updates to the icon manager.
+ */
+ @JvmStatic
+ fun bind(view: View, viewModel: MobileIconsViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.subscriptionIdsFlow.collect {
+ // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+ // sets up a side-effect in this flow to trigger the methods on
+ // [StatusBarIconController] which allows for this pipeline to be a data
+ // source for the mobile icons.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 0000000..ec4fa9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+ context: Context,
+ attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // TODO
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ // TODO
+ }
+
+ override fun setDecorColor(color: Int) {
+ // TODO
+ }
+
+ override fun setVisibleState(state: Int, animate: Boolean) {
+ // TODO
+ }
+
+ override fun getVisibleState(): Int {
+ return STATE_ICON
+ }
+
+ override fun isIconVisible(): Boolean {
+ return true
+ }
+
+ companion object {
+
+ /**
+ * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+ * returns it.
+ */
+ @JvmStatic
+ fun constructAndBind(
+ context: Context,
+ slot: String,
+ viewModel: MobileIconViewModel,
+ ): ModernStatusBarMobileView {
+ return (LayoutInflater.from(context)
+ .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+ as ModernStatusBarMobileView)
+ .also {
+ it.slot = slot
+ MobileIconBinder.bind(it, viewModel)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 0000000..cfabeba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+ val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ logger: ConnectivityPipelineLogger,
+) {
+ /** An int consumable by [SignalDrawable] for display */
+ var iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ level,
+ numberOfLevels,
+ cutOut ->
+ SignalDrawable.getState(level, numberOfLevels, cutOut)
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "iconId($subscriptionId)")
+
+ var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 0000000..24c1db9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+ val subscriptionIdsFlow: Flow<List<Int>>,
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+) {
+ /** TODO: do we need to cache these? */
+ fun viewModelForSub(subId: Int): MobileIconViewModel =
+ MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ logger
+ )
+
+ class Factory
+ @Inject
+ constructor(
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+ ) {
+ fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+ return MobileIconsViewModel(
+ subscriptionIdsFlow,
+ interactor,
+ logger,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 6c616ac..0cd9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,7 +22,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -37,7 +37,7 @@
class ModernStatusBarWifiView(
context: Context,
attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
private lateinit var binding: WifiViewBinder.Binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
new file mode 100644
index 0000000..ba5fa1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.PowerManager;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * com.android.systemui.statusbar.policy related providers that others may want to override.
+ */
+@Module
+public class AospPolicyModule {
+ @Provides
+ @SysUISingleton
+ static BatteryController provideBatteryController(
+ Context context,
+ EnhancedEstimates enhancedEstimates,
+ PowerManager powerManager,
+ BroadcastDispatcher broadcastDispatcher,
+ DemoModeController demoModeController,
+ DumpManager dumpManager,
+ @Main Handler mainHandler,
+ @Background Handler bgHandler) {
+ BatteryController bC = new BatteryControllerImpl(
+ context,
+ enhancedEstimates,
+ powerManager,
+ broadcastDispatcher,
+ demoModeController,
+ dumpManager,
+ mainHandler,
+ bgHandler);
+ bC.init();
+ return bC;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f4d08e0..437d4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -435,7 +435,7 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean visible) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 0995a00..494a4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -91,11 +91,11 @@
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", visible));
// Any time the keyguard is hidden, try to close the user switcher menu to
// restore keyguard to the default state
- if (!showing) {
+ if (!visible) {
closeSwitcherIfOpenAndNotSimple(false);
}
}
@@ -505,7 +505,7 @@
v.bind(name, drawable, item.info.id);
}
v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(getController().isDisabledByAdmin(item));
+ v.setDisabledByAdmin(item.isDisabledByAdmin());
v.setEnabled(item.isSwitchToEnabled);
UserSwitcherController.setSelectableAlpha(v);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a33603..da6d455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -221,8 +221,10 @@
mEditText.setTextColor(textColor);
mEditText.setHintTextColor(hintColor);
- mEditText.getTextCursorDrawable().setColorFilter(
- accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+ if (mEditText.getTextCursorDrawable() != null) {
+ mEditText.getTextCursorDrawable().setColorFilter(
+ accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+ }
mContentBackground.setColor(editBgColor);
mContentBackground.setStroke(stroke, accentColor);
mDelete.setImageTintList(ColorStateList.valueOf(deleteFgColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 843c232..146b222 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -19,7 +19,6 @@
import android.annotation.UserIdInt
import android.content.Intent
import android.view.View
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
import com.android.systemui.Dumpable
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
@@ -130,12 +129,6 @@
/** Whether keyguard is showing. */
val isKeyguardShowing: Boolean
- /** Returns the [EnforcedAdmin] for the given record, or `null` if there isn't one. */
- fun getEnforcedAdmin(record: UserRecord): EnforcedAdmin?
-
- /** Returns `true` if the given record is disabled by the admin; `false` otherwise. */
- fun isDisabledByAdmin(record: UserRecord): Boolean
-
/** Starts an activity with the given [Intent]. */
fun startActivity(intent: Intent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
index 12834f6..af39eee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -17,13 +17,20 @@
package com.android.systemui.statusbar.policy
+import android.content.Context
import android.content.Intent
import android.view.View
-import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import dagger.Lazy
import java.io.PrintWriter
import java.lang.ref.WeakReference
@@ -31,58 +38,76 @@
import kotlinx.coroutines.flow.Flow
/** Implementation of [UserSwitcherController]. */
+@SysUISingleton
class UserSwitcherControllerImpl
@Inject
constructor(
- private val flags: FeatureFlags,
+ @Application private val applicationContext: Context,
+ flags: FeatureFlags,
@Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
+ private val userInteractorLazy: Lazy<UserInteractor>,
+ private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+ private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+ private val activityStarter: ActivityStarter,
) : UserSwitcherController {
- private val isNewImpl: Boolean
- get() = flags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)
+ private val useInteractor: Boolean =
+ flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
+ !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
private val _oldImpl: UserSwitcherControllerOldImpl
get() = oldImpl.get()
+ private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+ private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+ private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
- private fun notYetImplemented(): Nothing {
- error("Not yet implemented!")
+ private val callbackCompatMap =
+ mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
+
+ private fun notSupported(): Nothing {
+ error("Not supported in the new implementation!")
}
override val users: ArrayList<UserRecord>
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.userRecords.value
} else {
_oldImpl.users
}
override val isSimpleUserSwitcher: Boolean
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.isSimpleUserSwitcher
} else {
_oldImpl.isSimpleUserSwitcher
}
override fun init(view: View) {
- if (isNewImpl) {
- notYetImplemented()
- } else {
+ if (!useInteractor) {
_oldImpl.init(view)
}
}
override val currentUserRecord: UserRecord?
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.selectedUserRecord.value
} else {
_oldImpl.currentUserRecord
}
override val currentUserName: String?
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ currentUserRecord?.let {
+ LegacyUserUiHelper.getUserRecordName(
+ context = applicationContext,
+ record = it,
+ isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = userInteractor.isGuestUserResetting,
+ )
+ }
} else {
_oldImpl.currentUserName
}
@@ -91,8 +116,8 @@
userId: Int,
dialogShower: UserSwitchDialogController.DialogShower?
) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.selectUser(userId, dialogShower)
} else {
_oldImpl.onUserSelected(userId, dialogShower)
}
@@ -100,24 +125,24 @@
override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ notSupported()
} else {
_oldImpl.isAddUsersFromLockScreenEnabled
}
override val isGuestUserAutoCreated: Boolean
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.isGuestUserAutoCreated
} else {
_oldImpl.isGuestUserAutoCreated
}
override val isGuestUserResetting: Boolean
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.isGuestUserResetting
} else {
_oldImpl.isGuestUserResetting
}
@@ -125,40 +150,48 @@
override fun createAndSwitchToGuestUser(
dialogShower: UserSwitchDialogController.DialogShower?,
) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ notSupported()
} else {
_oldImpl.createAndSwitchToGuestUser(dialogShower)
}
}
override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ notSupported()
} else {
_oldImpl.showAddUserDialog(dialogShower)
}
}
override fun startSupervisedUserActivity() {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ notSupported()
} else {
_oldImpl.startSupervisedUserActivity()
}
}
override fun onDensityOrFontScaleChanged() {
- if (isNewImpl) {
- notYetImplemented()
- } else {
+ if (!useInteractor) {
_oldImpl.onDensityOrFontScaleChanged()
}
}
override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.addCallback(
+ object : UserInteractor.UserCallback {
+ override fun isEvictable(): Boolean {
+ return adapter.get() == null
+ }
+
+ override fun onUserStateChanged() {
+ adapter.get()?.notifyDataSetChanged()
+ }
+ }
+ )
} else {
_oldImpl.addAdapter(adapter)
}
@@ -168,16 +201,19 @@
record: UserRecord,
dialogShower: UserSwitchDialogController.DialogShower?,
) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.onRecordSelected(record, dialogShower)
} else {
_oldImpl.onUserListItemClicked(record, dialogShower)
}
}
override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.removeGuestUser(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ )
} else {
_oldImpl.removeGuestUser(guestUserId, targetUserId)
}
@@ -188,16 +224,16 @@
targetUserId: Int,
forceRemoveGuestOnExit: Boolean
) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
} else {
_oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
}
}
override fun schedulePostBootGuestCreation() {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ guestUserInteractor.onDeviceBootCompleted()
} else {
_oldImpl.schedulePostBootGuestCreation()
}
@@ -205,63 +241,57 @@
override val isKeyguardShowing: Boolean
get() =
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ keyguardInteractor.isKeyguardShowing()
} else {
_oldImpl.isKeyguardShowing
}
- override fun getEnforcedAdmin(record: UserRecord): RestrictedLockUtils.EnforcedAdmin? {
- return if (isNewImpl) {
- notYetImplemented()
- } else {
- _oldImpl.getEnforcedAdmin(record)
- }
- }
-
- override fun isDisabledByAdmin(record: UserRecord): Boolean {
- return if (isNewImpl) {
- notYetImplemented()
- } else {
- _oldImpl.isDisabledByAdmin(record)
- }
- }
-
override fun startActivity(intent: Intent) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ activityStarter.startActivity(intent, /* dismissShade= */ false)
} else {
_oldImpl.startActivity(intent)
}
}
override fun refreshUsers(forcePictureLoadForId: Int) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.refreshUsers()
} else {
_oldImpl.refreshUsers(forcePictureLoadForId)
}
}
override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ val interactorCallback =
+ object : UserInteractor.UserCallback {
+ override fun onUserStateChanged() {
+ callback.onUserSwitched()
+ }
+ }
+ callbackCompatMap[callback] = interactorCallback
+ userInteractor.addCallback(interactorCallback)
} else {
_oldImpl.addUserSwitchCallback(callback)
}
}
override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ val interactorCallback = callbackCompatMap.remove(callback)
+ if (interactorCallback != null) {
+ userInteractor.removeCallback(interactorCallback)
+ }
} else {
_oldImpl.removeUserSwitchCallback(callback)
}
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
- if (isNewImpl) {
- notYetImplemented()
+ if (useInteractor) {
+ userInteractor.dump(pw)
} else {
_oldImpl.dump(pw, args)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index d365aa6..46d2f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -17,17 +17,13 @@
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
@@ -40,7 +36,6 @@
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -49,17 +44,14 @@
import android.widget.Toast;
import androidx.annotation.Nullable;
-import androidx.collection.SimpleArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -75,10 +67,12 @@
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.CreateUserActivity;
import com.android.systemui.user.data.source.UserRecord;
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
+import com.android.systemui.user.shared.model.UserActionModel;
+import com.android.systemui.user.ui.dialog.AddUserDialog;
+import com.android.systemui.user.ui.dialog.ExitGuestDialog;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -139,9 +133,6 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final DialogLaunchAnimator mDialogLaunchAnimator;
- private final SimpleArrayMap<UserRecord, EnforcedAdmin> mEnforcedAdminByUserRecord =
- new SimpleArrayMap<>();
- private final ArraySet<UserRecord> mDisabledByAdmin = new ArraySet<>();
private ArrayList<UserRecord> mUsers = new ArrayList<>();
@VisibleForTesting
@@ -334,7 +325,6 @@
for (UserInfo info : infos) {
boolean isCurrent = currentId == info.id;
- boolean switchToEnabled = canSwitchUsers || isCurrent;
if (!mUserSwitcherEnabled && !info.isPrimary()) {
continue;
}
@@ -343,25 +333,22 @@
if (info.isGuest()) {
// Tapping guest icon triggers remove and a user switch therefore
// the icon shouldn't be enabled even if the user is current
- guestRecord = new UserRecord(info, null /* picture */,
- true /* isGuest */, isCurrent, false /* isAddUser */,
- false /* isRestricted */, canSwitchUsers,
- false /* isAddSupervisedUser */);
+ guestRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ mUserManager,
+ null /* picture */,
+ info,
+ isCurrent,
+ canSwitchUsers);
} else if (info.supportsSwitchToByUser()) {
- Bitmap picture = bitmaps.get(info.id);
- if (picture == null) {
- picture = mUserManager.getUserIcon(info.id);
-
- if (picture != null) {
- int avatarSize = mContext.getResources()
- .getDimensionPixelSize(R.dimen.max_avatar_size);
- picture = Bitmap.createScaledBitmap(
- picture, avatarSize, avatarSize, true);
- }
- }
- records.add(new UserRecord(info, picture, false /* isGuest */,
- isCurrent, false /* isAddUser */, false /* isRestricted */,
- switchToEnabled, false /* isAddSupervisedUser */));
+ records.add(
+ LegacyUserDataHelper.createRecord(
+ mContext,
+ mUserManager,
+ bitmaps.get(info.id),
+ info,
+ isCurrent,
+ canSwitchUsers));
}
}
}
@@ -372,18 +359,20 @@
// we will just use it as an indicator for "Resetting guest...".
// Otherwise, default to canSwitchUsers.
boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
- guestRecord = new UserRecord(null /* info */, null /* picture */,
- true /* isGuest */, false /* isCurrent */,
- false /* isAddUser */, false /* isRestricted */,
- isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
- checkIfAddUserDisallowedByAdminOnly(guestRecord);
+ guestRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ currentId,
+ UserActionModel.ENTER_GUEST_MODE,
+ false /* isRestricted */,
+ isSwitchToGuestEnabled);
records.add(guestRecord);
} else if (canCreateGuest(guestRecord != null)) {
- guestRecord = new UserRecord(null /* info */, null /* picture */,
- true /* isGuest */, false /* isCurrent */,
- false /* isAddUser */, createIsRestricted(), canSwitchUsers,
- false /* isAddSupervisedUser */);
- checkIfAddUserDisallowedByAdminOnly(guestRecord);
+ guestRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ currentId,
+ UserActionModel.ENTER_GUEST_MODE,
+ false /* isRestricted */,
+ canSwitchUsers);
records.add(guestRecord);
}
} else {
@@ -391,20 +380,23 @@
}
if (canCreateUser()) {
- UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
- false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
- createIsRestricted(), canSwitchUsers,
- false /* isAddSupervisedUser */);
- checkIfAddUserDisallowedByAdminOnly(addUserRecord);
- records.add(addUserRecord);
+ final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ currentId,
+ UserActionModel.ADD_USER,
+ createIsRestricted(),
+ canSwitchUsers);
+ records.add(userRecord);
}
if (canCreateSupervisedUser()) {
- UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
- false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
- createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
- checkIfAddUserDisallowedByAdminOnly(addUserRecord);
- records.add(addUserRecord);
+ final UserRecord userRecord = LegacyUserDataHelper.createRecord(
+ mContext,
+ currentId,
+ UserActionModel.ADD_SUPERVISED_USER,
+ createIsRestricted(),
+ canSwitchUsers);
+ records.add(userRecord);
}
mUiExecutor.execute(() -> {
@@ -591,12 +583,23 @@
showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
}
- private void showExitGuestDialog(int id, boolean isGuestEphemeral,
- int targetId, DialogShower dialogShower) {
+ private void showExitGuestDialog(
+ int id,
+ boolean isGuestEphemeral,
+ int targetId,
+ DialogShower dialogShower) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
}
- mExitGuestDialog = new ExitGuestDialog(mContext, id, isGuestEphemeral, targetId);
+ mExitGuestDialog = new ExitGuestDialog(
+ mContext,
+ id,
+ isGuestEphemeral,
+ targetId,
+ mKeyguardStateController.isShowing(),
+ mFalsingManager,
+ mDialogLaunchAnimator,
+ this::exitGuestUser);
if (dialogShower != null) {
dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
@@ -622,7 +625,15 @@
if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
mAddUserDialog.cancel();
}
- mAddUserDialog = new AddUserDialog(mContext);
+ final UserInfo currentUser = mUserTracker.getUserInfo();
+ mAddUserDialog = new AddUserDialog(
+ mContext,
+ currentUser.getUserHandle(),
+ mKeyguardStateController.isShowing(),
+ /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
+ mFalsingManager,
+ mBroadcastSender,
+ mDialogLaunchAnimator);
if (dialogShower != null) {
dialogShower.showDialog(mAddUserDialog,
new DialogCuj(
@@ -964,30 +975,6 @@
return mKeyguardStateController.isShowing();
}
- @Override
- @Nullable
- public EnforcedAdmin getEnforcedAdmin(UserRecord record) {
- return mEnforcedAdminByUserRecord.get(record);
- }
-
- @Override
- public boolean isDisabledByAdmin(UserRecord record) {
- return mDisabledByAdmin.contains(record);
- }
-
- private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
- EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
- UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
- if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) {
- mDisabledByAdmin.add(record);
- mEnforcedAdminByUserRecord.put(record, admin);
- } else {
- mDisabledByAdmin.remove(record);
- mEnforcedAdminByUserRecord.put(record, null);
- }
- }
-
private boolean shouldUseSimpleUserSwitcher() {
int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
@@ -1052,133 +1039,4 @@
}
}
};
-
-
- private final class ExitGuestDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
-
- private final int mGuestId;
- private final int mTargetId;
- private final boolean mIsGuestEphemeral;
-
- ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
- int targetId) {
- super(context);
- if (isGuestEphemeral) {
- setTitle(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_title));
- setMessage(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_button), this);
- } else {
- setTitle(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_title_non_ephemeral));
- setMessage(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_message_non_ephemeral));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_clear_data_button),
- this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_save_data_button),
- this);
- }
- SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
- setCanceledOnTouchOutside(false);
- mGuestId = guestId;
- mTargetId = targetId;
- mIsGuestEphemeral = isGuestEphemeral;
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
- : FalsingManager.HIGH_PENALTY;
- if (mFalsingManager.isFalseTap(penalty)) {
- return;
- }
- if (mIsGuestEphemeral) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- mDialogLaunchAnimator.dismissStack(this);
- // Ephemeral guest: exit guest, guest is removed by the system
- // on exit, since its marked ephemeral
- exitGuestUser(mGuestId, mTargetId, false);
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- // Cancel clicked, do nothing
- cancel();
- }
- } else {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- mDialogLaunchAnimator.dismissStack(this);
- // Non-ephemeral guest: exit guest, guest is not removed by the system
- // on exit, since its marked non-ephemeral
- exitGuestUser(mGuestId, mTargetId, false);
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- mDialogLaunchAnimator.dismissStack(this);
- // Non-ephemeral guest: remove guest and then exit
- exitGuestUser(mGuestId, mTargetId, true);
- } else if (which == DialogInterface.BUTTON_NEUTRAL) {
- // Cancel clicked, do nothing
- cancel();
- }
- }
- }
- }
-
- @VisibleForTesting
- final class AddUserDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
-
- AddUserDialog(Context context) {
- super(context);
-
- setTitle(com.android.settingslib.R.string.user_add_user_title);
- String message = context.getString(
- com.android.settingslib.R.string.user_add_user_message_short);
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser != null && currentUser.isGuest() && currentUser.isEphemeral()) {
- message += context.getString(R.string.user_add_user_message_guest_remove);
- }
- setMessage(message);
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(android.R.string.ok), this);
- SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- int penalty = which == BUTTON_NEGATIVE ? FalsingManager.NO_PENALTY
- : FalsingManager.MODERATE_PENALTY;
- if (mFalsingManager.isFalseTap(penalty)) {
- return;
- }
- if (which == BUTTON_NEUTRAL) {
- cancel();
- } else {
- mDialogLaunchAnimator.dismissStack(this);
- if (ActivityManager.isUserAMonkey()) {
- return;
- }
- // Use broadcast instead of ShadeController, as this dialog may have started in
- // another process and normal dagger bindings are not available
- mBroadcastSender.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
- getContext().startActivityAsUser(
- CreateUserActivity.createIntentForStart(getContext()),
- mUserTracker.getUserHandle());
- }
- }
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
new file mode 100644
index 0000000..9c38dc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.telephony.data.repository
+
+import android.telephony.Annotation
+import android.telephony.TelephonyCallback
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.TelephonyListenerManager
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that encapsulate _some_ telephony-related state. */
+interface TelephonyRepository {
+ /** The state of the current call. */
+ @Annotation.CallState val callState: Flow<Int>
+}
+
+/**
+ * NOTE: This repository tracks only telephony-related state regarding the default mobile
+ * subscription. `TelephonyListenerManager` does not create new instances of `TelephonyManager` on a
+ * per-subscription basis and thus will always be tracking telephony information regarding
+ * `SubscriptionManager.getDefaultSubscriptionId`. See `TelephonyManager` and `SubscriptionManager`
+ * for more documentation.
+ */
+@SysUISingleton
+class TelephonyRepositoryImpl
+@Inject
+constructor(
+ private val manager: TelephonyListenerManager,
+) : TelephonyRepository {
+ @Annotation.CallState
+ override val callState: Flow<Int> = conflatedCallbackFlow {
+ val listener = TelephonyCallback.CallStateListener { state -> trySend(state) }
+
+ manager.addCallStateListener(listener)
+
+ awaitClose { manager.removeCallStateListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
new file mode 100644
index 0000000..630fbf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.telephony.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface TelephonyRepositoryModule {
+ @Binds fun repository(impl: TelephonyRepositoryImpl): TelephonyRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
new file mode 100644
index 0000000..86ca33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.telephony.domain.interactor
+
+import android.telephony.Annotation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.telephony.data.repository.TelephonyRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Hosts business logic related to telephony. */
+@SysUISingleton
+class TelephonyInteractor
+@Inject
+constructor(
+ repository: TelephonyRepository,
+) {
+ @Annotation.CallState val callState: Flow<Int> = repository.callState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a52e2af..93650b0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -20,17 +20,18 @@
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
-import androidx.annotation.CallSuper
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -70,7 +71,8 @@
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
- flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
@@ -84,11 +86,8 @@
*/
internal abstract val windowLayoutParams: WindowManager.LayoutParams
- /** The view currently being displayed. Null if the view is not being displayed. */
- private var view: ViewGroup? = null
-
- /** The info currently being displayed. Null if the view is not being displayed. */
- internal var info: T? = null
+ /** A container for all the display-related objects. Null if the view is not being displayed. */
+ private var displayInfo: DisplayInfo? = null
/** A [Runnable] that, when run, will cancel the pending timeout of the view. */
private var cancelViewTimeout: Runnable? = null
@@ -100,10 +99,11 @@
* display the correct information in the view.
*/
fun displayView(newInfo: T) {
- val currentView = view
+ val currentDisplayInfo = displayInfo
- if (currentView != null) {
- updateView(newInfo, currentView)
+ if (currentDisplayInfo != null) {
+ currentDisplayInfo.info = newInfo
+ updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
// The view is new, so set up all our callbacks and inflate the view
configurationController.addCallback(displayScaleListener)
@@ -140,19 +140,24 @@
val newView = LayoutInflater
.from(context)
.inflate(viewLayoutRes, null) as ViewGroup
- view = newView
- updateView(newInfo, newView)
+ val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
+ newViewController.init()
+
+ // We don't need to hold on to the view controller since we never set anything additional
+ // on it -- it will be automatically cleaned up when the view is detached.
+ val newDisplayInfo = DisplayInfo(newView, newInfo)
+ displayInfo = newDisplayInfo
+ updateView(newDisplayInfo.info, newDisplayInfo.view)
windowManager.addView(newView, windowLayoutParams)
animateViewIn(newView)
}
/** Removes then re-inflates the view. */
private fun reinflateView() {
- val currentInfo = info
- if (view == null || currentInfo == null) { return }
+ val currentViewInfo = displayInfo ?: return
- windowManager.removeView(view)
- inflateAndUpdateView(currentInfo)
+ windowManager.removeView(currentViewInfo.view)
+ inflateAndUpdateView(currentViewInfo.info)
}
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -167,30 +172,67 @@
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
- open fun removeView(removalReason: String) {
- if (view == null) { return }
+ fun removeView(removalReason: String) {
+ val currentDisplayInfo = displayInfo ?: return
+ if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) {
+ return
+ }
+
+ val currentView = currentDisplayInfo.view
+ animateViewOut(currentView) { windowManager.removeView(currentView) }
+
logger.logChipRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
- windowManager.removeView(view)
- view = null
- info = null
+ // Re-set to null immediately (instead as part of the animation end runnable) so
+ // that if a new view event comes in while this view is animating out, we still display the
+ // new view appropriately.
+ displayInfo = null
// No need to time the view out since it's already gone
cancelViewTimeout?.run()
}
/**
+ * Returns true if a view removal request should be ignored and false otherwise.
+ *
+ * Allows subclasses to keep the view visible for longer in certain circumstances.
+ */
+ open fun shouldIgnoreViewRemoval(info: T, removalReason: String): Boolean = false
+
+ /**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
- @CallSuper
- open fun updateView(newInfo: T, currentView: ViewGroup) {
- info = newInfo
- }
+ abstract fun updateView(newInfo: T, currentView: ViewGroup)
+
+ /**
+ * Fills [outRect] with the touchable region of this view. This will be used by WindowManager
+ * to decide which touch events go to the view.
+ */
+ abstract fun getTouchableRegion(view: View, outRect: Rect)
/**
* A method that can be implemented by subclasses to do custom animations for when the view
* appears.
*/
- open fun animateViewIn(view: ViewGroup) {}
+ internal open fun animateViewIn(view: ViewGroup) {}
+
+ /**
+ * A method that can be implemented by subclasses to do custom animations for when the view
+ * disappears.
+ *
+ * @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
+ */
+ internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ onAnimationEnd.run()
+ }
+
+ /** A container for all the display-related state objects. */
+ private inner class DisplayInfo(
+ /** The view currently being displayed. */
+ val view: ViewGroup,
+
+ /** The info currently being displayed. */
+ var info: T,
+ )
}
object TemporaryDisplayRemovalReason {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt
new file mode 100644
index 0000000..60241a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TouchableRegionViewController.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.temporarydisplay
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewTreeObserver
+import com.android.systemui.util.ViewController
+
+/**
+ * A view controller that will notify the [ViewTreeObserver] about the touchable region for this
+ * view. This will be used by WindowManager to decide which touch events go to the view and which
+ * pass through to the window below.
+ *
+ * @param touchableRegionSetter a function that, given the view and an out rect, fills the rect with
+ * the touchable region of this view.
+ */
+class TouchableRegionViewController(
+ view: View,
+ touchableRegionSetter: (View, Rect) -> Unit,
+) : ViewController<View>(view) {
+
+ private val tempRect = Rect()
+
+ private val internalInsetsListener =
+ ViewTreeObserver.OnComputeInternalInsetsListener { inoutInfo ->
+ inoutInfo.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
+ )
+
+ tempRect.setEmpty()
+ touchableRegionSetter.invoke(mView, tempRect)
+ inoutInfo.touchableRegion.set(tempRect)
+ }
+
+ public override fun onViewAttached() {
+ mView.viewTreeObserver.addOnComputeInternalInsetsListener(internalInsetsListener)
+ }
+
+ public override fun onViewDetached() {
+ mView.viewTreeObserver.removeOnComputeInternalInsetsListener(internalInsetsListener)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index adef182..a345d99 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -171,6 +171,10 @@
@Override
public void onColorsChanged(WallpaperColors wallpaperColors, int which, int userId) {
+ WallpaperColors currentColors = mCurrentColors.get(userId);
+ if (wallpaperColors != null && wallpaperColors.equals(currentColors)) {
+ return;
+ }
boolean currentUser = userId == mUserTracker.getUserId();
if (currentUser && !mAcceptColorEvents
&& mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 00ed3d6..3ce5ca3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -22,25 +22,20 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.os.PowerManager;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.ReferenceSystemUIModule;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.privacy.MediaProjectionPrivacyItemMonitor;
import com.android.systemui.privacy.PrivacyItemMonitor;
@@ -64,8 +59,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryControllerImpl;
+import com.android.systemui.statusbar.policy.AospPolicyModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
@@ -86,18 +80,21 @@
import dagger.multibindings.IntoSet;
/**
- * A dagger module for injecting default implementations of components of System UI that may be
- * overridden by the System UI implementation.
+ * A TV specific version of {@link ReferenceSystemUIModule}.
+ *
+ * Code here should be specific to the TV variant of SystemUI and will not be included in other
+ * variants of SystemUI.
*/
-@Module(includes = {
- GestureModule.class,
- PowerModule.class,
- QSModule.class,
- ReferenceScreenshotModule.class,
- VolumeModule.class,
- },
- subcomponents = {
- })
+@Module(
+ includes = {
+ AospPolicyModule.class,
+ GestureModule.class,
+ PowerModule.class,
+ QSModule.class,
+ ReferenceScreenshotModule.class,
+ VolumeModule.class,
+ }
+)
public abstract class TvSystemUIModule {
@SysUISingleton
@@ -114,21 +111,6 @@
@Provides
@SysUISingleton
- static BatteryController provideBatteryController(Context context,
- EnhancedEstimates enhancedEstimates, PowerManager powerManager,
- BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
- DumpManager dumpManager,
- @Main Handler mainHandler, @Background Handler bgHandler) {
- BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
- broadcastDispatcher, demoModeController,
- dumpManager,
- mainHandler, bgHandler);
- bC.init();
- return bC;
- }
-
- @Provides
- @SysUISingleton
static SensorPrivacyController provideSensorPrivacyController(
SensorPrivacyManager sensorPrivacyManager) {
SensorPrivacyController spC = new SensorPrivacyControllerImpl(sensorPrivacyManager);
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 5b522dc..0c72b78 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -20,6 +20,7 @@
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
+import com.android.systemui.user.ui.dialog.UserDialogModule;
import dagger.Binds;
import dagger.Module;
@@ -32,6 +33,7 @@
*/
@Module(
includes = {
+ UserDialogModule.class,
UserRepositoryModule.class,
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
new file mode 100644
index 0000000..4fd55c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/UserSwitcherSettingsModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.data.model
+
+/** Encapsulates the state of settings related to user switching. */
+data class UserSwitcherSettingsModel(
+ val isSimpleUserSwitcher: Boolean = false,
+ val isAddUsersFromLockscreen: Boolean = false,
+ val isUserSwitcherEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 0356388..919e699 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -18,9 +18,13 @@
package com.android.systemui.user.data.repository
import android.content.Context
+import android.content.pm.UserInfo
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.util.UserIcons
import com.android.systemui.R
@@ -29,15 +33,36 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Acts as source of truth for user related data.
@@ -55,6 +80,18 @@
/** List of available user-related actions. */
val actions: Flow<List<UserActionModel>>
+ /** User switcher related settings. */
+ val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
+
+ /** List of all users on the device. */
+ val userInfos: Flow<List<UserInfo>>
+
+ /** [UserInfo] of the currently-selected user. */
+ val selectedUserInfo: Flow<UserInfo>
+
+ /** User ID of the last non-guest selected user. */
+ val lastSelectedNonGuestUserId: Int
+
/** Whether actions are available even when locked. */
val isActionableWhenLocked: Flow<Boolean>
@@ -62,7 +99,23 @@
val isGuestUserAutoCreated: Boolean
/** Whether the guest user is currently being reset. */
- val isGuestUserResetting: Boolean
+ var isGuestUserResetting: Boolean
+
+ /** Whether we've scheduled the creation of a guest user. */
+ val isGuestUserCreationScheduled: AtomicBoolean
+
+ /** The user of the secondary service. */
+ var secondaryUserId: Int
+
+ /** Whether refresh users should be paused. */
+ var isRefreshUsersPaused: Boolean
+
+ /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
+ fun refreshUsers()
+
+ fun getSelectedUserInfo(): UserInfo
+
+ fun isSimpleUserSwitcher(): Boolean
}
@SysUISingleton
@@ -71,9 +124,31 @@
constructor(
@Application private val appContext: Context,
private val manager: UserManager,
- controller: UserSwitcherController,
+ private val controller: UserSwitcherController,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val globalSettings: GlobalSettings,
+ private val tracker: UserTracker,
+ private val featureFlags: FeatureFlags,
) : UserRepository {
+ private val isNewImpl: Boolean
+ get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+ private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null)
+ override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+ _userSwitcherSettings.asStateFlow().filterNotNull()
+
+ private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
+ override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
+
+ private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
+ override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+
+ override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+ private set
+
private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
fun send() {
trySendWithFailureLogging(
@@ -99,11 +174,148 @@
override val actions: Flow<List<UserActionModel>> =
userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
- override val isActionableWhenLocked: Flow<Boolean> = controller.isAddUsersFromLockScreenEnabled
+ override val isActionableWhenLocked: Flow<Boolean> =
+ if (isNewImpl) {
+ emptyFlow()
+ } else {
+ controller.isAddUsersFromLockScreenEnabled
+ }
- override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated
+ override val isGuestUserAutoCreated: Boolean =
+ if (isNewImpl) {
+ appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
+ } else {
+ controller.isGuestUserAutoCreated
+ }
- override val isGuestUserResetting: Boolean = controller.isGuestUserResetting
+ private var _isGuestUserResetting: Boolean = false
+ override var isGuestUserResetting: Boolean =
+ if (isNewImpl) {
+ _isGuestUserResetting
+ } else {
+ controller.isGuestUserResetting
+ }
+ set(value) =
+ if (isNewImpl) {
+ _isGuestUserResetting = value
+ } else {
+ error("Not supported in the old implementation!")
+ }
+
+ override val isGuestUserCreationScheduled = AtomicBoolean()
+
+ override var secondaryUserId: Int = UserHandle.USER_NULL
+
+ override var isRefreshUsersPaused: Boolean = false
+
+ init {
+ if (isNewImpl) {
+ observeSelectedUser()
+ observeUserSettings()
+ }
+ }
+
+ override fun refreshUsers() {
+ applicationScope.launch {
+ val result = withContext(backgroundDispatcher) { manager.aliveUsers }
+
+ if (result != null) {
+ _userInfos.value = result.sortedBy { it.creationTime }
+ }
+ }
+ }
+
+ override fun getSelectedUserInfo(): UserInfo {
+ return checkNotNull(_selectedUserInfo.value)
+ }
+
+ override fun isSimpleUserSwitcher(): Boolean {
+ return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+ }
+
+ private fun observeSelectedUser() {
+ conflatedCallbackFlow {
+ fun send() {
+ trySendWithFailureLogging(tracker.userInfo, TAG)
+ }
+
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ send()
+ }
+ }
+
+ tracker.addCallback(callback, mainDispatcher.asExecutor())
+ send()
+
+ awaitClose { tracker.removeCallback(callback) }
+ }
+ .onEach {
+ if (!it.isGuest) {
+ lastSelectedNonGuestUserId = it.id
+ }
+
+ _selectedUserInfo.value = it
+ }
+ .launchIn(applicationScope)
+ }
+
+ private fun observeUserSettings() {
+ globalSettings
+ .observerFlow(
+ names =
+ arrayOf(
+ SETTING_SIMPLE_USER_SWITCHER,
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ Settings.Global.USER_SWITCHER_ENABLED,
+ ),
+ userId = UserHandle.USER_SYSTEM,
+ )
+ .onStart { emit(Unit) } // Forces an initial update.
+ .map { getSettings() }
+ .onEach { _userSwitcherSettings.value = it }
+ .launchIn(applicationScope)
+ }
+
+ private suspend fun getSettings(): UserSwitcherSettingsModel {
+ return withContext(backgroundDispatcher) {
+ val isSimpleUserSwitcher =
+ globalSettings.getIntForUser(
+ SETTING_SIMPLE_USER_SWITCHER,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
+ UserHandle.USER_SYSTEM,
+ ) != 0
+
+ val isAddUsersFromLockscreen =
+ globalSettings.getIntForUser(
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ 0,
+ UserHandle.USER_SYSTEM,
+ ) != 0
+
+ val isUserSwitcherEnabled =
+ globalSettings.getIntForUser(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ 0,
+ UserHandle.USER_SYSTEM,
+ ) != 0
+
+ UserSwitcherSettingsModel(
+ isSimpleUserSwitcher = isSimpleUserSwitcher,
+ isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+ isUserSwitcherEnabled = isUserSwitcherEnabled,
+ )
+ }
+ }
private fun UserRecord.isUser(): Boolean {
return when {
@@ -125,6 +337,7 @@
image = getUserImage(this),
isSelected = isCurrent,
isSelectable = isSwitchToEnabled || isGuest,
+ isGuest = isGuest,
)
}
@@ -162,5 +375,6 @@
companion object {
private const val TAG = "UserRepository"
+ @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index cf6da9a..9370286 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -19,6 +19,7 @@
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserHandle
+import com.android.settingslib.RestrictedLockUtils
/** Encapsulates raw data for a user or an option item related to managing users on the device. */
data class UserRecord(
@@ -41,6 +42,11 @@
@JvmField val isSwitchToEnabled: Boolean = false,
/** Whether this record represents an option to add another supervised user to the device. */
@JvmField val isAddSupervisedUser: Boolean = false,
+ /**
+ * An enforcing admin, if the user action represented by this record is disabled by the admin.
+ * If not disabled, this is `null`.
+ */
+ @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
) {
/** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
@@ -59,6 +65,14 @@
}
}
+ /**
+ * Returns `true` if the user action represented by this record has been disabled by an admin;
+ * `false` otherwise.
+ */
+ fun isDisabledByAdmin(): Boolean {
+ return enforcedAdmin != null
+ }
+
companion object {
@JvmStatic
fun createForGuest(): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
new file mode 100644
index 0000000..07e5cf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.domain.interactor
+
+import android.annotation.UserIdInt
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import android.view.WindowManagerGlobal
+import android.widget.Toast
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+/** Encapsulates business logic to interact with guest user data and systems. */
+@SysUISingleton
+class GuestUserInteractor
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val manager: UserManager,
+ private val repository: UserRepository,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val refreshUsersScheduler: RefreshUsersScheduler,
+ private val uiEventLogger: UiEventLogger,
+) {
+ /** Whether the device is configured to always have a guest user available. */
+ val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+
+ /** Whether the guest user is currently being reset. */
+ val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+
+ /** Notifies that the device has finished booting. */
+ fun onDeviceBootCompleted() {
+ applicationScope.launch {
+ if (isDeviceAllowedToAddGuest()) {
+ guaranteePresent()
+ return@launch
+ }
+
+ suspendCancellableCoroutine<Unit> { continuation ->
+ val callback =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ continuation.resumeWith(Result.success(Unit))
+ deviceProvisionedController.removeCallback(this)
+ }
+ }
+
+ deviceProvisionedController.addCallback(callback)
+ }
+
+ if (isDeviceAllowedToAddGuest()) {
+ guaranteePresent()
+ }
+ }
+ }
+
+ /** Creates a guest user and switches to it. */
+ fun createAndSwitchTo(
+ showDialog: (ShowDialogRequestModel) -> Unit,
+ dismissDialog: () -> Unit,
+ selectUser: (userId: Int) -> Unit,
+ ) {
+ applicationScope.launch {
+ val newGuestUserId = create(showDialog, dismissDialog)
+ if (newGuestUserId != UserHandle.USER_NULL) {
+ selectUser(newGuestUserId)
+ }
+ }
+ }
+
+ /** Exits the guest user, switching back to the last non-guest user or to the default user. */
+ fun exit(
+ @UserIdInt guestUserId: Int,
+ @UserIdInt targetUserId: Int,
+ forceRemoveGuestOnExit: Boolean,
+ showDialog: (ShowDialogRequestModel) -> Unit,
+ dismissDialog: () -> Unit,
+ switchUser: (userId: Int) -> Unit,
+ ) {
+ val currentUserInfo = repository.getSelectedUserInfo()
+ if (currentUserInfo.id != guestUserId) {
+ Log.w(
+ TAG,
+ "User requesting to start a new session ($guestUserId) is not current user" +
+ " (${currentUserInfo.id})"
+ )
+ return
+ }
+
+ if (!currentUserInfo.isGuest) {
+ Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+ return
+ }
+
+ applicationScope.launch {
+ var newUserId = UserHandle.USER_SYSTEM
+ if (targetUserId == UserHandle.USER_NULL) {
+ // When a target user is not specified switch to last non guest user:
+ val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
+ if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
+ val info =
+ withContext(backgroundDispatcher) {
+ manager.getUserInfo(lastSelectedNonGuestUserHandle)
+ }
+ if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
+ newUserId = info.id
+ }
+ }
+ } else {
+ newUserId = targetUserId
+ }
+
+ if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
+ remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
+ } else {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
+ switchUser(newUserId)
+ }
+ }
+ }
+
+ /**
+ * Guarantees that the guest user is present on the device, creating it if needed and if allowed
+ * to.
+ */
+ suspend fun guaranteePresent() {
+ if (!isDeviceAllowedToAddGuest()) {
+ return
+ }
+
+ val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
+ if (guestUser == null) {
+ scheduleCreation()
+ }
+ }
+
+ /** Removes the guest user from the device. */
+ suspend fun remove(
+ @UserIdInt guestUserId: Int,
+ @UserIdInt targetUserId: Int,
+ showDialog: (ShowDialogRequestModel) -> Unit,
+ dismissDialog: () -> Unit,
+ switchUser: (userId: Int) -> Unit,
+ ) {
+ val currentUser: UserInfo = repository.getSelectedUserInfo()
+ if (currentUser.id != guestUserId) {
+ Log.w(
+ TAG,
+ "User requesting to start a new session ($guestUserId) is not current user" +
+ " ($currentUser.id)"
+ )
+ return
+ }
+
+ if (!currentUser.isGuest) {
+ Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
+ return
+ }
+
+ val marked =
+ withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
+ if (!marked) {
+ Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
+ return
+ }
+
+ if (targetUserId == UserHandle.USER_NULL) {
+ // Create a new guest in the foreground, and then immediately switch to it
+ val newGuestId = create(showDialog, dismissDialog)
+ if (newGuestId == UserHandle.USER_NULL) {
+ Log.e(TAG, "Could not create new guest, switching back to system user")
+ switchUser(UserHandle.USER_SYSTEM)
+ withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+ } catch (e: RemoteException) {
+ Log.e(
+ TAG,
+ "Couldn't remove guest because ActivityManager or WindowManager is dead"
+ )
+ }
+ return
+ }
+
+ switchUser(newGuestId)
+
+ withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+ } else {
+ if (repository.isGuestUserAutoCreated) {
+ repository.isGuestUserResetting = true
+ }
+ switchUser(targetUserId)
+ manager.removeUser(currentUser.id)
+ }
+ }
+
+ /**
+ * Creates the guest user and adds it to the device.
+ *
+ * @param showDialog A function to invoke to show a dialog.
+ * @param dismissDialog A function to invoke to dismiss a dialog.
+ * @return The user ID of the newly-created guest user.
+ */
+ private suspend fun create(
+ showDialog: (ShowDialogRequestModel) -> Unit,
+ dismissDialog: () -> Unit,
+ ): Int {
+ return withContext(mainDispatcher) {
+ showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+ val guestUserId = createInBackground()
+ dismissDialog()
+ if (guestUserId != UserHandle.USER_NULL) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
+ } else {
+ Toast.makeText(
+ applicationContext,
+ com.android.settingslib.R.string.add_guest_failed,
+ Toast.LENGTH_SHORT,
+ )
+ .show()
+ }
+
+ guestUserId
+ }
+ }
+
+ /** Schedules the creation of the guest user. */
+ private suspend fun scheduleCreation() {
+ if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
+ return
+ }
+
+ withContext(backgroundDispatcher) {
+ val newGuestUserId = createInBackground()
+ repository.isGuestUserCreationScheduled.set(false)
+ repository.isGuestUserResetting = false
+ if (newGuestUserId == UserHandle.USER_NULL) {
+ Log.w(TAG, "Could not create new guest while exiting existing guest")
+ // Refresh users so that we still display "Guest" if
+ // config_guestUserAutoCreated=true
+ refreshUsersScheduler.refreshIfNotPaused()
+ }
+ }
+ }
+
+ /**
+ * Creates a guest user and return its multi-user user ID.
+ *
+ * This method does not check if a guest already exists before it makes a call to [UserManager]
+ * to create a new one.
+ *
+ * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
+ * the guest couldn't be created.
+ */
+ @UserIdInt
+ private suspend fun createInBackground(): Int {
+ return withContext(backgroundDispatcher) {
+ try {
+ val guestUser = manager.createGuest(applicationContext)
+ if (guestUser != null) {
+ guestUser.id
+ } else {
+ Log.e(
+ TAG,
+ "Couldn't create guest, most likely because there already exists one!"
+ )
+ UserHandle.USER_NULL
+ }
+ } catch (e: UserManager.UserOperationException) {
+ Log.e(TAG, "Couldn't create guest user!", e)
+ UserHandle.USER_NULL
+ }
+ }
+ }
+
+ private fun isDeviceAllowedToAddGuest(): Boolean {
+ return deviceProvisionedController.isDeviceProvisioned &&
+ !devicePolicyManager.isDeviceManaged
+ }
+
+ companion object {
+ private const val TAG = "GuestUserInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
new file mode 100644
index 0000000..8f36821
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/RefreshUsersScheduler.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Encapsulates logic for pausing, unpausing, and scheduling a delayed job. */
+@SysUISingleton
+class RefreshUsersScheduler
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val repository: UserRepository,
+) {
+ private var scheduledUnpauseJob: Job? = null
+ private var isPaused = false
+
+ fun pause() {
+ applicationScope.launch(mainDispatcher) {
+ isPaused = true
+ scheduledUnpauseJob?.cancel()
+ scheduledUnpauseJob =
+ applicationScope.launch {
+ delay(PAUSE_REFRESH_USERS_TIMEOUT_MS)
+ unpauseAndRefresh()
+ }
+ }
+ }
+
+ fun unpauseAndRefresh() {
+ applicationScope.launch(mainDispatcher) {
+ isPaused = false
+ refreshIfNotPaused()
+ }
+ }
+
+ fun refreshIfNotPaused() {
+ applicationScope.launch(mainDispatcher) {
+ if (isPaused) {
+ return@launch
+ }
+
+ repository.refreshUsers()
+ }
+ }
+
+ companion object {
+ private const val PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
new file mode 100644
index 0000000..1b4746a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.domain.interactor
+
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.systemui.user.data.repository.UserRepository
+
+/** Utilities related to user management actions. */
+object UserActionsUtil {
+
+ /** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
+ fun canCreateGuest(
+ manager: UserManager,
+ repository: UserRepository,
+ isUserSwitcherEnabled: Boolean,
+ isAddUsersFromLockScreenEnabled: Boolean,
+ ): Boolean {
+ if (!isUserSwitcherEnabled) {
+ return false
+ }
+
+ return currentUserCanCreateUsers(manager, repository) ||
+ anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+ }
+
+ /** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
+ fun canCreateUser(
+ manager: UserManager,
+ repository: UserRepository,
+ isUserSwitcherEnabled: Boolean,
+ isAddUsersFromLockScreenEnabled: Boolean,
+ ): Boolean {
+ if (!isUserSwitcherEnabled) {
+ return false
+ }
+
+ if (
+ !currentUserCanCreateUsers(manager, repository) &&
+ !anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+ ) {
+ return false
+ }
+
+ return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
+ }
+
+ /**
+ * Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
+ */
+ fun canCreateSupervisedUser(
+ manager: UserManager,
+ repository: UserRepository,
+ isUserSwitcherEnabled: Boolean,
+ isAddUsersFromLockScreenEnabled: Boolean,
+ supervisedUserPackageName: String?
+ ): Boolean {
+ if (supervisedUserPackageName.isNullOrEmpty()) {
+ return false
+ }
+
+ return canCreateUser(
+ manager,
+ repository,
+ isUserSwitcherEnabled,
+ isAddUsersFromLockScreenEnabled
+ )
+ }
+
+ /**
+ * Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
+ */
+ private fun currentUserCanCreateUsers(
+ manager: UserManager,
+ repository: UserRepository,
+ ): Boolean {
+ val currentUser = repository.getSelectedUserInfo()
+ if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
+ return false
+ }
+
+ return systemCanCreateUsers(manager)
+ }
+
+ /** Returns `true` if the system can add users to the device; `false` otherwise. */
+ private fun systemCanCreateUsers(
+ manager: UserManager,
+ ): Boolean {
+ return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
+ }
+
+ /** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
+ private fun anyoneCanCreateUsers(
+ manager: UserManager,
+ isAddUsersFromLockScreenEnabled: Boolean,
+ ): Boolean {
+ return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 3c5b969..142a328 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -17,94 +17,747 @@
package com.android.systemui.user.domain.interactor
+import android.annotation.SuppressLint
+import android.annotation.UserIdInt
+import android.app.ActivityManager
+import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.RemoteException
+import android.os.UserHandle
+import android.os.UserManager
import android.provider.Settings
+import android.util.Log
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
+import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
/** Encapsulates business logic to interact with user data and systems. */
@SysUISingleton
class UserInteractor
@Inject
constructor(
- repository: UserRepository,
+ @Application private val applicationContext: Context,
+ private val repository: UserRepository,
private val controller: UserSwitcherController,
private val activityStarter: ActivityStarter,
- keyguardInteractor: KeyguardInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val featureFlags: FeatureFlags,
+ private val manager: UserManager,
+ @Application private val applicationScope: CoroutineScope,
+ telephonyInteractor: TelephonyInteractor,
+ broadcastDispatcher: BroadcastDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val activityManager: ActivityManager,
+ private val refreshUsersScheduler: RefreshUsersScheduler,
+ private val guestUserInteractor: GuestUserInteractor,
) {
+ /**
+ * Defines interface for classes that can be notified when the state of users on the device is
+ * changed.
+ */
+ interface UserCallback {
+ /** Returns `true` if this callback can be cleaned-up. */
+ fun isEvictable(): Boolean = false
+ /** Notifies that the state of users on the device has changed. */
+ fun onUserStateChanged()
+ }
+
+ private val isNewImpl: Boolean
+ get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
+ private val supervisedUserPackageName: String?
+ get() =
+ applicationContext.getString(
+ com.android.internal.R.string.config_supervisedUserCreationPackage
+ )
+
+ private val callbackMutex = Mutex()
+ private val callbacks = mutableSetOf<UserCallback>()
+
/** List of current on-device users to select from. */
- val users: Flow<List<UserModel>> = repository.users
+ val users: Flow<List<UserModel>>
+ get() =
+ if (isNewImpl) {
+ combine(
+ repository.userInfos,
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, settings ->
+ toUserModels(
+ userInfos = userInfos,
+ selectedUserId = selectedUserInfo.id,
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+ )
+ }
+ } else {
+ repository.users
+ }
/** The currently-selected user. */
- val selectedUser: Flow<UserModel> = repository.selectedUser
+ val selectedUser: Flow<UserModel>
+ get() =
+ if (isNewImpl) {
+ combine(
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { selectedUserInfo, settings ->
+ val selectedUserId = selectedUserInfo.id
+ checkNotNull(
+ toUserModel(
+ userInfo = selectedUserInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId),
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+ )
+ )
+ }
+ } else {
+ repository.selectedUser
+ }
/** List of user-switcher related actions that are available. */
- val actions: Flow<List<UserActionModel>> =
- combine(
- repository.isActionableWhenLocked,
- keyguardInteractor.isKeyguardShowing,
- ) { isActionableWhenLocked, isLocked ->
- isActionableWhenLocked || !isLocked
- }
- .flatMapLatest { isActionable ->
- if (isActionable) {
- repository.actions.map { actions ->
- actions +
- if (actions.isNotEmpty()) {
- // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because
- // that's a user
- // switcher specific action that is not known to the our data source
- // or other
- // features.
- listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- } else {
- // If no actions, don't add the navigate action.
- emptyList()
- }
+ val actions: Flow<List<UserActionModel>>
+ get() =
+ if (isNewImpl) {
+ combine(
+ repository.userInfos,
+ repository.userSwitcherSettings,
+ keyguardInteractor.isKeyguardShowing,
+ ) { userInfos, settings, isDeviceLocked ->
+ buildList {
+ val hasGuestUser = userInfos.any { it.isGuest }
+ if (
+ !hasGuestUser &&
+ (guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ ))
+ ) {
+ add(UserActionModel.ENTER_GUEST_MODE)
+ }
+
+ if (isDeviceLocked && !settings.isAddUsersFromLockscreen) {
+ // The device is locked and our setting to allow actions that add users
+ // from the lock-screen is not enabled. The guest action from above is
+ // always allowed, even when the device is locked, but the various "add
+ // user" actions below are not. We can finish building the list here.
+ return@buildList
+ }
+
+ if (
+ UserActionsUtil.canCreateUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ ) {
+ add(UserActionModel.ADD_USER)
+ }
+
+ if (
+ UserActionsUtil.canCreateSupervisedUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ supervisedUserPackageName,
+ )
+ ) {
+ add(UserActionModel.ADD_SUPERVISED_USER)
+ }
}
- } else {
- // If not actionable it means that we're not allowed to show actions when locked
- // and we
- // are locked. Therefore, we should show no actions.
- flowOf(emptyList())
}
+ } else {
+ combine(
+ repository.isActionableWhenLocked,
+ keyguardInteractor.isKeyguardShowing,
+ ) { isActionableWhenLocked, isLocked ->
+ isActionableWhenLocked || !isLocked
+ }
+ .flatMapLatest { isActionable ->
+ if (isActionable) {
+ repository.actions.map { actions ->
+ actions +
+ if (actions.isNotEmpty()) {
+ // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
+ // because that's a user switcher specific action that is
+ // not known to the our data source or other features.
+ listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+ } else {
+ // If no actions, don't add the navigate action.
+ emptyList()
+ }
+ }
+ } else {
+ // If not actionable it means that we're not allowed to show actions
+ // when
+ // locked and we are locked. Therefore, we should show no actions.
+ flowOf(emptyList())
+ }
+ }
}
+ val userRecords: StateFlow<ArrayList<UserRecord>> =
+ if (isNewImpl) {
+ combine(
+ repository.userInfos,
+ repository.selectedUserInfo,
+ actions,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, actionModels, settings ->
+ ArrayList(
+ userInfos.map {
+ toRecord(
+ userInfo = it,
+ selectedUserId = selectedUserInfo.id,
+ )
+ } +
+ actionModels.map {
+ toRecord(
+ action = it,
+ selectedUserId = selectedUserInfo.id,
+ isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+ )
+ }
+ )
+ }
+ .onEach { notifyCallbacks() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = ArrayList(),
+ )
+ } else {
+ MutableStateFlow(ArrayList())
+ }
+
+ val selectedUserRecord: StateFlow<UserRecord?> =
+ if (isNewImpl) {
+ repository.selectedUserInfo
+ .map { selectedUserInfo ->
+ toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+ } else {
+ MutableStateFlow(null)
+ }
+
/** Whether the device is configured to always have a guest user available. */
- val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
+ val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
/** Whether the guest user is currently being reset. */
- val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+ val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+
+ private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
+ val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
+
+ private val _dialogDismissRequests = MutableStateFlow<Unit?>(null)
+ val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
+
+ val isSimpleUserSwitcher: Boolean
+ get() =
+ if (isNewImpl) {
+ repository.isSimpleUserSwitcher()
+ } else {
+ error("Not supported in the old implementation!")
+ }
+
+ init {
+ if (isNewImpl) {
+ refreshUsersScheduler.refreshIfNotPaused()
+ telephonyInteractor.callState
+ .distinctUntilChanged()
+ .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+ .launchIn(applicationScope)
+
+ combine(
+ broadcastDispatcher.broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_USER_ADDED)
+ addAction(Intent.ACTION_USER_REMOVED)
+ addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_USER_SWITCHED)
+ addAction(Intent.ACTION_USER_STOPPED)
+ addAction(Intent.ACTION_USER_UNLOCKED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { intent, _ -> intent },
+ ),
+ repository.selectedUserInfo.pairwise(null),
+ ) { intent, selectedUserChange ->
+ Pair(intent, selectedUserChange.previousValue)
+ }
+ .onEach { (intent, previousSelectedUser) ->
+ onBroadcastReceived(intent, previousSelectedUser)
+ }
+ .launchIn(applicationScope)
+ }
+ }
+
+ fun addCallback(callback: UserCallback) {
+ applicationScope.launch { callbackMutex.withLock { callbacks.add(callback) } }
+ }
+
+ fun removeCallback(callback: UserCallback) {
+ applicationScope.launch { callbackMutex.withLock { callbacks.remove(callback) } }
+ }
+
+ fun refreshUsers() {
+ refreshUsersScheduler.refreshIfNotPaused()
+ }
+
+ fun onDialogShown() {
+ _dialogShowRequests.value = null
+ }
+
+ fun onDialogDismissed() {
+ _dialogDismissRequests.value = null
+ }
+
+ fun dump(pw: PrintWriter) {
+ pw.println("UserInteractor state:")
+ pw.println(" lastSelectedNonGuestUserId=${repository.lastSelectedNonGuestUserId}")
+
+ val users = userRecords.value.filter { it.info != null }
+ pw.println(" userCount=${userRecords.value.count { LegacyUserDataHelper.isUser(it) }}")
+ for (i in users.indices) {
+ pw.println(" ${users[i]}")
+ }
+
+ val actions = userRecords.value.filter { it.info == null }
+ pw.println(" actionCount=${userRecords.value.count { !LegacyUserDataHelper.isUser(it) }}")
+ for (i in actions.indices) {
+ pw.println(" ${actions[i]}")
+ }
+
+ pw.println("isSimpleUserSwitcher=$isSimpleUserSwitcher")
+ pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
+ }
+
+ fun onDeviceBootCompleted() {
+ guestUserInteractor.onDeviceBootCompleted()
+ }
+
+ /** Switches to the user or executes the action represented by the given record. */
+ fun onRecordSelected(
+ record: UserRecord,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
+ ) {
+ if (LegacyUserDataHelper.isUser(record)) {
+ // It's safe to use checkNotNull around record.info because isUser only returns true
+ // if record.info is not null.
+ selectUser(checkNotNull(record.info).id, dialogShower)
+ } else {
+ executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower)
+ }
+ }
/** Switches to the user with the given user ID. */
fun selectUser(
- userId: Int,
+ newlySelectedUserId: Int,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- controller.onUserSelected(userId, /* dialogShower= */ null)
+ if (isNewImpl) {
+ val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+ if (
+ newlySelectedUserId == currentlySelectedUserInfo.id &&
+ currentlySelectedUserInfo.isGuest
+ ) {
+ // Here when clicking on the currently-selected guest user to leave guest mode
+ // and return to the previously-selected non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = repository.lastSelectedNonGuestUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ )
+ )
+ return
+ }
+
+ if (currentlySelectedUserInfo.isGuest) {
+ // Here when switching from guest to a non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = newlySelectedUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ )
+ )
+ return
+ }
+
+ dialogShower?.dismiss()
+
+ switchUser(newlySelectedUserId)
+ } else {
+ controller.onUserSelected(newlySelectedUserId, dialogShower)
+ }
}
/** Executes the given action. */
- fun executeAction(action: UserActionModel) {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
- UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
- UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ false,
- )
+ fun executeAction(
+ action: UserActionModel,
+ dialogShower: UserSwitchDialogController.DialogShower? = null,
+ ) {
+ if (isNewImpl) {
+ when (action) {
+ UserActionModel.ENTER_GUEST_MODE ->
+ guestUserInteractor.createAndSwitchTo(
+ this::showDialog,
+ this::dismissDialog,
+ ) { userId ->
+ selectUser(userId, dialogShower)
+ }
+ UserActionModel.ADD_USER -> {
+ val currentUser = repository.getSelectedUserInfo()
+ showDialog(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = currentUser.userHandle,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ )
+ )
+ }
+ UserActionModel.ADD_SUPERVISED_USER ->
+ activityStarter.startActivity(
+ Intent()
+ .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ .setPackage(supervisedUserPackageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ /* dismissShade= */ false,
+ )
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_USER_SETTINGS),
+ /* dismissShade= */ false,
+ )
+ }
+ } else {
+ when (action) {
+ UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
+ UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
+ UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_USER_SETTINGS),
+ /* dismissShade= */ false,
+ )
+ }
}
}
+
+ fun exitGuestUser(
+ @UserIdInt guestUserId: Int,
+ @UserIdInt targetUserId: Int,
+ forceRemoveGuestOnExit: Boolean,
+ ) {
+ guestUserInteractor.exit(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ forceRemoveGuestOnExit = forceRemoveGuestOnExit,
+ showDialog = this::showDialog,
+ dismissDialog = this::dismissDialog,
+ switchUser = this::switchUser,
+ )
+ }
+
+ fun removeGuestUser(
+ @UserIdInt guestUserId: Int,
+ @UserIdInt targetUserId: Int,
+ ) {
+ applicationScope.launch {
+ guestUserInteractor.remove(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ ::showDialog,
+ ::dismissDialog,
+ ::selectUser,
+ )
+ }
+ }
+
+ private fun showDialog(request: ShowDialogRequestModel) {
+ _dialogShowRequests.value = request
+ }
+
+ private fun dismissDialog() {
+ _dialogDismissRequests.value = Unit
+ }
+
+ private fun notifyCallbacks() {
+ applicationScope.launch {
+ callbackMutex.withLock {
+ val iterator = callbacks.iterator()
+ while (iterator.hasNext()) {
+ val callback = iterator.next()
+ if (!callback.isEvictable()) {
+ callback.onUserStateChanged()
+ } else {
+ iterator.remove()
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun toRecord(
+ userInfo: UserInfo,
+ selectedUserId: Int,
+ ): UserRecord {
+ return LegacyUserDataHelper.createRecord(
+ context = applicationContext,
+ manager = manager,
+ userInfo = userInfo,
+ picture = null,
+ isCurrent = userInfo.id == selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId),
+ )
+ }
+
+ private suspend fun toRecord(
+ action: UserActionModel,
+ selectedUserId: Int,
+ isAddFromLockscreenEnabled: Boolean,
+ ): UserRecord {
+ return LegacyUserDataHelper.createRecord(
+ context = applicationContext,
+ selectedUserId = selectedUserId,
+ actionType = action,
+ isRestricted =
+ if (action == UserActionModel.ENTER_GUEST_MODE) {
+ // Entering guest mode is never restricted, so it's allowed to happen from the
+ // lockscreen even if the "add from lockscreen" system setting is off.
+ false
+ } else {
+ !isAddFromLockscreenEnabled
+ },
+ isSwitchToEnabled =
+ canSwitchUsers(selectedUserId) &&
+ // If the user is auto-created is must not be currently resetting.
+ !(isGuestUserAutoCreated && isGuestUserResetting),
+ )
+ }
+
+ private fun switchUser(userId: Int) {
+ // TODO(b/246631653): track jank and latency like in the old impl.
+ refreshUsersScheduler.pause()
+ try {
+ activityManager.switchUser(userId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't switch user.", e)
+ }
+ }
+
+ private suspend fun onBroadcastReceived(
+ intent: Intent,
+ previousUserInfo: UserInfo?,
+ ) {
+ val shouldRefreshAllUsers =
+ when (intent.action) {
+ Intent.ACTION_USER_SWITCHED -> {
+ dismissDialog()
+ val selectedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+ if (previousUserInfo?.id != selectedUserId) {
+ notifyCallbacks()
+ restartSecondaryService(selectedUserId)
+ }
+ if (guestUserInteractor.isGuestUserAutoCreated) {
+ guestUserInteractor.guaranteePresent()
+ }
+ true
+ }
+ Intent.ACTION_USER_INFO_CHANGED -> true
+ Intent.ACTION_USER_UNLOCKED -> {
+ // If we unlocked the system user, we should refresh all users.
+ intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL,
+ ) == UserHandle.USER_SYSTEM
+ }
+ else -> true
+ }
+
+ if (shouldRefreshAllUsers) {
+ refreshUsersScheduler.unpauseAndRefresh()
+ }
+ }
+
+ private fun restartSecondaryService(@UserIdInt userId: Int) {
+ val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java)
+ // Disconnect from the old secondary user's service
+ val secondaryUserId = repository.secondaryUserId
+ if (secondaryUserId != UserHandle.USER_NULL) {
+ applicationContext.stopServiceAsUser(
+ intent,
+ UserHandle.of(secondaryUserId),
+ )
+ repository.secondaryUserId = UserHandle.USER_NULL
+ }
+
+ // Connect to the new secondary user's service (purely to ensure that a persistent
+ // SystemUI application is created for that user)
+ if (userId != UserHandle.USER_SYSTEM) {
+ applicationContext.startServiceAsUser(
+ intent,
+ UserHandle.of(userId),
+ )
+ repository.secondaryUserId = userId
+ }
+ }
+
+ private suspend fun toUserModels(
+ userInfos: List<UserInfo>,
+ selectedUserId: Int,
+ isUserSwitcherEnabled: Boolean,
+ ): List<UserModel> {
+ val canSwitchUsers = canSwitchUsers(selectedUserId)
+
+ return userInfos
+ // The guest user should go in the last position.
+ .sortedBy { it.isGuest }
+ .mapNotNull { userInfo ->
+ toUserModel(
+ userInfo = userInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers,
+ isUserSwitcherEnabled = isUserSwitcherEnabled,
+ )
+ }
+ }
+
+ private suspend fun toUserModel(
+ userInfo: UserInfo,
+ selectedUserId: Int,
+ canSwitchUsers: Boolean,
+ isUserSwitcherEnabled: Boolean,
+ ): UserModel? {
+ val userId = userInfo.id
+ val isSelected = userId == selectedUserId
+
+ return when {
+ // When the user switcher is not enabled in settings, we only show the primary user.
+ !isUserSwitcherEnabled && !userInfo.isPrimary -> null
+
+ // We avoid showing disabled users.
+ !userInfo.isEnabled -> null
+ userInfo.isGuest ->
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = true,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers,
+ isGuest = true,
+ )
+ userInfo.supportsSwitchToByUser() ->
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = false,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers || isSelected,
+ isGuest = false,
+ )
+ else -> null
+ }
+ }
+
+ private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
+ return withContext(backgroundDispatcher) {
+ manager.getUserSwitchability(UserHandle.of(selectedUserId))
+ } == UserManager.SWITCHABILITY_STATUS_OK
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private suspend fun getUserImage(
+ isGuest: Boolean,
+ userId: Int,
+ ): Drawable {
+ if (isGuest) {
+ return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle))
+ }
+
+ // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
+ // TODO(b/246631653): downscale the bitmaps to R.dimen.max_avatar_size if requested.
+ val userIcon = withContext(backgroundDispatcher) { manager.getUserIcon(userId) }
+ if (userIcon != null) {
+ return BitmapDrawable(userIcon)
+ }
+
+ return UserIcons.getDefaultUserIcon(
+ applicationContext.resources,
+ userId,
+ /* light= */ false
+ )
+ }
+
+ companion object {
+ private const val TAG = "UserInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
new file mode 100644
index 0000000..08d7c5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.domain.model
+
+import android.os.UserHandle
+
+/** Encapsulates a request to show a dialog. */
+sealed class ShowDialogRequestModel {
+ data class ShowAddUserDialog(
+ val userHandle: UserHandle,
+ val isKeyguardShowing: Boolean,
+ val showEphemeralMessage: Boolean,
+ ) : ShowDialogRequestModel()
+
+ data class ShowUserCreationDialog(
+ val isGuest: Boolean,
+ ) : ShowDialogRequestModel()
+
+ data class ShowExitGuestDialog(
+ val guestUserId: Int,
+ val targetUserId: Int,
+ val isGuestEphemeral: Boolean,
+ val isKeyguardShowing: Boolean,
+ val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
+ ) : ShowDialogRequestModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
new file mode 100644
index 0000000..137de15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.legacyhelper.data
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.systemui.R
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+
+/**
+ * Defines utility functions for helping with legacy data code for users.
+ *
+ * We need these to avoid code duplication between logic inside the UserSwitcherController and in
+ * modern architecture classes such as repositories, interactors, and view-models. If we ever
+ * simplify UserSwitcherController (or delete it), the code here could be moved into its call-sites.
+ */
+object LegacyUserDataHelper {
+
+ @JvmStatic
+ fun createRecord(
+ context: Context,
+ manager: UserManager,
+ picture: Bitmap?,
+ userInfo: UserInfo,
+ isCurrent: Boolean,
+ canSwitchUsers: Boolean,
+ ): UserRecord {
+ val isGuest = userInfo.isGuest
+ return UserRecord(
+ info = userInfo,
+ picture =
+ getPicture(
+ manager = manager,
+ context = context,
+ userInfo = userInfo,
+ picture = picture,
+ ),
+ isGuest = isGuest,
+ isCurrent = isCurrent,
+ isSwitchToEnabled = canSwitchUsers || (isCurrent && !isGuest),
+ )
+ }
+
+ @JvmStatic
+ fun createRecord(
+ context: Context,
+ selectedUserId: Int,
+ actionType: UserActionModel,
+ isRestricted: Boolean,
+ isSwitchToEnabled: Boolean,
+ ): UserRecord {
+ return UserRecord(
+ isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
+ isAddUser = actionType == UserActionModel.ADD_USER,
+ isAddSupervisedUser = actionType == UserActionModel.ADD_SUPERVISED_USER,
+ isRestricted = isRestricted,
+ isSwitchToEnabled = isSwitchToEnabled,
+ enforcedAdmin =
+ getEnforcedAdmin(
+ context = context,
+ selectedUserId = selectedUserId,
+ ),
+ )
+ }
+
+ fun toUserActionModel(record: UserRecord): UserActionModel {
+ check(!isUser(record))
+
+ return when {
+ record.isAddUser -> UserActionModel.ADD_USER
+ record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
+ record.isGuest -> UserActionModel.ENTER_GUEST_MODE
+ else -> error("Not a known action: $record")
+ }
+ }
+
+ fun isUser(record: UserRecord): Boolean {
+ return record.info != null
+ }
+
+ private fun getEnforcedAdmin(
+ context: Context,
+ selectedUserId: Int,
+ ): EnforcedAdmin? {
+ val admin =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context,
+ UserManager.DISALLOW_ADD_USER,
+ selectedUserId,
+ )
+ ?: return null
+
+ return if (
+ !RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context,
+ UserManager.DISALLOW_ADD_USER,
+ selectedUserId,
+ )
+ ) {
+ admin
+ } else {
+ null
+ }
+ }
+
+ private fun getPicture(
+ context: Context,
+ manager: UserManager,
+ userInfo: UserInfo,
+ picture: Bitmap?,
+ ): Bitmap? {
+ if (userInfo.isGuest) {
+ return null
+ }
+
+ if (picture != null) {
+ return picture
+ }
+
+ val unscaledOrNull = manager.getUserIcon(userInfo.id) ?: return null
+
+ val avatarSize = context.resources.getDimensionPixelSize(R.dimen.max_avatar_size)
+ return Bitmap.createScaledBitmap(
+ unscaledOrNull,
+ avatarSize,
+ avatarSize,
+ /* filter= */ true,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
index bf7977a..2095683 100644
--- a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
@@ -32,4 +32,6 @@
val isSelected: Boolean,
/** Whether this use is selectable. A non-selectable user cannot be switched to. */
val isSelectable: Boolean,
+ /** Whether this model represents the guest user. */
+ val isGuest: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
new file mode 100644
index 0000000..a9d66de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.dialog
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.UserHandle
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.CreateUserActivity
+
+/** Dialog for adding a new user to the device. */
+class AddUserDialog(
+ context: Context,
+ userHandle: UserHandle,
+ isKeyguardShowing: Boolean,
+ showEphemeralMessage: Boolean,
+ private val falsingManager: FalsingManager,
+ private val broadcastSender: BroadcastSender,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
+) : SystemUIDialog(context) {
+
+ private val onClickListener =
+ object : DialogInterface.OnClickListener {
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ val penalty =
+ if (which == BUTTON_NEGATIVE) {
+ FalsingManager.NO_PENALTY
+ } else {
+ FalsingManager.MODERATE_PENALTY
+ }
+ if (falsingManager.isFalseTap(penalty)) {
+ return
+ }
+
+ if (which == BUTTON_NEUTRAL) {
+ cancel()
+ return
+ }
+
+ dialogLaunchAnimator.dismissStack(this@AddUserDialog)
+ if (ActivityManager.isUserAMonkey()) {
+ return
+ }
+
+ // Use broadcast instead of ShadeController, as this dialog may have started in
+ // another
+ // process where normal dagger bindings are not available.
+ broadcastSender.sendBroadcastAsUser(
+ Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ UserHandle.CURRENT
+ )
+
+ context.startActivityAsUser(
+ CreateUserActivity.createIntentForStart(context),
+ userHandle,
+ )
+ }
+ }
+
+ init {
+ setTitle(R.string.user_add_user_title)
+ val message =
+ context.getString(R.string.user_add_user_message_short) +
+ if (showEphemeralMessage) {
+ context.getString(
+ com.android.systemui.R.string.user_add_user_message_guest_remove
+ )
+ } else {
+ ""
+ }
+ setMessage(message)
+
+ setButton(
+ BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel),
+ onClickListener,
+ )
+
+ setButton(
+ BUTTON_POSITIVE,
+ context.getString(android.R.string.ok),
+ onClickListener,
+ )
+
+ setWindowOnTop(this, isKeyguardShowing)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
new file mode 100644
index 0000000..19ad44d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/ExitGuestDialog.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.dialog
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.DialogInterface
+import com.android.settingslib.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog for exiting the guest user. */
+class ExitGuestDialog(
+ context: Context,
+ private val guestUserId: Int,
+ private val isGuestEphemeral: Boolean,
+ private val targetUserId: Int,
+ isKeyguardShowing: Boolean,
+ private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val onExitGuestUserListener: OnExitGuestUserListener,
+) : SystemUIDialog(context) {
+
+ fun interface OnExitGuestUserListener {
+ fun onExitGuestUser(
+ @UserIdInt guestId: Int,
+ @UserIdInt targetId: Int,
+ forceRemoveGuest: Boolean,
+ )
+ }
+
+ private val onClickListener =
+ object : DialogInterface.OnClickListener {
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ val penalty =
+ if (which == BUTTON_NEGATIVE) {
+ FalsingManager.NO_PENALTY
+ } else {
+ FalsingManager.MODERATE_PENALTY
+ }
+ if (falsingManager.isFalseTap(penalty)) {
+ return
+ }
+
+ if (isGuestEphemeral) {
+ if (which == BUTTON_POSITIVE) {
+ dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+ // Ephemeral guest: exit guest, guest is removed by the system
+ // on exit, since its marked ephemeral
+ onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, false)
+ } else if (which == BUTTON_NEGATIVE) {
+ // Cancel clicked, do nothing
+ cancel()
+ }
+ } else {
+ when (which) {
+ BUTTON_POSITIVE -> {
+ dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+ // Non-ephemeral guest: exit guest, guest is not removed by the system
+ // on exit, since its marked non-ephemeral
+ onExitGuestUserListener.onExitGuestUser(
+ guestUserId,
+ targetUserId,
+ false
+ )
+ }
+ BUTTON_NEGATIVE -> {
+ dialogLaunchAnimator.dismissStack(this@ExitGuestDialog)
+ // Non-ephemeral guest: remove guest and then exit
+ onExitGuestUserListener.onExitGuestUser(guestUserId, targetUserId, true)
+ }
+ BUTTON_NEUTRAL -> {
+ // Cancel clicked, do nothing
+ cancel()
+ }
+ }
+ }
+ }
+ }
+
+ init {
+ if (isGuestEphemeral) {
+ setTitle(context.getString(R.string.guest_exit_dialog_title))
+ setMessage(context.getString(R.string.guest_exit_dialog_message))
+ setButton(
+ BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel),
+ onClickListener,
+ )
+ setButton(
+ BUTTON_POSITIVE,
+ context.getString(R.string.guest_exit_dialog_button),
+ onClickListener,
+ )
+ } else {
+ setTitle(context.getString(R.string.guest_exit_dialog_title_non_ephemeral))
+ setMessage(context.getString(R.string.guest_exit_dialog_message_non_ephemeral))
+ setButton(
+ BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel),
+ onClickListener,
+ )
+ setButton(
+ BUTTON_NEGATIVE,
+ context.getString(R.string.guest_exit_clear_data_button),
+ onClickListener,
+ )
+ setButton(
+ BUTTON_POSITIVE,
+ context.getString(R.string.guest_exit_save_data_button),
+ onClickListener,
+ )
+ }
+ setWindowOnTop(this, isKeyguardShowing)
+ setCanceledOnTouchOutside(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
new file mode 100644
index 0000000..c1d2f47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserDialogModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.dialog
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface UserDialogModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(UserSwitcherDialogCoordinator::class)
+ fun bindFeature(impl: UserSwitcherDialogCoordinator): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
new file mode 100644
index 0000000..6e7b523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.dialog
+
+import android.app.Dialog
+import android.content.Context
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Coordinates dialogs for user switcher logic. */
+@SysUISingleton
+class UserSwitcherDialogCoordinator
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val falsingManager: FalsingManager,
+ private val broadcastSender: BroadcastSender,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val interactor: UserInteractor,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable(context) {
+
+ private var currentDialog: Dialog? = null
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+ return
+ }
+
+ startHandlingDialogShowRequests()
+ startHandlingDialogDismissRequests()
+ }
+
+ private fun startHandlingDialogShowRequests() {
+ applicationScope.launch {
+ interactor.dialogShowRequests.filterNotNull().collect { request ->
+ currentDialog?.let {
+ if (it.isShowing) {
+ it.cancel()
+ }
+ }
+
+ currentDialog =
+ when (request) {
+ is ShowDialogRequestModel.ShowAddUserDialog ->
+ AddUserDialog(
+ context = context,
+ userHandle = request.userHandle,
+ isKeyguardShowing = request.isKeyguardShowing,
+ showEphemeralMessage = request.showEphemeralMessage,
+ falsingManager = falsingManager,
+ broadcastSender = broadcastSender,
+ dialogLaunchAnimator = dialogLaunchAnimator,
+ )
+ is ShowDialogRequestModel.ShowUserCreationDialog ->
+ UserCreatingDialog(
+ context,
+ request.isGuest,
+ )
+ is ShowDialogRequestModel.ShowExitGuestDialog ->
+ ExitGuestDialog(
+ context = context,
+ guestUserId = request.guestUserId,
+ isGuestEphemeral = request.isGuestEphemeral,
+ targetUserId = request.targetUserId,
+ isKeyguardShowing = request.isKeyguardShowing,
+ falsingManager = falsingManager,
+ dialogLaunchAnimator = dialogLaunchAnimator,
+ onExitGuestUserListener = request.onExitGuestUser,
+ )
+ }
+
+ currentDialog?.show()
+ interactor.onDialogShown()
+ }
+ }
+ }
+
+ private fun startHandlingDialogDismissRequests() {
+ applicationScope.launch {
+ interactor.dialogDismissRequests.filterNotNull().collect {
+ currentDialog?.let {
+ if (it.isShowing) {
+ it.cancel()
+ }
+ }
+
+ interactor.onDialogDismissed()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 398341d..5b83df7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -21,7 +21,10 @@
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.ui.drawable.CircularDrawable
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
@@ -36,9 +39,14 @@
class UserSwitcherViewModel
private constructor(
private val userInteractor: UserInteractor,
+ private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
+ private val featureFlags: FeatureFlags,
) : ViewModel() {
+ private val isNewImpl: Boolean
+ get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
+
/** On-device users. */
val users: Flow<List<UserViewModel>> =
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -47,9 +55,6 @@
val maximumUserColumns: Flow<Int> =
users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
- /** Whether the button to open the user action menu is visible. */
- val isOpenMenuButtonVisible: Flow<Boolean> = userInteractor.actions.map { it.isNotEmpty() }
-
private val _isMenuVisible = MutableStateFlow(false)
/**
* Whether the user action menu should be shown. Once the action menu is dismissed/closed, the
@@ -58,9 +63,23 @@
val isMenuVisible: Flow<Boolean> = _isMenuVisible
/** The user action menu. */
val menu: Flow<List<UserActionViewModel>> =
- userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+ userInteractor.actions.map { actions ->
+ if (isNewImpl && actions.isNotEmpty()) {
+ // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
+ // switcher specific action that is not known to the our data source or other
+ // features.
+ actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+ } else {
+ actions
+ }
+ .map { action -> toViewModel(action) }
+ }
+
+ /** Whether the button to open the user action menu is visible. */
+ val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
private val hasCancelButtonBeenClicked = MutableStateFlow(false)
+ private val isFinishRequiredDueToExecutedAction = MutableStateFlow(false)
/**
* Whether the observer should finish the experience. Once consumed, [onFinished] must be called
@@ -81,6 +100,7 @@
*/
fun onFinished() {
hasCancelButtonBeenClicked.value = false
+ isFinishRequiredDueToExecutedAction.value = false
}
/** Notifies that the user has clicked the "open menu" button. */
@@ -120,8 +140,10 @@
},
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
- ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked ->
- selectedUserChanged || screenTurnedOff || cancelButtonClicked
+ // If an executed action told us to finish, we should finish,
+ isFinishRequiredDueToExecutedAction,
+ ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
+ selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
}
}
@@ -164,13 +186,25 @@
} else {
LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
- isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = userInteractor.isGuestUserResetting,
+ isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
isAddUser = model == UserActionModel.ADD_USER,
)
},
- onClicked = { userInteractor.executeAction(action = model) },
+ onClicked = {
+ userInteractor.executeAction(action = model)
+ // We don't finish because we want to show a dialog over the full-screen UI and
+ // that dialog can be dismissed in case the user changes their mind and decides not
+ // to add a user.
+ //
+ // We finish for all other actions because they navigate us away from the
+ // full-screen experience or are destructive (like changing to the guest user).
+ val shouldFinish = model != UserActionModel.ADD_USER
+ if (shouldFinish) {
+ isFinishRequiredDueToExecutedAction.value = true
+ }
+ },
)
}
@@ -186,13 +220,17 @@
@Inject
constructor(
private val userInteractor: UserInteractor,
+ private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
+ private val featureFlags: FeatureFlags,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return UserSwitcherViewModel(
userInteractor = userInteractor,
+ guestUserInteractor = guestUserInteractor,
powerInteractor = powerInteractor,
+ featureFlags = featureFlags,
)
as T
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d745..a925e38 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
private boolean mDefaultShowOperatorNameConfigLoaded;
private boolean mDefaultShowOperatorNameConfig;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
@Inject
public CarrierConfigTracker(
@@ -207,6 +209,22 @@
}
/**
+ * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+ * the default carrier config.
+ */
+ public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+ if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+ .getDefaultConfig().getBoolean(CarrierConfigManager
+ .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+ );
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+ }
+
+ return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+ }
+
+ /**
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
* default value if no override exists
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 8c736dc..81ae6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.BroadcastRunning;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
@@ -51,6 +52,17 @@
return thread.getLooper();
}
+ /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Looper provideBroadcastRunningLooper() {
+ HandlerThread thread = new HandlerThread("BroadcastRunning",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ }
+
/** Long running tasks Looper */
@Provides
@SysUISingleton
@@ -83,7 +95,17 @@
}
/**
- * Provide a Long running Executor by default.
+ * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
+ */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+ /**
+ * Provide a Long running Executor.
*/
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
new file mode 100644
index 0000000..0b8257d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.util.settings
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Kotlin extension functions for [SettingsProxy]. */
+object SettingsProxyExt {
+
+ /** Returns a flow of [Unit] that is invoked each time that content is updated. */
+ fun SettingsProxy.observerFlow(
+ vararg names: String,
+ @UserIdInt userId: Int = UserHandle.USER_CURRENT,
+ ): Flow<Unit> {
+ return conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ names.forEach { name -> registerContentObserverForUser(name, observer, userId) }
+
+ awaitClose { unregisterContentObserver(observer) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
index 613a797..6160b00 100644
--- a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
@@ -1,5 +1,22 @@
+/*
+ * 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.util.view
+import android.graphics.Rect
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -23,4 +40,22 @@
top <= y &&
y <= top + view.height
}
+
+ /**
+ * Sets [outRect] to be the view's location within its window.
+ */
+ fun setRectToViewWindowLocation(view: View, outRect: Rect) {
+ val locInWindow = IntArray(2)
+ view.getLocationInWindow(locInWindow)
+
+ val x = locInWindow[0]
+ val y = locInWindow[1]
+
+ outRect.set(
+ x,
+ y,
+ x + view.width,
+ y + view.height,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index bf7c459..7c022eb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -65,6 +65,7 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
@@ -178,7 +179,8 @@
WakefulnessLifecycle wakefulnessLifecycle,
CaptioningManager captioningManager,
KeyguardManager keyguardManager,
- ActivityManager activityManager
+ ActivityManager activityManager,
+ DumpManager dumpManager
) {
mContext = context.getApplicationContext();
mPackageManager = packageManager;
@@ -207,7 +209,7 @@
mCaptioningManager = captioningManager;
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
-
+ dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
boolean accessibilityVolumeStreamActive = accessibilityManager
.isAccessibilityVolumeStreamActive();
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 0f7e143..42d7d52 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -16,21 +16,17 @@
package com.android.systemui.wallpapers;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -40,8 +36,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.Display;
-import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -49,8 +43,11 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.wallpapers.canvas.ImageCanvasWallpaperRenderer;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -59,6 +56,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -78,15 +76,28 @@
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
private volatile int mPages = 1;
+ private boolean mPagesComputed = false;
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
private final FeatureFlags mFeatureFlags;
+ // used in canvasEngine to load/unload the bitmap and extract the colors
+ @Background
+ private final DelayableExecutor mBackgroundExecutor;
+ private static final int DELAY_UNLOAD_BITMAP = 2000;
+
+ @Main
+ private final Executor mMainExecutor;
+
@Inject
- public ImageWallpaper(FeatureFlags featureFlags) {
+ public ImageWallpaper(FeatureFlags featureFlags,
+ @Background DelayableExecutor backgroundExecutor,
+ @Main Executor mainExecutor) {
super();
mFeatureFlags = featureFlags;
+ mBackgroundExecutor = backgroundExecutor;
+ mMainExecutor = mainExecutor;
}
@Override
@@ -339,7 +350,6 @@
imgArea.left = 0;
imgArea.right = 1;
}
-
return imgArea;
}
@@ -510,69 +520,85 @@
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
-
- // time [ms] before unloading the wallpaper after it is loaded
- private static final int DELAY_FORGET_WALLPAPER = 5000;
-
- private final Runnable mUnloadWallpaperCallback = this::unloadWallpaper;
-
private WallpaperManager mWallpaperManager;
- private ImageCanvasWallpaperRenderer mImageCanvasWallpaperRenderer;
+ private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private SurfaceHolder mSurfaceHolder;
+ @VisibleForTesting
+ static final int MIN_SURFACE_WIDTH = 128;
+ @VisibleForTesting
+ static final int MIN_SURFACE_HEIGHT = 128;
private Bitmap mBitmap;
+ private boolean mWideColorGamut = false;
- private Display mDisplay;
- private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
-
- private AsyncTask<Void, Void, Bitmap> mLoader;
- private boolean mNeedsDrawAfterLoadingWallpaper = false;
+ /*
+ * Counter to unload the bitmap as soon as possible.
+ * Before any bitmap operation, this is incremented.
+ * After an operation completion, this is decremented (synchronously),
+ * and if the count is 0, unload the bitmap
+ */
+ private int mBitmapUsages = 0;
+ private final Object mLock = new Object();
CanvasEngine() {
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- }
+ mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ CanvasEngine.this.onColorsProcessed(regions, colors);
+ }
- void trimMemory(int level) {
- if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
- && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
- && isBitmapLoaded()) {
- if (DEBUG) {
- Log.d(TAG, "trimMemory");
- }
- unloadWallpaper();
+ @Override
+ public void onMiniBitmapUpdated() {
+ CanvasEngine.this.onMiniBitmapUpdated();
+ }
+
+ @Override
+ public void onActivated() {
+ setOffsetNotificationsEnabled(true);
+ }
+
+ @Override
+ public void onDeactivated() {
+ setOffsetNotificationsEnabled(false);
+ }
+ });
+
+ // if the number of pages is already computed, transmit it to the color extractor
+ if (mPagesComputed) {
+ mWallpaperColorExtractor.onPageChanged(mPages);
}
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#onCreate");
if (DEBUG) {
Log.d(TAG, "onCreate");
}
+ mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
+ mSurfaceHolder = surfaceHolder;
+ Rect dimensions = mWallpaperManager.peekBitmapDimensions();
+ int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
+ int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
+ mSurfaceHolder.setFixedSize(width, height);
- mWallpaperManager = getSystemService(WallpaperManager.class);
- super.onCreate(surfaceHolder);
-
- final Context displayContext = getDisplayContext();
- final int displayId = displayContext == null ? DEFAULT_DISPLAY :
- displayContext.getDisplayId();
- DisplayManager dm = getSystemService(DisplayManager.class);
- if (dm != null) {
- mDisplay = dm.getDisplay(displayId);
- if (mDisplay == null) {
- Log.e(TAG, "Cannot find display! Fallback to default.");
- mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
- }
- }
- setOffsetNotificationsEnabled(false);
-
- mImageCanvasWallpaperRenderer = new ImageCanvasWallpaperRenderer(surfaceHolder);
- loadWallpaper(false);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, null);
+ getDisplaySizeAndUpdateColorExtractor();
+ Trace.endSection();
}
@Override
public void onDestroy() {
- super.onDestroy();
- unloadWallpaper();
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
+ mWallpaperColorExtractor.cleanUp();
+ unloadBitmap();
}
@Override
@@ -581,31 +607,30 @@
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
}
- super.onSurfaceChanged(holder, format, width, height);
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
- drawFrame(false);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
- super.onSurfaceDestroyed(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceDestroyed");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(null);
+ mSurfaceHolder = null;
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
- super.onSurfaceCreated(holder);
if (DEBUG) {
Log.i(TAG, "onSurfaceCreated");
}
- mImageCanvasWallpaperRenderer.setSurfaceHolder(holder);
}
@Override
@@ -613,135 +638,90 @@
if (DEBUG) {
Log.d(TAG, "onSurfaceRedrawNeeded");
}
- super.onSurfaceRedrawNeeded(holder);
- // At the end of this method we should have drawn into the surface.
- // This means that the bitmap should be loaded synchronously if
- // it was already unloaded.
- if (!isBitmapLoaded()) {
- setBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+ drawFrame();
+ }
+
+ private void drawFrame() {
+ mBackgroundExecutor.execute(this::drawFrameSynchronized);
+ }
+
+ private void drawFrameSynchronized() {
+ synchronized (mLock) {
+ drawFrameInternal();
}
- drawFrame(true);
}
- private DisplayInfo getDisplayInfo() {
- mDisplay.getDisplayInfo(mTmpDisplayInfo);
- return mTmpDisplayInfo;
- }
-
- private void drawFrame(boolean forceRedraw) {
- if (!mImageCanvasWallpaperRenderer.isSurfaceHolderLoaded()) {
+ private void drawFrameInternal() {
+ if (mSurfaceHolder == null) {
Log.e(TAG, "attempt to draw a frame without a valid surface");
return;
}
+ // load the wallpaper if not already done
if (!isBitmapLoaded()) {
- // ensure that we load the wallpaper.
- // if the wallpaper is currently loading, this call will have no effect.
- loadWallpaper(true);
- return;
- }
- mImageCanvasWallpaperRenderer.drawFrame(mBitmap, forceRedraw);
- }
-
- private void setBitmap(Bitmap bitmap) {
- if (bitmap == null) {
- Log.e(TAG, "Attempt to set a null bitmap");
- } else if (mBitmap == bitmap) {
- Log.e(TAG, "The value of bitmap is the same");
- } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
+ loadWallpaperAndDrawFrameInternal();
} else {
- if (mBitmap != null) {
- mBitmap.recycle();
- }
- mBitmap = bitmap;
+ mBitmapUsages++;
+
+ // drawing is done on the main thread
+ mMainExecutor.execute(() -> {
+ drawFrameOnCanvas(mBitmap);
+ reportEngineShown(false);
+ unloadBitmapIfNotUsed();
+ });
}
}
- private boolean isBitmapLoaded() {
+ @VisibleForTesting
+ void drawFrameOnCanvas(Bitmap bitmap) {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
+ Surface surface = mSurfaceHolder.getSurface();
+ Canvas canvas = mWideColorGamut
+ ? surface.lockHardwareWideColorGamutCanvas()
+ : surface.lockHardwareCanvas();
+ if (canvas != null) {
+ Rect dest = mSurfaceHolder.getSurfaceFrame();
+ try {
+ canvas.drawBitmap(bitmap, null, dest, null);
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
+ }
+ Trace.endSection();
+ }
+
+ @VisibleForTesting
+ boolean isBitmapLoaded() {
return mBitmap != null && !mBitmap.isRecycled();
}
- /**
- * Loads the wallpaper on background thread and schedules updating the surface frame,
- * and if {@code needsDraw} is set also draws a frame.
- *
- * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
- * the active request).
- *
- */
- private void loadWallpaper(boolean needsDraw) {
- mNeedsDrawAfterLoadingWallpaper |= needsDraw;
- if (mLoader != null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping loadWallpaper, already in flight ");
- }
- return;
- }
- mLoader = new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- Throwable exception;
- try {
- Bitmap wallpaper = mWallpaperManager.getBitmap(true /* hardware */);
- if (wallpaper != null
- && wallpaper.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
- throw new RuntimeException("Wallpaper is too large to draw!");
- }
- return wallpaper;
- } catch (RuntimeException | OutOfMemoryError e) {
- exception = e;
- }
-
- if (isCancelled()) {
- return null;
- }
-
- // Note that if we do fail at this, and the default wallpaper can't
- // be loaded, we will go into a cycle. Don't do a build where the
- // default wallpaper can't be loaded.
- Log.w(TAG, "Unable to load wallpaper!", exception);
- try {
- mWallpaperManager.clear();
- } catch (IOException ex) {
- // now we're really screwed.
- Log.w(TAG, "Unable reset to default wallpaper!", ex);
- }
-
- if (isCancelled()) {
- return null;
- }
-
- try {
- return mWallpaperManager.getBitmap(true /* hardware */);
- } catch (RuntimeException | OutOfMemoryError e) {
- Log.w(TAG, "Unable to load default wallpaper!", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- setBitmap(bitmap);
-
- if (mNeedsDrawAfterLoadingWallpaper) {
- drawFrame(true);
- }
-
- mLoader = null;
- mNeedsDrawAfterLoadingWallpaper = false;
- scheduleUnloadWallpaper();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ private void unloadBitmapIfNotUsed() {
+ mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
}
- private void unloadWallpaper() {
- if (mLoader != null) {
- mLoader.cancel(false);
- mLoader = null;
+ private void unloadBitmapIfNotUsedSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages -= 1;
+ if (mBitmapUsages <= 0) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
}
+ }
+ private void unloadBitmap() {
+ mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
+ }
+
+ private void unloadBitmapSynchronized() {
+ synchronized (mLock) {
+ mBitmapUsages = 0;
+ unloadBitmapInternal();
+ }
+ }
+
+ private void unloadBitmapInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
if (mBitmap != null) {
mBitmap.recycle();
}
@@ -750,12 +730,131 @@
final Surface surface = getSurfaceHolder().getSurface();
surface.hwuiDestroy();
mWallpaperManager.forgetLoadedWallpaper();
+ Trace.endSection();
}
- private void scheduleUnloadWallpaper() {
- Handler handler = getMainThreadHandler();
- handler.removeCallbacks(mUnloadWallpaperCallback);
- handler.postDelayed(mUnloadWallpaperCallback, DELAY_FORGET_WALLPAPER);
+ private void loadWallpaperAndDrawFrameInternal() {
+ Trace.beginSection("ImageWallpaper.CanvasEngine#loadWallpaper");
+ boolean loadSuccess = false;
+ Bitmap bitmap;
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ if (bitmap != null
+ && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+ throw new RuntimeException("Wallpaper is too large to draw!");
+ }
+ } catch (RuntimeException | OutOfMemoryError exception) {
+
+ // Note that if we do fail at this, and the default wallpaper can't
+ // be loaded, we will go into a cycle. Don't do a build where the
+ // default wallpaper can't be loaded.
+ Log.w(TAG, "Unable to load wallpaper!", exception);
+ try {
+ mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
+ } catch (IOException ex) {
+ // now we're really screwed.
+ Log.w(TAG, "Unable reset to default wallpaper!", ex);
+ }
+
+ try {
+ bitmap = mWallpaperManager.getBitmap(false);
+ } catch (RuntimeException | OutOfMemoryError e) {
+ Log.w(TAG, "Unable to load default wallpaper!", e);
+ bitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w(TAG, "Could not load bitmap");
+ } else if (bitmap.isRecycled()) {
+ Log.e(TAG, "Attempt to load a recycled bitmap");
+ } else if (mBitmap == bitmap) {
+ Log.e(TAG, "Loaded a bitmap that was already loaded");
+ } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
+ Log.e(TAG, "Attempt to load an invalid wallpaper of length "
+ + bitmap.getWidth() + "x" + bitmap.getHeight());
+ } else {
+ // at this point, loading is done correctly.
+ loadSuccess = true;
+ // recycle the previously loaded bitmap
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = bitmap;
+ mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(
+ WallpaperManager.FLAG_SYSTEM);
+
+ // +2 usages for the color extraction and the delayed unload.
+ mBitmapUsages += 2;
+ recomputeColorExtractorMiniBitmap();
+ drawFrameInternal();
+
+ /*
+ * after loading, the bitmap will be unloaded after all these conditions:
+ * - the frame is redrawn
+ * - the mini bitmap from color extractor is recomputed
+ * - the DELAY_UNLOAD_BITMAP has passed
+ */
+ mBackgroundExecutor.executeDelayed(
+ this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
+ }
+ // even if the bitmap cannot be loaded, call reportEngineShown
+ if (!loadSuccess) reportEngineShown(false);
+ Trace.endSection();
+ }
+
+ private void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors) {
+ try {
+ notifyLocalColorsChanged(regions, colors);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ @VisibleForTesting
+ void recomputeColorExtractorMiniBitmap() {
+ mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ }
+
+ @VisibleForTesting
+ void onMiniBitmapUpdated() {
+ unloadBitmapIfNotUsed();
+ }
+
+ @Override
+ public boolean supportsLocalColorExtraction() {
+ return true;
+ }
+
+ @Override
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will activate the offset notifications
+ // if no colors were being processed before
+ mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ }
+
+ @Override
+ public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+ // this call will deactivate the offset notifications
+ // if we are no longer processing colors
+ mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ }
+
+ @Override
+ public void onOffsetsChanged(float xOffset, float yOffset,
+ float xOffsetStep, float yOffsetStep,
+ int xPixelOffset, int yPixelOffset) {
+ final int pages;
+ if (xOffsetStep > 0 && xOffsetStep <= 1) {
+ pages = Math.round(1 / xOffsetStep) + 1;
+ } else {
+ pages = 1;
+ }
+ if (pages != mPages || !mPagesComputed) {
+ mPages = pages;
+ mPagesComputed = true;
+ mWallpaperColorExtractor.onPageChanged(mPages);
+ }
}
@Override
@@ -764,13 +863,46 @@
}
@Override
- public void onDisplayChanged(int displayId) {
+ public void onDisplayRemoved(int displayId) {
}
@Override
- public void onDisplayRemoved(int displayId) {
+ public void onDisplayChanged(int displayId) {
+ // changes the display in the color extractor
+ // the new display dimensions will be used in the next color computation
+ if (displayId == getDisplayContext().getDisplayId()) {
+ getDisplaySizeAndUpdateColorExtractor();
+ }
+ }
+ private void getDisplaySizeAndUpdateColorExtractor() {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ }
+
+
+ @Override
+ protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ super.dump(prefix, fd, out, args);
+ out.print(prefix); out.print("Engine="); out.println(this);
+ out.print(prefix); out.print("valid surface=");
+ out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
+ ? getSurfaceHolder().getSurface().isValid()
+ : "null");
+
+ out.print(prefix); out.print("surface frame=");
+ out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mBitmap == null ? "null"
+ : mBitmap.isRecycled() ? "recycled"
+ : mBitmap.getWidth() + "x" + mBitmap.getHeight());
+
+ mWallpaperColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
deleted file mode 100644
index fdba16e..0000000
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRenderer.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.wallpapers.canvas;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to draw a wallpaper on a surface.
- * It handles the geometry regarding the dimensions of the display and the wallpaper,
- * and rescales the surface and the wallpaper accordingly.
- */
-public class ImageCanvasWallpaperRenderer {
-
- private static final String TAG = ImageCanvasWallpaperRenderer.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private SurfaceHolder mSurfaceHolder;
- //private Bitmap mBitmap = null;
-
- @VisibleForTesting
- static final int MIN_SURFACE_WIDTH = 128;
- @VisibleForTesting
- static final int MIN_SURFACE_HEIGHT = 128;
-
- private boolean mSurfaceRedrawNeeded;
-
- private int mLastSurfaceWidth = -1;
- private int mLastSurfaceHeight = -1;
-
- public ImageCanvasWallpaperRenderer(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Set the surface holder on which to draw.
- * Should be called when the surface holder is created or changed
- * @param surfaceHolder the surface on which to draw the wallpaper
- */
- public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
- mSurfaceHolder = surfaceHolder;
- }
-
- /**
- * Check if a surface holder is loaded
- * @return true if a valid surfaceHolder has been set.
- */
- public boolean isSurfaceHolderLoaded() {
- return mSurfaceHolder != null;
- }
-
- /**
- * Computes and set the surface dimensions, by using the play and the bitmap dimensions.
- * The Bitmap must be loaded before any call to this function
- */
- private boolean updateSurfaceSize(Bitmap bitmap) {
- int surfaceWidth = Math.max(MIN_SURFACE_WIDTH, bitmap.getWidth());
- int surfaceHeight = Math.max(MIN_SURFACE_HEIGHT, bitmap.getHeight());
- boolean surfaceChanged =
- surfaceWidth != mLastSurfaceWidth || surfaceHeight != mLastSurfaceHeight;
- if (surfaceChanged) {
- /*
- Used a fixed size surface, because we are special. We can do
- this because we know the current design of window animations doesn't
- cause this to break.
- */
- mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- }
- return surfaceChanged;
- }
-
- /**
- * Draw a the wallpaper on the surface.
- * The bitmap and the surface must be loaded before calling
- * this function.
- * @param forceRedraw redraw the wallpaper even if no changes are detected
- */
- public void drawFrame(Bitmap bitmap, boolean forceRedraw) {
-
- if (bitmap == null || bitmap.isRecycled()) {
- Log.e(TAG, "Attempt to draw frame before background is loaded:");
- return;
- }
-
- if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to set an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
- return;
- }
-
- mSurfaceRedrawNeeded |= forceRedraw;
- boolean surfaceChanged = updateSurfaceSize(bitmap);
-
- boolean redrawNeeded = surfaceChanged || mSurfaceRedrawNeeded;
- mSurfaceRedrawNeeded = false;
-
- if (!redrawNeeded) {
- if (DEBUG) {
- Log.d(TAG, "Suppressed drawFrame since redraw is not needed ");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Redrawing wallpaper");
- }
- drawWallpaperWithCanvas(bitmap);
- }
-
- @VisibleForTesting
- void drawWallpaperWithCanvas(Bitmap bitmap) {
- Canvas c = mSurfaceHolder.lockHardwareCanvas();
- if (c != null) {
- Rect dest = mSurfaceHolder.getSurfaceFrame();
- Log.i(TAG, "Redrawing in rect: " + dest + " with surface size: "
- + mLastSurfaceWidth + "x" + mLastSurfaceHeight);
- try {
- c.drawBitmap(bitmap, null, dest, null);
- } finally {
- mSurfaceHolder.unlockCanvasAndPost(c);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
new file mode 100644
index 0000000..e2e4555
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
@@ -0,0 +1,400 @@
+/*
+ * 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.wallpapers.canvas;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.Assert;
+import com.android.systemui.wallpapers.ImageWallpaper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is used by the {@link ImageWallpaper} to extract colors from areas of a wallpaper.
+ * It uses a background executor, and uses callbacks to inform that the work is done.
+ * It uses a downscaled version of the wallpaper to extract the colors.
+ */
+public class WallpaperColorExtractor {
+
+ private Bitmap mMiniBitmap;
+
+ @VisibleForTesting
+ static final int SMALL_SIDE = 128;
+
+ private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+ new RectF(0, 0, 1, 1);
+
+ private int mDisplayWidth = -1;
+ private int mDisplayHeight = -1;
+ private int mPages = -1;
+ private int mBitmapWidth = -1;
+ private int mBitmapHeight = -1;
+
+ private final Object mLock = new Object();
+
+ private final List<RectF> mPendingRegions = new ArrayList<>();
+ private final Set<RectF> mProcessedRegions = new ArraySet<>();
+
+ @Background
+ private final Executor mBackgroundExecutor;
+
+ private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+
+ /**
+ * Interface to handle the callbacks after the different steps of the color extraction
+ */
+ public interface WallpaperColorExtractorCallback {
+ /**
+ * Callback after the colors of new regions have been extracted
+ * @param regions the list of new regions that have been processed
+ * @param colors the resulting colors for these regions, in the same order as the regions
+ */
+ void onColorsProcessed(List<RectF> regions, List<WallpaperColors> colors);
+
+ /**
+ * Callback after the mini bitmap is computed, to indicate that the wallpaper bitmap is
+ * no longer used by the color extractor and can be safely recycled
+ */
+ void onMiniBitmapUpdated();
+
+ /**
+ * Callback to inform that the extractor has started processing colors
+ */
+ void onActivated();
+
+ /**
+ * Callback to inform that no more colors are being processed
+ */
+ void onDeactivated();
+ }
+
+ /**
+ * Creates a new color extractor.
+ * @param backgroundExecutor the executor on which the color extraction will be performed
+ * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * the color extractor.
+ */
+ public WallpaperColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ mBackgroundExecutor = backgroundExecutor;
+ mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ }
+
+ /**
+ * Used by the outside to inform that the display size has changed.
+ * The new display size will be used in the next computations, but the current colors are
+ * not recomputed.
+ */
+ public void setDisplayDimensions(int displayWidth, int displayHeight) {
+ mBackgroundExecutor.execute(() ->
+ setDisplayDimensionsSynchronized(displayWidth, displayHeight));
+ }
+
+ private void setDisplayDimensionsSynchronized(int displayWidth, int displayHeight) {
+ synchronized (mLock) {
+ if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * @return whether color extraction is currently in use
+ */
+ private boolean isActive() {
+ return mPendingRegions.size() + mProcessedRegions.size() > 0;
+ }
+
+ /**
+ * Should be called when the wallpaper is changed.
+ * This will recompute the mini bitmap
+ * and restart the extraction of all areas
+ * @param bitmap the new wallpaper
+ */
+ public void onBitmapChanged(@NonNull Bitmap bitmap) {
+ mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+ }
+
+ private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
+ synchronized (mLock) {
+ if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+ Log.e(TAG, "Attempt to extract colors from an invalid bitmap");
+ return;
+ }
+ mBitmapWidth = bitmap.getWidth();
+ mBitmapHeight = bitmap.getHeight();
+ mMiniBitmap = createMiniBitmap(bitmap);
+ mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ recomputeColors();
+ }
+ }
+
+ /**
+ * Should be called when the number of pages is changed
+ * This will restart the extraction of all areas
+ * @param pages the total number of pages of the launcher
+ */
+ public void onPageChanged(int pages) {
+ mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+ }
+
+ private void onPageChangedSynchronized(int pages) {
+ synchronized (mLock) {
+ if (mPages == pages) return;
+ mPages = pages;
+ if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
+ recomputeColors();
+ }
+ }
+ }
+
+ // helper to recompute colors, to be called in synchronized methods
+ private void recomputeColors() {
+ mPendingRegions.addAll(mProcessedRegions);
+ mProcessedRegions.clear();
+ processColorsInternal();
+ }
+
+ /**
+ * Add new regions to extract
+ * This will trigger the color extraction and call the callback only for these new regions
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+ if (regions.size() > 0) {
+ mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+ } else {
+ Log.w(TAG, "Attempt to add colors with an empty list");
+ }
+ }
+
+ private void addLocalColorsAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.addAll(regions);
+ if (!wasActive && isActive()) {
+ mWallpaperColorExtractorCallback.onActivated();
+ }
+ processColorsInternal();
+ }
+ }
+
+ /**
+ * Remove regions to extract. If a color extraction is ongoing does not stop it.
+ * But if there are subsequent changes that restart the extraction, the removed regions
+ * will not be recomputed.
+ * @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
+ */
+ public void removeLocalColorAreas(@NonNull List<RectF> regions) {
+ mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+ }
+
+ private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
+ synchronized (mLock) {
+ boolean wasActive = isActive();
+ mPendingRegions.removeAll(regions);
+ regions.forEach(mProcessedRegions::remove);
+ if (wasActive && !isActive()) {
+ mWallpaperColorExtractorCallback.onDeactivated();
+ }
+ }
+ }
+
+ /**
+ * Clean up the memory (in particular, the mini bitmap) used by this class.
+ */
+ public void cleanUp() {
+ mBackgroundExecutor.execute(this::cleanUpSynchronized);
+ }
+
+ private void cleanUpSynchronized() {
+ synchronized (mLock) {
+ if (mMiniBitmap != null) {
+ mMiniBitmap.recycle();
+ mMiniBitmap = null;
+ }
+ mProcessedRegions.clear();
+ mPendingRegions.clear();
+ }
+ }
+
+ private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
+ Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ // if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
+ int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
+ float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
+ Bitmap result = createMiniBitmap(bitmap,
+ (int) (scale * bitmap.getWidth()),
+ (int) (scale * bitmap.getHeight()));
+ Trace.endSection();
+ return result;
+ }
+
+ @VisibleForTesting
+ Bitmap createMiniBitmap(@NonNull Bitmap bitmap, int width, int height) {
+ return Bitmap.createScaledBitmap(bitmap, width, height, false);
+ }
+
+ private WallpaperColors getLocalWallpaperColors(@NonNull RectF area) {
+ RectF imageArea = pageToImgRect(area);
+ if (imageArea == null || !LOCAL_COLOR_BOUNDS.contains(imageArea)) {
+ return null;
+ }
+ Rect subImage = new Rect(
+ (int) Math.floor(imageArea.left * mMiniBitmap.getWidth()),
+ (int) Math.floor(imageArea.top * mMiniBitmap.getHeight()),
+ (int) Math.ceil(imageArea.right * mMiniBitmap.getWidth()),
+ (int) Math.ceil(imageArea.bottom * mMiniBitmap.getHeight()));
+ if (subImage.isEmpty()) {
+ // Do not notify client. treat it as too small to sample
+ return null;
+ }
+ return getLocalWallpaperColors(subImage);
+ }
+
+ @VisibleForTesting
+ WallpaperColors getLocalWallpaperColors(@NonNull Rect subImage) {
+ Assert.isNotMainThread();
+ Bitmap colorImg = Bitmap.createBitmap(mMiniBitmap,
+ subImage.left, subImage.top, subImage.width(), subImage.height());
+ return WallpaperColors.fromBitmap(colorImg);
+ }
+
+ /**
+ * Transform the logical coordinates into wallpaper coordinates.
+ *
+ * Logical coordinates are organised such that the various pages are non-overlapping. So,
+ * if there are n pages, the first page will have its X coordinate on the range [0-1/n].
+ *
+ * The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
+ * Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
+ * pages increase.
+ * If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
+ * last page is at position (1-Wr) and the others are regularly spread on the range [0-
+ * (1-Wr)].
+ */
+ private RectF pageToImgRect(RectF area) {
+ // Width of a page for the caller of this API.
+ float virtualPageWidth = 1f / (float) mPages;
+ float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
+ float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
+ int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
+
+ if (mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ Log.e(TAG, "Trying to extract colors with invalid display dimensions");
+ return null;
+ }
+
+ RectF imgArea = new RectF();
+ imgArea.bottom = area.bottom;
+ imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mBitmapHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mBitmapWidth > 0 ? mappedScreenWidth / (float) mBitmapWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
+ imgArea.left = MathUtils.constrain(
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ imgArea.right = MathUtils.constrain(
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
+ if (imgArea.left > imgArea.right) {
+ // take full page
+ imgArea.left = 0;
+ imgArea.right = 1;
+ }
+ return imgArea;
+ }
+
+ /**
+ * Extract the colors from the pending regions,
+ * then notify the callback with the resulting colors for these regions
+ * This method should only be called synchronously
+ */
+ private void processColorsInternal() {
+ /*
+ * if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
+ * called, and thus the wallpaper is not yet loaded. In that case, exit, the function
+ * will be called again when the bitmap is loaded and the miniBitmap is computed.
+ */
+ if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
+
+ /*
+ * if the screen size or number of pages is not yet known, exit
+ * the function will be called again once the screen size and page are known
+ */
+ if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
+
+ Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ List<WallpaperColors> processedColors = new ArrayList<>();
+ for (int i = 0; i < mPendingRegions.size(); i++) {
+ RectF nextArea = mPendingRegions.get(i);
+ WallpaperColors colors = getLocalWallpaperColors(nextArea);
+
+ mProcessedRegions.add(nextArea);
+ processedColors.add(colors);
+ }
+ List<RectF> processedRegions = new ArrayList<>(mPendingRegions);
+ mPendingRegions.clear();
+ Trace.endSection();
+
+ mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ }
+
+ /**
+ * Called to dump current state.
+ * @param prefix prefix.
+ * @param fd fd.
+ * @param out out.
+ * @param args args.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
+ out.print(prefix); out.print("display="); out.println(mDisplayWidth + "x" + mDisplayHeight);
+ out.print(prefix); out.print("mPages="); out.println(mPages);
+
+ out.print(prefix); out.print("bitmap dimensions=");
+ out.println(mBitmapWidth + "x" + mBitmapHeight);
+
+ out.print(prefix); out.print("bitmap=");
+ out.println(mMiniBitmap == null ? "null"
+ : mMiniBitmap.isRecycled() ? "recycled"
+ : mMiniBitmap.getWidth() + "x" + mMiniBitmap.getHeight());
+
+ out.print(prefix); out.print("PendingRegions size="); out.print(mPendingRegions.size());
+ out.print(prefix); out.print("ProcessedRegions size="); out.print(mProcessedRegions.size());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 25e7dbb..8a2c354 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -22,9 +22,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.Clock
import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -40,6 +42,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -61,17 +64,25 @@
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
- @Mock private lateinit var clock: Clock
+ @Mock private lateinit var clock: ClockController
@Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var smallClockController: ClockFaceController
+ @Mock private lateinit var largeClockController: ClockFaceController
+ @Mock private lateinit var smallClockEvents: ClockFaceEvents
+ @Mock private lateinit var largeClockEvents: ClockFaceEvents
private lateinit var clockEventController: ClockEventController
@Before
fun setUp() {
- whenever(clock.smallClock).thenReturn(TextView(context))
- whenever(clock.largeClock).thenReturn(TextView(context))
+ whenever(clock.smallClock).thenReturn(smallClockController)
+ whenever(clock.largeClock).thenReturn(largeClockController)
+ whenever(smallClockController.view).thenReturn(TextView(context))
+ whenever(largeClockController.view).thenReturn(TextView(context))
+ whenever(smallClockController.events).thenReturn(smallClockEvents)
+ whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
@@ -107,7 +118,8 @@
@Test
fun themeChanged_verifyClockPaletteUpdated() {
clockEventController.clock = clock
- verify(events).onColorPaletteChanged(any(), any(), any())
+ verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+ verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
clockEventController.registerListeners()
@@ -115,13 +127,14 @@
verify(configurationController).addCallback(capture(captor))
captor.value.onThemeChanged()
- verify(events, times(2)).onColorPaletteChanged(any(), any(), any())
+ verify(events).onColorPaletteChanged(any())
}
@Test
fun fontChanged_verifyFontSizeUpdated() {
clockEventController.clock = clock
- verify(events).onColorPaletteChanged(any(), any(), any())
+ verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+ verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
clockEventController.registerListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 635ee9e..400caa3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -45,7 +45,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
@@ -87,7 +87,7 @@
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private Clock mClock;
+ private ClockController mClock;
@Mock
DumpManager mDumpManager;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index a0295d0..254f953 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -41,7 +41,8 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
@@ -61,7 +62,13 @@
ViewGroup mMockKeyguardSliceView;
@Mock
- Clock mClock;
+ ClockController mClock;
+
+ @Mock
+ ClockFaceController mSmallClock;
+
+ @Mock
+ ClockFaceController mLargeClock;
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
@@ -75,8 +82,11 @@
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
- when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ when(mClock.getSmallClock()).thenReturn(mSmallClock);
+ when(mClock.getLargeClock()).thenReturn(mLargeClock);
+
+ when(mSmallClock.getView()).thenReturn(new TextView(getContext()));
+ when(mLargeClock.getView()).thenReturn(new TextView(getContext()));
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -124,41 +134,49 @@
public void onPluginConnected_showClock() {
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
- assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
+ assertEquals(mClock.getSmallClock().getView().getParent(), mSmallClockFrame);
+ assertEquals(mClock.getLargeClock().getView().getParent(), mLargeClockFrame);
}
@Test
public void onPluginConnected_showSecondPluginClock() {
// GIVEN a plugin has already connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ ClockController otherClock = mock(ClockController.class);
+ ClockFaceController smallClock = mock(ClockFaceController.class);
+ ClockFaceController largeClock = mock(ClockFaceController.class);
+ when(otherClock.getSmallClock()).thenReturn(smallClock);
+ when(otherClock.getLargeClock()).thenReturn(largeClock);
+ when(smallClock.getView()).thenReturn(new TextView(getContext()));
+ when(largeClock.getView()).thenReturn(new TextView(getContext()));
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
- assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ assertThat(otherClock.getSmallClock().getView().getParent()).isEqualTo(mSmallClockFrame);
+ assertThat(otherClock.getLargeClock().getView().getParent()).isEqualTo(mLargeClockFrame);
+ assertThat(mClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getView().getParent()).isNull();
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ ClockController otherClock = mock(ClockController.class);
+ ClockFaceController smallClock = mock(ClockFaceController.class);
+ ClockFaceController largeClock = mock(ClockFaceController.class);
+ when(otherClock.getSmallClock()).thenReturn(smallClock);
+ when(otherClock.getLargeClock()).thenReturn(largeClock);
+ when(smallClock.getView()).thenReturn(new TextView(getContext()));
+ when(largeClock.getView()).thenReturn(new TextView(getContext()));
mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
// THEN nothing should be shown
- assertThat(otherClock.getSmallClock().getParent()).isNull();
- assertThat(otherClock.getLargeClock().getParent()).isNull();
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ assertThat(otherClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(otherClock.getLargeClock().getView().getParent()).isNull();
+ assertThat(mClock.getSmallClock().getView().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getView().getParent()).isNull();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 43f6f1a..c1036e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -411,7 +411,7 @@
0 /* flags */);
users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */));
+ false /* isAddSupervisedUser */, null /* enforcedAdmin */));
}
return users;
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 9c64c1b..63729e3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -59,6 +59,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -79,13 +80,13 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.service.dreams.IDreamManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
import android.testing.TestableLooper;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -170,6 +171,8 @@
@Mock
private DevicePolicyManager mDevicePolicyManager;
@Mock
+ private IDreamManager mDreamManager;
+ @Mock
private KeyguardBypassController mKeyguardBypassController;
@Mock
private SubscriptionManager mSubscriptionManager;
@@ -178,6 +181,8 @@
@Mock
private TelephonyManager mTelephonyManager;
@Mock
+ private SensorPrivacyManager mSensorPrivacyManager;
+ @Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private AuthController mAuthController;
@@ -219,7 +224,6 @@
private TestableLooper mTestableLooper;
private Handler mHandler;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private TestableContext mSpiedContext;
private MockitoSession mMockitoSession;
private StatusBarStateController.StateListener mStatusBarStateListener;
private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
@@ -228,9 +232,6 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mSpiedContext = spy(mContext);
- when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
- when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -279,14 +280,6 @@
.thenReturn(new ServiceState());
when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
- mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
- mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
- mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
- mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager);
- mSpiedContext.addMockSystemService(UserManager.class, mUserManager);
- mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
- mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
- mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class)
@@ -301,7 +294,7 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
.registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -356,7 +349,7 @@
when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
- KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mContext);
mTestableLooper.processAllMessages();
@@ -614,7 +607,7 @@
public void testTriesToAuthenticate_whenKeyguard() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -624,7 +617,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -637,7 +630,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -661,7 +654,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
// Stop scanning when bouncer becomes visible
@@ -675,7 +668,7 @@
@Test
public void testTriesToAuthenticate_whenAssistant() {
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -690,7 +683,7 @@
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -700,7 +693,7 @@
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -712,7 +705,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -724,7 +717,7 @@
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
@@ -745,7 +738,7 @@
public void testFaceAndFingerprintLockout_onlyFace() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
@@ -756,7 +749,7 @@
public void testFaceAndFingerprintLockout_onlyFingerprint() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -768,7 +761,7 @@
public void testFaceAndFingerprintLockout() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
faceAuthLockedOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
@@ -867,7 +860,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1040,8 +1033,7 @@
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// THEN we shouldn't listen for fingerprints
@@ -1056,8 +1048,7 @@
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mKeyguardUpdateMonitor.setKeyguardOccluded(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
mKeyguardUpdateMonitor.requestFingerprintAuthOnOccludingApp(true);
@@ -1149,7 +1140,7 @@
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
// WHEN status bar state reports a change to the keyguard that would normally indicate to
// start running face auth
@@ -1160,8 +1151,9 @@
// listening state to update
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
- // WHEN biometric listening state is updated
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ // WHEN biometric listening state is updated when showing state changes from false => true
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
// THEN face unlock is running
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(true);
@@ -1202,9 +1194,9 @@
@Test
public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
cleanupKeyguardUpdateMonitor();
- mSpiedContext.addMockSystemService(FaceManager.class, null);
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ mFaceManager = null;
+
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -1258,7 +1250,7 @@
// This disables face auth
when(mUserManager.isPrimaryUser()).thenReturn(false);
mKeyguardUpdateMonitor =
- new TestableKeyguardUpdateMonitor(mSpiedContext);
+ new TestableKeyguardUpdateMonitor(mContext);
// Face auth should run when the following is true.
keyguardNotGoingAway();
@@ -1482,6 +1474,27 @@
}
@Test
+ public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+ throws RemoteException {
+ // Preconditions for face auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ deviceNotGoingToSleep();
+ mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ deviceGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
@@ -1506,7 +1519,7 @@
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
mTestableLooper.processAllMessages();
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
@@ -1515,7 +1528,7 @@
mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
// Make sure keyguard is going away after face auth attempt, and that it calls
// updateBiometricStateListeningState.
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false);
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
mTestableLooper.processAllMessages();
verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
@@ -1527,15 +1540,16 @@
verify(mHandler, times(1)).removeCallbacks(mKeyguardUpdateMonitor.mFpCancelNotReceived);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
- assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
}
@Test
public void testFingerAcquired_wakesUpPowerManager() {
cleanupKeyguardUpdateMonitor();
- mSpiedContext.getOrCreateTestableResources().addOverride(
+ mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.kg_wake_on_acquire_start, true);
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
fingerprintAcquireStart();
verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
@@ -1544,9 +1558,9 @@
@Test
public void testFingerAcquired_doesNotWakeUpPowerManager() {
cleanupKeyguardUpdateMonitor();
- mSpiedContext.getOrCreateTestableResources().addOverride(
+ mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.kg_wake_on_acquire_start, false);
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
fingerprintAcquireStart();
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -1574,7 +1588,7 @@
}
private void keyguardIsVisible() {
- mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
}
private void triggerAuthInterrupt() {
@@ -1667,6 +1681,10 @@
mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
}
+ private void deviceGoingToSleep() {
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
+ }
+
private void deviceIsInteractive() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
}
@@ -1716,7 +1734,9 @@
mAuthController, mTelephonyListenerManager,
mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
- mPowerManager);
+ mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
+ mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
+ mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 8fc0489..986e7cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -718,7 +718,7 @@
}
@Test
- fun animatesViewRemovalFromStartToEnd() {
+ fun animatesViewRemovalFromStartToEnd_viewHasSiblings() {
setUpRootWithChildren()
val child = rootView.getChildAt(0)
@@ -742,6 +742,35 @@
}
@Test
+ fun animatesViewRemovalFromStartToEnd_viewHasNoSiblings() {
+ rootView = LinearLayout(mContext)
+ (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
+ (rootView as LinearLayout).weightSum = 1f
+
+ val onlyChild = View(mContext)
+ rootView.addView(onlyChild)
+ forceLayout()
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ onlyChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ interpolator = Interpolators.LINEAR
+ )
+
+ assertTrue(success)
+ assertNotNull(onlyChild.getTag(R.id.tag_animator))
+ checkBounds(onlyChild, l = 0, t = 0, r = 200, b = 100)
+ advanceAnimation(onlyChild, 0.5f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 100, b = 100)
+ advanceAnimation(onlyChild, 1.0f)
+ checkBounds(onlyChild, l = 0, t = 0, r = 0, b = 100)
+ endAnimation(rootView)
+ endAnimation(onlyChild)
+ assertEquals(0, rootView.childCount)
+ assertFalse(onlyChild in rootView.children)
+ }
+
+ @Test
fun animatesViewRemovalRespectingDestination() {
// CENTER
setUpRootWithChildren()
@@ -964,6 +993,60 @@
}
@Test
+ fun animateRemoval_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ endAnimation(removedView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ cancelAnimation(removedView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationRemoval_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ setUpRootWithChildren()
+ forceLayout()
+ val removedView = rootView.getChildAt(0)
+
+ ViewHierarchyAnimator.animateRemoval(
+ removedView,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ advanceAnimation(removedView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun cleansUpListenersCorrectly() {
val firstChild = View(mContext)
firstChild.layoutParams = LinearLayout.LayoutParams(50 /* width */, 100 /* height */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 4a5b23c..d52612b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -322,6 +322,13 @@
}
@Test
+ fun testLayoutParams_hasShowWhenLockedFlag() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
+ .isTrue()
+ }
+
+ @Test
fun testLayoutParams_hasDimbehindWindowFlag() {
val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
val lpFlags = layoutParams.flags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 37bb0c2..0b528a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -117,13 +117,12 @@
}
@Test
- fun testFingerprintTrigger_KeyguardVisible_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
+ fun testFingerprintTrigger_KeyguardShowing_Ripple() {
+ // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN fingerprint authenticated
@@ -140,39 +139,15 @@
}
@Test
- fun testFingerprintTrigger_Dreaming_Ripple() {
- // GIVEN fp exists, keyguard is visible, user doesn't need strong auth
- val fpsLocation = Point(5, 5)
- `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
- controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
-
- // WHEN fingerprint authenticated
- val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onBiometricAuthenticated(
- 0 /* userId */,
- BiometricSourceType.FINGERPRINT /* type */,
- false /* isStrongBiometric */)
-
- // THEN update sensor location and show ripple
- verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
- verify(rippleView).startUnlockedRipple(any())
- }
-
- @Test
- fun testFingerprintTrigger_KeyguardNotVisible_NotDreaming_NoRipple() {
+ fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
// GIVEN fp exists & user doesn't need strong auth
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
- // WHEN keyguard is NOT visible & fingerprint authenticated
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false)
- `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ // WHEN keyguard is NOT showing & fingerprint authenticated
+ `when`(keyguardStateController.isShowing).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -186,11 +161,11 @@
@Test
fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
- // GIVEN fp exists & keyguard is visible
+ // GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
// WHEN user needs strong auth & fingerprint authenticated
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
@@ -207,12 +182,12 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is visible & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
// WHEN bypass is enabled & face authenticated
@@ -299,7 +274,7 @@
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -317,7 +292,7 @@
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 434cb48..25bc91f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -96,7 +96,7 @@
@Mock
private lateinit var removalPendingStore: PendingRemovalStore
- private lateinit var executor: Executor
+ private lateinit var mainExecutor: Executor
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
@@ -108,11 +108,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- executor = FakeExecutor(FakeSystemClock())
- `when`(mockContext.mainExecutor).thenReturn(executor)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ `when`(mockContext.mainExecutor).thenReturn(mainExecutor)
broadcastDispatcher = TestBroadcastDispatcher(
mockContext,
+ mainExecutor,
testableLooper.looper,
mock(Executor::class.java),
mock(DumpManager::class.java),
@@ -148,9 +149,9 @@
@Test
fun testAddingReceiverToCorrectUBR_executor() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mainExecutor, user0)
broadcastDispatcher.registerReceiver(
- broadcastReceiverOther, intentFilterOther, executor, user1)
+ broadcastReceiverOther, intentFilterOther, mainExecutor, user1)
testableLooper.processAllMessages()
@@ -427,8 +428,9 @@
private class TestBroadcastDispatcher(
context: Context,
- bgLooper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ backgroundRunningLooper: Looper,
+ backgroundRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker,
@@ -436,8 +438,9 @@
var mockUBRMap: Map<Int, UserBroadcastDispatcher>
) : BroadcastDispatcher(
context,
- bgLooper,
- executor,
+ mainExecutor,
+ backgroundRunningLooper,
+ backgroundRunningExecutor,
dumpManager,
logger,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
index d70d6fc..588edb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
@@ -19,6 +19,7 @@
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
import static com.android.systemui.classifier.Classifier.PULSE_EXPAND;
@@ -406,4 +407,46 @@
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(QS_SWIPE_NESTED, 0.5, 0).isFalse()).isTrue();
}
+
+ @Test
+ public void testPass_MediaSeekbar() {
+ when(mDataProvider.isVertical()).thenReturn(false);
+
+ when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(false);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isFalse();
+ }
+
+ @Test
+ public void testFalse_MediaSeekbar() {
+ when(mDataProvider.isVertical()).thenReturn(true);
+
+ when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(false);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(false);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+
+ when(mDataProvider.isUp()).thenReturn(true);
+ when(mDataProvider.isRight()).thenReturn(true);
+ assertThat(mClassifier.classifyGesture(MEDIA_SEEKBAR, 0.5, 0).isFalse()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6436981..781dc15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -144,6 +144,12 @@
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
+ ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doAnswer(invocation ->
+ when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+ ).when(mHost).setPulsePending(boolCaptor.capture());
+
+ when(mHost.isPulsingBlocked()).thenReturn(false);
mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
@@ -160,6 +166,29 @@
}
@Test
+ public void testOnNotification_noPulseIfPulseIsNotPendingAnymore() {
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+ doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
+
+ mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+ clearInvocations(mMachine);
+ when(mHost.isPulsingBlocked()).thenReturn(false);
+
+ // GIVEN pulsePending = false
+ when(mHost.isPulsePending()).thenReturn(false);
+
+ // WHEN prox check returns FAR
+ mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
+ mProximitySensor.alertListeners();
+
+ // THEN don't request pulse because the pending pulse was abandoned early
+ verify(mMachine, never()).requestPulse(anyInt());
+ }
+
+ @Test
public void testTransitionTo_disablesAndEnablesTouchSensors() {
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
@@ -237,6 +266,11 @@
when(mSessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD))
.thenReturn(keyguardSessionId);
+ ArgumentCaptor<Boolean> boolCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doAnswer(invocation ->
+ when(mHost.isPulsePending()).thenReturn(boolCaptor.getValue())
+ ).when(mHost).setPulsePending(boolCaptor.capture());
+
// WHEN quick pick up is triggered
mTriggers.onSensor(DozeLog.REASON_SENSOR_QUICK_PICKUP, 100, 100, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 2448f1a..849ac5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -297,10 +297,10 @@
}
/**
- * Ensures margin is applied
+ * Ensures default margin is applied
*/
@Test
- public void testMargin() {
+ public void testDefaultMargin() {
final int margin = 5;
final ComplicationLayoutEngine engine =
new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0);
@@ -373,6 +373,74 @@
}
/**
+ * Ensures complication margin is applied
+ */
+ @Test
+ public void testComplicationMargin() {
+ final int defaultMargin = 5;
+ final int complicationMargin = 10;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ complicationMargin),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, firstViewInfo);
+
+ final ViewInfo secondViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 0),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, secondViewInfo);
+
+ firstViewInfo.clearInvocations();
+ secondViewInfo.clearInvocations();
+
+ final ViewInfo thirdViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 1),
+ Complication.CATEGORY_SYSTEM,
+ mLayout);
+
+ addComplication(engine, thirdViewInfo);
+
+ // The first added view should now be underneath the second view.
+ verifyChange(firstViewInfo, false, lp -> {
+ assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.topMargin).isEqualTo(complicationMargin);
+ });
+
+ // The second view should be in underneath the third view.
+ verifyChange(secondViewInfo, false, lp -> {
+ assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.getMarginEnd()).isEqualTo(defaultMargin);
+ });
+ }
+
+ /**
* Ensures layout in a particular position updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index 967b30d..cb7e47b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -97,6 +97,35 @@
}
/**
+ * Ensures unspecified margin uses default.
+ */
+ @Test
+ public void testUnspecifiedMarginUsesDefault() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3);
+ assertThat(params.getMargin(10) == 10).isTrue();
+ }
+
+ /**
+ * Ensures specified margin is used instead of default.
+ */
+ @Test
+ public void testSpecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ assertThat(params.getMargin(5) == 10).isTrue();
+ }
+
+ /**
* Ensures ComplicationLayoutParams is properly duplicated on copy construction.
*/
@Test
@@ -106,12 +135,36 @@
100,
ComplicationLayoutParams.POSITION_TOP,
ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+ assertThat(copy.getDirection() == params.getDirection()).isTrue();
+ assertThat(copy.getPosition() == params.getPosition()).isTrue();
+ assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+ assertThat(copy.height == params.height).isTrue();
+ assertThat(copy.width == params.width).isTrue();
+ }
+
+ /**
+ * Ensures ComplicationLayoutParams is properly duplicated on copy construction with unspecified
+ * margin.
+ */
+ @Test
+ public void testCopyConstructionWithUnspecifiedMargin() {
+ final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
3);
final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
assertThat(copy.getDirection() == params.getDirection()).isTrue();
assertThat(copy.getPosition() == params.getPosition()).isTrue();
assertThat(copy.getWeight() == params.getWeight()).isTrue();
+ assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue();
assertThat(copy.height == params.height).isTrue();
assertThat(copy.width == params.width).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 04ff7ae..db6082d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -29,9 +29,12 @@
import android.content.Context;
import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.ImageView;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
@@ -40,6 +43,7 @@
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
import org.junit.Before;
import org.junit.Test;
@@ -79,6 +83,15 @@
@Captor
private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
+ @Mock
+ private ImageView mView;
+
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -151,6 +164,30 @@
verify(mDreamOverlayStateController).addComplication(mComplication);
}
+ /**
+ * Ensures clicking home controls chip logs UiEvent.
+ */
+ @Test
+ public void testClick_logsUiEvent() {
+ final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+ new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+ mView,
+ mActivityStarter,
+ mContext,
+ mControlsComponent,
+ mUiEventLogger);
+ viewController.onViewAttached();
+
+ final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+ clickListenerCaptor.getValue().onClick(mView);
+ verify(mUiEventLogger).log(
+ DreamHomeControlsComplication.DreamHomeControlsChipViewController
+ .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+ }
+
private void setHaveFavorites(boolean value) {
final List<StructureInfo> favorites = mock(List.class);
when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 4511193..20a82c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -21,15 +21,10 @@
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
@@ -46,18 +41,16 @@
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
-import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
/**
- * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
- * overriding, and should never return any value other than the one provided as the default.
+ * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
+ * the default.
*/
@SmallTest
class FeatureFlagsDebugTest : SysuiTestCase() {
@@ -68,10 +61,8 @@
@Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources
- @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var commandRegistry: CommandRegistry
- @Mock private lateinit var barService: IStatusBarService
- @Mock private lateinit var pw: PrintWriter
+ @Mock private lateinit var restarter: Restarter
private val flagMap = mutableMapOf<Int, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int>
@@ -92,12 +83,10 @@
secureSettings,
systemProperties,
resources,
- dumpManager,
deviceConfig,
serverFlagReader,
flagMap,
- commandRegistry,
- barService
+ restarter
)
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
@@ -366,53 +355,6 @@
}
@Test
- fun statusBarCommand_IsRegistered() {
- verify(commandRegistry).registerCommand(anyString(), any())
- }
-
- @Test
- fun noOpCommand() {
- val cmd = captureCommand()
-
- cmd.execute(pw, ArrayList())
- verify(pw, atLeastOnce()).println()
- verify(flagManager).readFlagValue<Boolean>(eq(1), any())
- verifyZeroInteractions(secureSettings)
- }
-
- @Test
- fun readFlagCommand() {
- addFlag(UnreleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1"))
- verify(flagManager).readFlagValue<Boolean>(eq(1), any())
- }
-
- @Test
- fun setFlagCommand() {
- addFlag(UnreleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "on"))
- verifyPutData(1, "{\"type\":\"boolean\",\"value\":true}")
- }
-
- @Test
- fun toggleFlagCommand() {
- addFlag(ReleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "toggle"))
- verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}", 2)
- }
-
- @Test
- fun eraseFlagCommand() {
- addFlag(ReleasedFlag(1))
- val cmd = captureCommand()
- cmd.execute(pw, listOf("1", "erase"))
- verify(secureSettings).putStringForUser(eq("key-1"), eq(""), anyInt())
- }
-
- @Test
fun dumpFormat() {
val flag1 = ReleasedFlag(1)
val flag2 = ResourceBooleanFlag(2, 1002)
@@ -471,13 +413,6 @@
return flag
}
- private fun captureCommand(): Command {
- val captor = argumentCaptor<Function0<Command>>()
- verify(commandRegistry).registerCommand(anyString(), capture(captor))
-
- return captor.value.invoke()
- }
-
private fun dumpToString(): String {
val sw = StringWriter()
val pw = PrintWriter(sw)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index e94b520..575c142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,17 +19,12 @@
import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxyFake
-import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
-import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -43,7 +38,6 @@
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
- @Mock private lateinit var mDumpManager: DumpManager
private val serverFlagReader = ServerFlagReaderFake()
private val deviceConfig = DeviceConfigProxyFake()
@@ -55,15 +49,7 @@
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader,
- mDumpManager)
- }
-
- @After
- fun onFinished() {
- // The dump manager should be registered with even for the release version, but that's it.
- verify(mDumpManager).registerDumpable(any(), any())
- verifyNoMoreInteractions(mDumpManager)
+ serverFlagReader)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
new file mode 100644
index 0000000..4c61138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class FlagCommandTest : SysuiTestCase() {
+
+ @Mock private lateinit var featureFlags: FeatureFlagsDebug
+ @Mock private lateinit var pw: PrintWriter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
+ private val flagA = UnreleasedFlag(500)
+ private val flagB = ReleasedFlag(501)
+
+ private lateinit var cmd: FlagCommand
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
+ whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
+ flagMap.put(flagA.id, flagA)
+ flagMap.put(flagB.id, flagB)
+
+ cmd = FlagCommand(featureFlags, flagMap)
+ }
+
+ @Test
+ fun noOpCommand() {
+ cmd.execute(pw, ArrayList())
+ Mockito.verify(pw, Mockito.atLeastOnce()).println()
+ Mockito.verify(featureFlags).isEnabled(flagA)
+ Mockito.verify(featureFlags).isEnabled(flagB)
+ }
+
+ @Test
+ fun readFlagCommand() {
+ cmd.execute(pw, listOf(flagA.id.toString()))
+ Mockito.verify(featureFlags).isEnabled(flagA)
+ }
+
+ @Test
+ fun setFlagCommand() {
+ cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+ Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
+ }
+
+ @Test
+ fun toggleFlagCommand() {
+ cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+ Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
+ }
+
+ @Test
+ fun eraseFlagCommand() {
+ cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+ Mockito.verify(featureFlags).eraseFlag(flagA)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836..7f55d38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@
import org.junit.Rule;
import org.junit.Test;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -148,8 +149,12 @@
return false;
}
- private static void executeShellCommand(String cmd) {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+ private void executeShellCommand(String cmd) {
+ try {
+ runShellCommand(cmd);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index ba1e168..7a15680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -116,6 +116,7 @@
val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
verify(keyguardStateController).addCallback(captor.capture())
@@ -123,10 +124,12 @@
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
assertThat(latest).isTrue()
+ assertThat(underTest.isKeyguardShowing()).isTrue()
whenever(keyguardStateController.isShowing).thenReturn(false)
captor.value.onKeyguardShowingChanged()
assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
job.cancel()
}
@@ -150,6 +153,21 @@
}
@Test
+ fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+ var latest: Boolean? = null
+
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
+
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
+
+ @Test
fun dozeAmount() = runBlockingTest {
val values = mutableListOf<Float>()
val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index d4fba41..329c4db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -138,7 +138,7 @@
assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
assertThat(visibleState.icon).isNotNull()
- assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+ assertThat(visibleState.icon.contentDescription).isNotNull()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 5a3a78e..0a4478f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -21,9 +21,11 @@
import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -69,8 +71,16 @@
val job = underTest.state.onEach { latest = it }.launchIn(this)
val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
- assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
- assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+ assertThat(visibleModel.icon)
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = ICON,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ )
+ )
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c5e828e..b6d7559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -21,7 +21,8 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -34,6 +35,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -46,7 +48,6 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -55,7 +56,15 @@
companion object {
private val INTENT = Intent("some.intent.action")
- private val DRAWABLE = mock<ContainedDrawable>()
+ private val DRAWABLE =
+ mock<Icon> {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
@Parameters(
@@ -236,7 +245,6 @@
state =
KeyguardQuickAffordanceConfig.State.Visible(
icon = DRAWABLE,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
homeControls.onClickedResult =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 19d8412..1dd919a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,7 +19,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -32,6 +33,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -101,7 +103,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -120,8 +121,8 @@
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
assertThat(visibleModel.configKey).isEqualTo(configKey)
assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.contentDescriptionResourceId)
- .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
job.cancel()
}
@@ -131,7 +132,6 @@
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -150,8 +150,8 @@
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
assertThat(visibleModel.configKey).isEqualTo(configKey)
assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.contentDescriptionResourceId)
- .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
job.cancel()
}
@@ -161,7 +161,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -182,7 +181,6 @@
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
@@ -197,7 +195,14 @@
}
companion object {
- private val ICON: ContainedDrawable = mock()
+ private val ICON: Icon = mock {
+ whenever(this.contentDescription)
+ .thenReturn(
+ ContentDescription.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ }
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index c612091..96544e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -505,7 +505,6 @@
}
KeyguardQuickAffordanceConfig.State.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
} else {
KeyguardQuickAffordanceConfig.State.Hidden
@@ -543,7 +542,7 @@
private data class TestConfig(
val isVisible: Boolean,
val isClickable: Boolean = false,
- val icon: ContainedDrawable? = null,
+ val icon: Icon? = null,
val canShowWhileLocked: Boolean = false,
val intent: Intent? = null,
) {
@@ -555,6 +554,5 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
- private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 82aa612..5973340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -26,13 +26,13 @@
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.concurrency.FakeRepeatableExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -47,8 +47,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
}
@Mock private lateinit var mockController: MediaController
@Mock private lateinit var mockTransport: MediaController.TransportControls
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var mockBar: SeekBar
private val token1 = MediaSession.Token(1, null)
private val token2 = MediaSession.Token(2, null)
@@ -78,9 +80,10 @@
@Before
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
- viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
+ viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
viewModel.logSeek = { }
whenever(mockController.sessionToken).thenReturn(token1)
+ whenever(mockBar.context).thenReturn(context)
// LiveData to run synchronously
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
@@ -454,6 +457,25 @@
}
@Test
+ fun onFalseTapOrTouch() {
+ whenever(mockController.getTransportControls()).thenReturn(mockTransport)
+ whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
+ whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ viewModel.updateController(mockController)
+ val pos = 169
+
+ viewModel.attachTouchHandlers(mockBar)
+ with(viewModel.seekBarListener) {
+ onStartTrackingTouch(mockBar)
+ onProgressChanged(mockBar, pos, true)
+ onStopTrackingTouch(mockBar)
+ }
+
+ // THEN transport controls should not be used
+ verify(mockTransport, never()).seekTo(pos.toLong())
+ }
+
+ @Test
fun queuePollTaskWhenPlaying() {
// GIVEN that the track is playing
val state = PlaybackState.Builder().run {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 37f6434..7c83cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -19,9 +19,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -90,48 +88,6 @@
assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
}
-
- @Test
- fun setIcon_viewHasIconAndContentDescription() {
- val view = CachingIconView(context)
- val icon = context.getDrawable(R.drawable.ic_celebration)!!
- val contentDescription = "Happy birthday!"
-
- MediaTttUtils.setIcon(view, icon, contentDescription)
-
- assertThat(view.drawable).isEqualTo(icon)
- assertThat(view.contentDescription).isEqualTo(contentDescription)
- }
-
- @Test
- fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")
-
- assertThat(view.layoutParams.width).isEqualTo(size)
- assertThat(view.layoutParams.height).isEqualTo(size)
- }
-
- @Test
- fun setIcon_iconSizeProvided_viewSizeUpdates() {
- val view = CachingIconView(context)
- val size = 456
- view.layoutParams = FrameLayout.LayoutParams(size, size)
-
- val newSize = 40
- MediaTttUtils.setIcon(
- view,
- context.getDrawable(R.drawable.ic_cake)!!,
- "desc",
- iconSize = newSize
- )
-
- assertThat(view.layoutParams.width).isEqualTo(newSize)
- assertThat(view.layoutParams.height).isEqualTo(newSize)
- }
}
private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index d41ad48..f7b3091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -41,6 +41,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -71,6 +72,8 @@
@Mock
private lateinit var powerManager: PowerManager
@Mock
+ private lateinit var viewUtil: ViewUtil
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
@@ -104,7 +107,8 @@
configurationController,
powerManager,
Handler.getMain(),
- receiverUiEventLogger
+ receiverUiEventLogger,
+ viewUtil,
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -212,35 +216,27 @@
}
@Test
- fun updateView_isAppIcon_usesAppIconSize() {
+ fun updateView_isAppIcon_usesAppIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0)
}
@Test
- fun updateView_notAppIcon_usesGenericIconSize() {
+ fun updateView_notAppIcon_usesGenericIconPadding() {
controllerReceiver.displayView(getChipReceiverInfo(packageName = null))
+
val chipView = getChipView()
-
- chipView.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- val expectedSize =
- context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver)
- assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
- assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
+ val expectedPadding =
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding)
+ assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding)
+ assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ff0faf9..213b74a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
+import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
@@ -35,18 +36,25 @@
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -57,7 +65,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class MediaTttChipControllerSenderTest : SysuiTestCase() {
- private lateinit var controllerSender: MediaTttChipControllerSender
+ private lateinit var controllerSender: TestMediaTttChipControllerSender
@Mock
private lateinit var packageManager: PackageManager
@@ -75,6 +83,16 @@
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
+ @Mock
+ private lateinit var lazyFalsingManager: Lazy<FalsingManager>
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+ @Mock
+ private lateinit var lazyFalsingCollector: Lazy<FalsingCollector>
+ @Mock
+ private lateinit var falsingCollector: FalsingCollector
+ @Mock
+ private lateinit var viewUtil: ViewUtil
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var fakeClock: FakeSystemClock
@@ -101,8 +119,10 @@
senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+ whenever(lazyFalsingManager.get()).thenReturn(falsingManager)
+ whenever(lazyFalsingCollector.get()).thenReturn(falsingCollector)
- controllerSender = MediaTttChipControllerSender(
+ controllerSender = TestMediaTttChipControllerSender(
commandQueue,
context,
logger,
@@ -111,7 +131,10 @@
accessibilityManager,
configurationController,
powerManager,
- senderUiEventLogger
+ senderUiEventLogger,
+ lazyFalsingManager,
+ lazyFalsingCollector,
+ viewUtil,
)
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
@@ -417,6 +440,38 @@
}
@Test
+ fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
+ whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(true)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isFalse()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
+ whenever(lazyFalsingManager.get().isFalseTap(anyInt())).thenReturn(false)
+ var undoCallbackCalled = false
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ }
+
+ controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
+ getChipView().getUndoButton().performClick()
+
+ assertThat(undoCallbackCalled).isTrue()
+ }
+
+ @Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
@@ -773,6 +828,39 @@
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceFailed() =
ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
+
+ private class TestMediaTttChipControllerSender(
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ uiEventLogger: MediaTttSenderUiEventLogger,
+ falsingManager: Lazy<FalsingManager>,
+ falsingCollector: Lazy<FalsingCollector>,
+ viewUtil: ViewUtil,
+ ) : MediaTttChipControllerSender(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ uiEventLogger,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+ }
}
private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 00b1f32..19d2d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -25,6 +25,7 @@
private val controller = MediaProjectionAppSelectorController(
taskListProvider,
+ view,
scope,
appSelectorComponentName
)
@@ -33,7 +34,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init(view)
+ controller.init()
verify(view).bind(emptyList())
}
@@ -44,7 +45,7 @@
createRecentTask(taskId = 1)
)
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -62,7 +63,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
@@ -84,7 +85,7 @@
)
taskListProvider.tasks = tasks
- controller.init(view)
+ controller.init()
verify(view).bind(
listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
new file mode 100644
index 0000000..464acb6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.mediaprojection.appselector.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Rect
+import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TaskPreviewSizeProviderTest : SysuiTestCase() {
+
+ private val mockContext: Context = mock()
+ private val resources: Resources = mock()
+ private val windowManager: WindowManager = mock()
+ private val sizeUpdates = arrayListOf<Rect>()
+ private val testConfigurationController = FakeConfigurationController()
+
+ @Before
+ fun setup() {
+ whenever(mockContext.getSystemService(eq(WindowManager::class.java)))
+ .thenReturn(windowManager)
+ whenever(mockContext.resources).thenReturn(resources)
+ }
+
+ @Test
+ fun size_phoneDisplay_thumbnailSizeIsSmallerAndProportionalToTheScreenSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 100, 150))
+ }
+
+ @Test
+ fun size_tabletDisplay_thumbnailSizeProportionalToTheScreenSizeExcludingTaskbar() {
+ givenDisplay(width = 400, height = 600, isTablet = true)
+ givenTaskbarSize(20)
+
+ val size = createSizeProvider().size
+
+ assertThat(size).isEqualTo(Rect(0, 0, 97, 140))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeUpdates).containsExactly(Rect(0, 0, 150, 100))
+ }
+
+ @Test
+ fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
+ givenDisplay(width = 400, height = 600, isTablet = false)
+ val sizeProvider = createSizeProvider()
+
+ givenDisplay(width = 600, height = 400, isTablet = false)
+ testConfigurationController.onConfigurationChanged(Configuration())
+
+ assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
+ }
+
+ private fun givenTaskbarSize(size: Int) {
+ whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+ }
+
+ private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
+ val bounds = Rect(0, 0, width, height)
+ val windowMetrics = WindowMetrics(bounds, null)
+ whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+ whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
+
+ val minDimension = min(width, height)
+
+ // Calculate DPI so the smallest width is either considered as tablet or as phone
+ val targetSmallestWidthDpi =
+ if (isTablet) SMALLEST_WIDTH_DPI_TABLET else SMALLEST_WIDTH_DPI_PHONE
+ val densityDpi = minDimension * DENSITY_DEFAULT / targetSmallestWidthDpi
+
+ val configuration = Configuration(context.resources.configuration)
+ configuration.densityDpi = densityDpi
+ whenever(resources.configuration).thenReturn(configuration)
+ }
+
+ private fun createSizeProvider(): TaskPreviewSizeProvider {
+ val listener =
+ object : TaskPreviewSizeListener {
+ override fun onTaskSizeChanged(size: Rect) {
+ sizeUpdates.add(size)
+ }
+ }
+
+ return TaskPreviewSizeProvider(mockContext, windowManager, testConfigurationController)
+ .also { it.addCallback(listener) }
+ }
+
+ private companion object {
+ private const val SMALLEST_WIDTH_DPI_TABLET = 800
+ private const val SMALLEST_WIDTH_DPI_PHONE = 400
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 5d5918d..d2c2d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -14,6 +14,9 @@
package com.android.systemui.qs;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
@@ -49,13 +52,13 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -93,7 +96,7 @@
@Mock private QSPanel.QSTileLayout mQsTileLayout;
@Mock private QSPanel.QSTileLayout mQQsTileLayout;
@Mock private QSAnimator mQSAnimator;
- @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private QSSquishinessController mSquishinessController;
private View mQsFragmentView;
@@ -158,7 +161,7 @@
public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
QSFragment fragment = resumeAndGetFragment();
- setStatusBarState(StatusBarState.KEYGUARD);
+ setStatusBarState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -174,7 +177,7 @@
public void
transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
QSFragment fragment = resumeAndGetFragment();
- setStatusBarState(StatusBarState.KEYGUARD);
+ setStatusBarState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -262,6 +265,27 @@
}
@Test
+ public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() {
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ boolean isTransitioningToFullShade = false;
+ float transitionProgress = 0;
+ float squishinessFraction = 0f;
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
+ // trigger alpha refresh with non-zero expansion and fraction values
+ fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1,
+ /* proposedTranslation= */ 0, /* squishinessFraction= */ 1);
+
+ // alpha should follow lockscreen to shade progress, not panel expansion fraction
+ assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress);
+ }
+
+ @Test
public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
@@ -402,6 +426,19 @@
verify(mQSPanelController).setListening(eq(true), anyBoolean());
}
+ @Test
+ public void passCorrectExpansionState_inSplitShade() {
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+ clearInvocations(mQSPanelController);
+
+ fragment.setExpanded(true);
+ verify(mQSPanelController).setExpanded(true);
+
+ fragment.setExpanded(false);
+ verify(mQSPanelController).setExpanded(false);
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 3cad2a0..b847ad0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -277,7 +277,7 @@
// Then the layout changes
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
- verify(mHorizontalLayoutListener).run(); // not invoked
+ verify(mHorizontalLayoutListener).run();
// When it is rotated back to portrait
mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
@@ -300,4 +300,24 @@
verify(mQSTile).refreshState();
verify(mOtherTile, never()).refreshState();
}
+
+ @Test
+ public void configurationChange_onlySplitShadeConfigChanges_horizontalLayoutStatusUpdated() {
+ // Preconditions for horizontal layout
+ when(mMediaHost.getVisible()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ reset(mHorizontalLayoutListener);
+
+ // Only split shade status changes
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Horizontal layout is updated accordingly.
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener).run();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5eb9a98..e539705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,6 +6,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -52,6 +53,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private lateinit var controller: QSPanelController
@@ -82,7 +84,8 @@
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
- statusBarKeyguardViewManager
+ statusBarKeyguardViewManager,
+ featureFlags
)
}
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 2db58be..7c930b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -159,6 +159,32 @@
}
@Test
+ fun testTopPadding_notCombinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(false)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(padding)
+ }
+
+ @Test
+ fun testTopPadding_combinedHeaders() {
+ qsPanel.setUsingCombinedHeaders(true)
+ val padding = 10
+ val paddingCombined = 100
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_panel_padding_top_combined_headers, paddingCombined)
+
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
+ }
+
+ @Test
fun testSetSquishinessFraction_noCrash() {
qsPanel.addView(qsPanel.mTileLayout as View, 0)
qsPanel.addView(FrameLayout(context))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 7d56339..4c44dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -33,6 +33,7 @@
import android.graphics.Paint;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.provider.MediaStore;
import android.testing.AndroidTestingRunner;
@@ -97,7 +98,8 @@
Bitmap original = createCheckerBitmap(10, 10, 10);
ListenableFuture<ImageExporter.Result> direct =
- exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME);
+ exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME,
+ Process.myUserHandle());
assertTrue("future should be done", direct.isDone());
assertFalse("future should not be canceled", direct.isCancelled());
ImageExporter.Result result = direct.get();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 073c23c..5cb27a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
@@ -140,66 +139,6 @@
}
@Test
- fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- val processedRequest = processor.process(request)
-
- // No changes
- assertThat(processedRequest).isEqualTo(request)
- }
-
- @Test
- fun testSelectedRegionScreenshot() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- policy.setManagedProfile(USER_ID, false)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
-
- val processedRequest = processor.process(request)
-
- // Request has topComponent added, but otherwise unchanged.
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
- assertThat(processedRequest.topComponent).isEqualTo(component)
- }
-
- @Test
- fun testSelectedRegionScreenshot_managedProfile() = runBlocking {
- flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
-
- // Provide a fake task bitmap when asked
- val bitmap = makeHardwareBitmap(100, 100)
- imageCapture.image = bitmap
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- // Indicate that the primary content belongs to a manged profile
- policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
-
- val processedRequest = processor.process(request)
-
- // Expect a task snapshot is taken, overriding the selected region mode
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
- assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
- assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
- assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
- assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
- assertThat(processedRequest.userId).isEqualTo(USER_ID)
- assertThat(processedRequest.topComponent).isEqualTo(component)
- }
-
- @Test
fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 69b7b88..8c9404e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -180,7 +180,7 @@
data.finisher = null;
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+ new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
ActionTransition::new, mSmartActionsProvider);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
@@ -208,7 +208,7 @@
data.finisher = null;
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+ new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
ActionTransition::new, mSmartActionsProvider);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
@@ -236,7 +236,7 @@
data.finisher = null;
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
+ new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
ActionTransition::new, mSmartActionsProvider);
Notification.Action deleteAction = task.createDeleteAction(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 002ef29..3a4da86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -33,7 +33,6 @@
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotHelper
@@ -175,28 +174,6 @@
}
@Test
- fun takeScreenshotPartial() {
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_SELECTED_REGION,
- SCREENSHOT_KEY_CHORD,
- /* topComponent = */ null)
-
- service.handleRequest(request, { /* onSaved */ }, callback)
-
- verify(controller, times(1)).takeScreenshotPartial(
- /* topComponent = */ isNull(),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName)
- }
-
- @Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
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 b40d5ac..0c60d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -23,6 +23,9 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
+import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
import static com.google.common.truth.Truth.assertThat;
@@ -1249,14 +1252,10 @@
@Test
public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
enableSplitShade(/* enabled= */ true);
- // set panel state to CLOSED
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
- /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ mPanelExpansionStateManager.updateState(STATE_CLOSED);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
- // change panel state to OPENING
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0.5f,
- /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 100);
+ mPanelExpansionStateManager.updateState(STATE_OPENING);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
}
@@ -1264,19 +1263,27 @@
@Test
public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
enableSplitShade(/* enabled= */ true);
- // set panel state to CLOSED
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 0,
- /* expanded= */ false, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ mPanelExpansionStateManager.updateState(STATE_CLOSED);
- // go to lockscreen, which also sets fraction to 1.0f and makes shade "expanded"
mStatusBarStateController.setState(KEYGUARD);
- mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
- /* expanded= */ true, /* tracking= */ true, /* dragDownPxAmount= */ 0);
+ // going to lockscreen would trigger STATE_OPENING
+ mPanelExpansionStateManager.updateState(STATE_OPENING);
assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
}
@Test
+ public void testQsImmediateResetsWhenPanelOpensOrCloses() {
+ mNotificationPanelViewController.mQsExpandImmediate = true;
+ mPanelExpansionStateManager.updateState(STATE_OPEN);
+ assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+
+ mNotificationPanelViewController.mQsExpandImmediate = true;
+ mPanelExpansionStateManager.updateState(STATE_CLOSED);
+ assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+ }
+
+ @Test
public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
// to make sure shade is in expanded state
mNotificationPanelViewController.startWaitingForOpenPanelGesture();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index a4a89a4..7a74b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -27,8 +27,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -42,8 +42,8 @@
private val viewsIdToRegister =
setOf(
- ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
- ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+ ViewIdToTranslate(START_VIEW_ID, Direction.START),
+ ViewIdToTranslate(END_VIEW_ID, Direction.END))
@Before
fun setup() {
@@ -66,41 +66,62 @@
}
@Test
- fun onTransition_oneMovesLeft() {
+ fun onTransition_oneMovesStartWithLTR() {
// GIVEN one view with a matching id
val view = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
- moveAndValidate(listOf(view to LEFT))
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_LTR)
}
@Test
- fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+ fun onTransition_oneMovesStartWithRTL() {
+ // GIVEN one view with a matching id
+ val view = View(context)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view)
+
+ whenever(parent.getLayoutDirection()).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_RTL)
+ }
+
+ @Test
+ fun onTransition_oneMovesStartAndOneMovesEndMultipleTimes() {
// GIVEN two views with a matching id
val leftView = View(context)
val rightView = View(context)
- whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
- whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+ whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView)
+ whenever(parent.findViewById<View>(END_VIEW_ID)).thenReturn(rightView)
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
+ moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR)
}
- private fun moveAndValidate(list: List<Pair<View, Int>>) {
+ private fun moveAndValidate(list: List<Pair<View, Int>>, layoutDirection: Int) {
// Compare values as ints because -0f != 0f
// WHEN the transition starts
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0f)
+ val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ 1
+ } else {
+ -1
+ }
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition progresses, translation is updated
progressProvider.onTransitionProgress(.5f)
list.forEach { (view, direction) ->
- assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+ assertEquals(
+ (-MAX_TRANSLATION / 2f * direction * rtlMultiplier).toInt(),
+ view.translationX.toInt()
+ )
}
// WHEN the transition ends, translation is completed
@@ -110,12 +131,12 @@
}
companion object {
- private val LEFT = Direction.LEFT.multiplier.toInt()
- private val RIGHT = Direction.RIGHT.multiplier.toInt()
+ private val START = Direction.START.multiplier.toInt()
+ private val END = Direction.END.multiplier.toInt()
private const val MAX_TRANSLATION = 42f
- private const val LEFT_VIEW_ID = 1
- private const val RIGHT_VIEW_ID = 2
+ private const val START_VIEW_ID = 1
+ private const val END_VIEW_ID = 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 8be138a..ffb41e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -22,7 +22,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
@@ -48,8 +48,8 @@
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockPluginManager: PluginManager
- @Mock private lateinit var mockClock: Clock
- @Mock private lateinit var mockDefaultClock: Clock
+ @Mock private lateinit var mockClock: ClockController
+ @Mock private lateinit var mockDefaultClock: ClockController
@Mock private lateinit var mockThumbnail: Drawable
@Mock private lateinit var mockHandler: Handler
@Mock private lateinit var mockContentResolver: ContentResolver
@@ -60,7 +60,7 @@
private var settingValue: String = ""
companion object {
- private fun failFactory(): Clock {
+ private fun failFactory(): ClockController {
fail("Unexpected call to createClock")
return null!!
}
@@ -73,17 +73,17 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
- private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+ private val createCallbacks = mutableMapOf<ClockId, () -> ClockController>()
private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
override fun getClocks() = metadata
- override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+ override fun createClock(id: ClockId): ClockController = createCallbacks[id]!!()
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
fun addClock(
id: ClockId,
name: String,
- create: () -> Clock = ::failFactory,
+ create: () -> ClockController = ::failFactory,
getThumbnail: () -> Drawable? = ::failThumbnail
): FakeClockPlugin {
metadata.add(ClockMetadata(id, name))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2b4a109..539a54b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shared.clocks
import android.content.res.Resources
+import android.graphics.Color
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.util.TypedValue
@@ -25,7 +26,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.clocks.DefaultClock.Companion.DOZE_COLOR
+import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -88,17 +89,20 @@
// Default clock provider must always provide the default clock
val clock = provider.createClock(DEFAULT_CLOCK_ID)
assertNotNull(clock)
- assertEquals(clock.smallClock, mockSmallClockView)
- assertEquals(clock.largeClock, mockLargeClockView)
+ assertEquals(mockSmallClockView, clock.smallClock.view)
+ assertEquals(mockLargeClockView, clock.largeClock.view)
}
@Test
fun defaultClock_initialize() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ verify(mockSmallClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+
clock.initialize(resources, 0f, 0f)
- verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
- verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockSmallClockView).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockLargeClockView).setColors(eq(DOZE_COLOR), anyInt())
verify(mockSmallClockView).onTimeZoneChanged(notNull())
verify(mockLargeClockView).onTimeZoneChanged(notNull())
verify(mockSmallClockView).refreshTime()
@@ -147,10 +151,14 @@
@Test
fun defaultClock_events_onColorPaletteChanged() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onColorPaletteChanged(resources, true, true)
- verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
- verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockSmallClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+ verify(mockLargeClockView).setColors(Color.MAGENTA, Color.MAGENTA)
+
+ clock.events.onColorPaletteChanged(resources)
+
+ verify(mockSmallClockView).setColors(eq(DOZE_COLOR), anyInt())
+ verify(mockLargeClockView).setColors(eq(DOZE_COLOR), anyInt())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
index 8bc438b..5fc09c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.shared.navigationbar
+
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -24,15 +25,23 @@
import androidx.concurrent.futures.DirectExecutor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.*
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -99,4 +108,39 @@
regionSamplingHelper.stopAndDestroy()
verify(compositionListener).unregister(any())
}
-}
\ No newline at end of file
+
+ @Test
+ fun testCompositionSamplingListener_has_nonEmptyRect() {
+ // simulate race condition
+ val fakeExecutor = FakeExecutor(FakeSystemClock()) // pass in as backgroundExecutor
+ val fakeSamplingCallback = mock(RegionSamplingHelper.SamplingCallback::class.java)
+
+ whenever(fakeSamplingCallback.isSamplingEnabled).thenReturn(true)
+ whenever(wrappedSurfaceControl.isValid).thenReturn(true)
+
+ regionSamplingHelper = object : RegionSamplingHelper(sampledView, fakeSamplingCallback,
+ DirectExecutor.INSTANCE, fakeExecutor, compositionListener) {
+ override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+ return wrappedSurfaceControl
+ }
+ }
+ regionSamplingHelper.setWindowVisible(true)
+ regionSamplingHelper.start(Rect(0, 0, 100, 100))
+
+ // make sure background task is enqueued
+ assertThat(fakeExecutor.numPending()).isEqualTo(1)
+
+ // make sure regionSamplingHelper will have empty Rect
+ whenever(fakeSamplingCallback.getSampledRegion(any())).thenReturn(Rect(0, 0, 0, 0))
+ regionSamplingHelper.onLayoutChange(sampledView, 0, 0, 0, 0, 0, 0, 0, 0)
+
+ // resume running of background thread
+ fakeExecutor.runAllReady()
+
+ // grab Rect passed into compositionSamplingListener and make sure it's not empty
+ val argumentGrabber = argumentCaptor<Rect>()
+ verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl),
+ argumentGrabber.capture())
+ assertThat(argumentGrabber.value.isEmpty).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
deleted file mode 100644
index 5432a74..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Verifies that particular sets of dependencies don't have dependencies on others. For example,
- * code managing notifications shouldn't directly depend on CentralSurfaces, since there are
- * platforms which want to manage notifications, but don't use CentralSurfaces.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NonPhoneDependencyTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationListContainer mListContainer;
- @Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
- @Mock private OnSettingsClickListener mOnSettingsClickListener;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- new Handler(TestableLooper.get(this).getLooper()));
- }
-
- @Ignore("Causes binder calls which fail")
- @Test
- public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
- mDependency.injectMockDependency(ShadeController.class);
- NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
- NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
- NotificationRemoteInputManager remoteInputManager =
- Dependency.get(NotificationRemoteInputManager.class);
- NotificationLockscreenUserManager lockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mOnSettingsClickListener);
- notificationLogger.setUpWithContainer(mListContainer);
- mediaManager.setUpWithPresenter(mPresenter);
- remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
- mDelegate);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
-
- TestableLooper.get(this).processAllMessages();
- assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 853d1df..bdafa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -88,6 +89,8 @@
@Mock
private NotificationClickNotifier mClickNotifier;
@Mock
+ private OverviewProxyService mOverviewProxyService;
+ @Mock
private KeyguardManager mKeyguardManager;
@Mock
private DeviceProvisionedController mDeviceProvisionedController;
@@ -344,6 +347,7 @@
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
+ (() -> mOverviewProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 9f21409..82e32b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection;
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
+import static com.android.systemui.statusbar.notification.collection.ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS;
import static com.google.common.truth.Truth.assertThat;
@@ -45,6 +46,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -125,6 +127,10 @@
private Map<String, Integer> mNextIdMap = new ArrayMap<>();
private int mNextRank = 0;
+ private Log.TerribleFailureHandler mOldWtfHandler = null;
+ private Log.TerribleFailure mLastWtf = null;
+ private int mWtfCount = 0;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -1748,14 +1754,17 @@
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the filter is invalidated exactly
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is NOT thrown.
+ // THEN an exception is NOT thrown directly, but a WTF IS logged.
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
}
@Test(expected = IllegalStateException.class)
@@ -1767,18 +1776,24 @@
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the filter is invalidated more than
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ try {
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ } finally {
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ }
// THEN an exception IS thrown.
}
@Test
- public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() {
+ public void testNonConsecutiveOutOfOrderInvalidationsDontThrowAfterTooManyRuns() {
// GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
NotifFilter filter = new PackageFilter(PACKAGE_1);
CountingInvalidator invalidator = new CountingInvalidator(filter);
@@ -1786,17 +1801,22 @@
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // WHEN we try to run the pipeline and the filter is invalidated at least
- // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
- addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
- dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ interceptWtfs();
- // THEN an exception is NOT thrown.
+ // WHEN we try to run the pipeline and the filter is invalidated
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason,
+ // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ dispatchBuild();
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
+ dispatchBuild();
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception is NOT thrown, but WTFs ARE logged.
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2);
}
@Test
@@ -1808,14 +1828,18 @@
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the promoter is invalidated exactly
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_1);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is NOT thrown.
+ // THEN an exception is NOT thrown directly, but a WTF IS logged.
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
}
@Test(expected = IllegalStateException.class)
@@ -1827,12 +1851,18 @@
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the promoter is invalidated more than
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_1);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ try {
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ } finally {
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ }
// THEN an exception IS thrown.
}
@@ -1846,14 +1876,17 @@
mListBuilder.setComparators(singletonList(comparator));
mListBuilder.addOnBeforeRenderListListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the comparator is invalidated exactly
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is NOT thrown.
+ // THEN an exception is NOT thrown directly, but a WTF IS logged.
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
}
@Test(expected = IllegalStateException.class)
@@ -1865,12 +1898,14 @@
mListBuilder.setComparators(singletonList(comparator));
mListBuilder.addOnBeforeRenderListListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the comparator is invalidated more than
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
// THEN an exception IS thrown.
}
@@ -1884,14 +1919,17 @@
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeRenderListListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is NOT thrown.
+ // THEN an exception is NOT thrown directly, but a WTF IS logged.
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
}
@Test(expected = IllegalStateException.class)
@@ -1903,16 +1941,59 @@
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeRenderListListener(listener);
+ interceptWtfs();
+
// WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
// MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_2);
- invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
dispatchBuild();
- runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ try {
+ runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ } finally {
+ expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ }
// THEN an exception IS thrown.
}
+ private void interceptWtfs() {
+ assertNull(mOldWtfHandler);
+
+ mLastWtf = null;
+ mWtfCount = 0;
+
+ mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> {
+ Log.e("ShadeListBuilderTest", "Observed WTF: " + e);
+ mLastWtf = e;
+ mWtfCount++;
+ });
+ }
+
+ private void expectNoWtfs() {
+ assertNull(expectWtfs(0));
+ }
+
+ private Log.TerribleFailure expectWtf() {
+ return expectWtfs(1);
+ }
+
+ private Log.TerribleFailure expectWtfs(int expectedWtfCount) {
+ assertNotNull(mOldWtfHandler);
+
+ Log.setWtfHandler(mOldWtfHandler);
+ mOldWtfHandler = null;
+
+ Log.TerribleFailure wtf = mLastWtf;
+ int wtfCount = mWtfCount;
+
+ mLastWtf = null;
+ mWtfCount = 0;
+
+ assertEquals(expectedWtfCount, wtfCount);
+ return wtf;
+ }
+
@Test
public void testStableOrdering() {
mStabilityManager.setAllowEntryReordering(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 2ee3126..2970807 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -337,6 +337,40 @@
}
@Test
+ fun testOnEntryUpdated_toAlert() {
+ // GIVEN that an entry is posted that should not heads up
+ setShouldHeadsUp(mEntry, false)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // WHEN it's updated to heads up
+ setShouldHeadsUp(mEntry)
+ mCollectionListener.onEntryUpdated(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification alerts
+ finishBind(mEntry)
+ verify(mHeadsUpManager).showNotification(mEntry)
+ }
+
+ @Test
+ fun testOnEntryUpdated_toNotAlert() {
+ // GIVEN that an entry is posted that should heads up
+ setShouldHeadsUp(mEntry)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // WHEN it's updated to not heads up
+ setShouldHeadsUp(mEntry, false)
+ mCollectionListener.onEntryUpdated(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification is never bound or shown
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+ }
+
+ @Test
fun testOnEntryRemovedRemovesHeadsUpNotification() {
// GIVEN the current HUN is mEntry
addHUN(mEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
new file mode 100644
index 0000000..16e2441
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
@@ -0,0 +1,321 @@
+/*
+ *
+ * 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.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryMonitorTest : SysuiTestCase() {
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification() {
+ val notification = createBasicNotification().build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+ val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = 0,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_customViewNotification_marksTrue() {
+ val notification =
+ createBasicNotification()
+ .setCustomContentView(
+ RemoteViews(context.packageName, android.R.layout.list_content)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3384,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = true,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_notificationWithDataIcon_calculatesCorrectly() {
+ val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
+ val notification =
+ createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = 444444,
+ largeIcon = 0,
+ extras = 3212,
+ bigPicture = 0,
+ extender = 0,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_bigPictureStyle() {
+ val bigPicture =
+ Icon.createWithBitmap(Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888))
+ val bigPictureIcon =
+ Icon.createWithAdaptiveBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.BigPictureStyle()
+ .bigPicture(bigPicture)
+ .bigLargeIcon(bigPictureIcon)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4092,
+ bigPicture = bigPicture.bitmap.allocationByteCount,
+ extender = 0,
+ style = "BigPictureStyle",
+ styleIcon = bigPictureIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_callingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val fakeIntent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ val notification =
+ createBasicNotification()
+ .setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 4084,
+ bigPicture = 0,
+ extender = 0,
+ style = "CallStyle",
+ styleIcon = personIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_messagingStyle() {
+ val personIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(386, 432, Bitmap.Config.ARGB_8888))
+ val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+ val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+ val historicPersonIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(348, 382, Bitmap.Config.ARGB_8888))
+ val historicPerson =
+ Person.Builder().setIcon(historicPersonIcon).setName("Historic person").build()
+ val historicMessage =
+ Notification.MessagingStyle.Message("Historic message!", 5848, historicPerson)
+
+ val notification =
+ createBasicNotification()
+ .setStyle(
+ Notification.MessagingStyle(person)
+ .addMessage(message)
+ .addHistoricMessage(historicMessage)
+ )
+ .build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 5024,
+ bigPicture = 0,
+ extender = 0,
+ style = "MessagingStyle",
+ styleIcon =
+ personIcon.bitmap.allocationByteCount +
+ historicPersonIcon.bitmap.allocationByteCount,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_carExtender() {
+ val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
+ val extender = Notification.CarExtender().setLargeIcon(carIcon)
+ val notification = createBasicNotification().extend(extender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3612,
+ bigPicture = 0,
+ extender = 556656,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_tvWearExtender() {
+ val tvExtender = Notification.TvExtender().setChannel("channel2")
+ val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
+ val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
+ val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
+ val nmm = createNMMWithNotifications(listOf(notification))
+ val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ assertNotificationObjectSizes(
+ memoryUse = memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3820,
+ bigPicture = 0,
+ extender = 388 + wearBackground.allocationByteCount,
+ style = null,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ private fun createBasicNotification(): Notification.Builder {
+ val smallIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888))
+ val largeIcon =
+ Icon.createWithBitmap(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888))
+ return Notification.Builder(context)
+ .setSmallIcon(smallIcon)
+ .setLargeIcon(largeIcon)
+ .setContentTitle("This is a title")
+ .setContentText("This is content text.")
+ }
+
+ /** This will generate a nicer error message than comparing objects */
+ private fun assertNotificationObjectSizes(
+ memoryUse: NotificationMemoryUsage,
+ smallIcon: Int,
+ largeIcon: Int,
+ extras: Int,
+ bigPicture: Int,
+ extender: Int,
+ style: String?,
+ styleIcon: Int,
+ hasCustomView: Boolean
+ ) {
+ assertThat(memoryUse.packageName).isEqualTo("test_pkg")
+ assertThat(memoryUse.notificationId)
+ .isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
+ assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
+ assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
+ assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
+ if (style == null) {
+ assertThat(memoryUse.objectUsage.style).isNull()
+ } else {
+ assertThat(memoryUse.objectUsage.style).isEqualTo(style)
+ }
+ assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
+ assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
+ }
+
+ private fun getUseObject(
+ singleItemUseList: List<NotificationMemoryUsage>
+ ): NotificationMemoryUsage {
+ assertThat(singleItemUseList).hasSize(1)
+ return singleItemUseList[0]
+ }
+
+ private fun createNMMWithNotifications(
+ notifications: List<Notification>
+ ): NotificationMemoryMonitor {
+ val notifPipeline: NotifPipeline = mock()
+ val notificationEntries =
+ notifications.map { n ->
+ NotificationEntryBuilder().setTag("test").setNotification(n).build()
+ }
+ whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
+ return NotificationMemoryMonitor(notifPipeline, mock())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index 7e97629..dae0aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -40,6 +40,10 @@
NotificationPanelLogger.toNotificationProto(visibleNotifications)));
}
+ @Override
+ public void logNotificationDrag(NotificationEntry draggedNotification) {
+ }
+
public static class CallRecord {
public boolean isLockscreen;
public Notifications.NotificationList list;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 922e93d..ed2afe7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -40,6 +40,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -63,6 +65,7 @@
private NotificationMenuRowPlugin.MenuItem mMenuItem =
mock(NotificationMenuRowPlugin.MenuItem.class);
private ShadeController mShadeController = mock(ShadeController.class);
+ private NotificationPanelLogger mNotificationPanelLogger = mock(NotificationPanelLogger.class);
@Before
public void setUp() throws Exception {
@@ -82,7 +85,7 @@
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager,
- mShadeController);
+ mShadeController, mNotificationPanelLogger);
}
@Test
@@ -96,6 +99,7 @@
mRow.doDragCallback(0, 0);
verify(controller).startDragAndDrop(mRow);
verify(mHeadsUpManager, times(1)).releaseAllImmediately();
+ verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
}
@Test
@@ -107,6 +111,7 @@
verify(controller).startDragAndDrop(mRow);
verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
eq(false), anyFloat());
+ verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
}
@Test
@@ -124,6 +129,7 @@
// Verify that we never start the actual drag since there is no content
verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt());
+ verify(mNotificationPanelLogger, never()).logNotificationDrag(any());
}
private ExpandableNotificationRowDragController createSpyController() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 682ff1f..81b8e98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -32,6 +32,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.widget.NotificationActionListLayout;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -142,4 +143,60 @@
verify(mockExpandedEB, times(1)).requestAccessibilityFocus();
verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus();
}
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(true);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() {
+ View mockContracted = mock(NotificationHeaderView.class);
+
+ View mockExpandedActions = mock(NotificationActionListLayout.class);
+ View mockExpanded = mock(NotificationHeaderView.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockExpandedActions);
+
+ View mockHeadsUpActions = mock(NotificationActionListLayout.class);
+ View mockHeadsUp = mock(NotificationHeaderView.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn(
+ mockHeadsUpActions);
+
+ mView.setContractedChild(mockContracted);
+ mView.setExpandedChild(mockExpanded);
+ mView.setHeadsUpChild(mockHeadsUp);
+
+ mView.setRemoteInputVisible(false);
+
+ verify(mockContracted, times(0)).findViewById(0);
+ verify(mockExpandedActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ verify(mockHeadsUpActions, times(1)).setImportantForAccessibility(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 8be9eb5..1c9b0be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -127,6 +128,7 @@
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController;
+ @Mock private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -174,7 +176,8 @@
mJankMonitor,
mStackLogger,
mLogger,
- mNotificationStackSizeCalculator
+ mNotificationStackSizeCalculator,
+ mFeatureFlags
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6ae021b..4353036 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -55,6 +55,7 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
+import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -179,6 +180,40 @@
}
@Test
+ public void testUpdateStackHeight_qsExpansionGreaterThanZero() {
+ final float expansionFraction = 0.2f;
+ final float overExpansion = 50f;
+
+ mStackScroller.setQsExpansionFraction(1f);
+ mAmbientState.setExpansionFraction(expansionFraction);
+ mAmbientState.setOverExpansion(overExpansion);
+ when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+
+ mStackScroller.setExpandedHeight(100f);
+
+ float expected = MathUtils.lerp(0, overExpansion,
+ BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction));
+ assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+ }
+
+ @Test
+ public void testUpdateStackHeight_qsExpansionZero() {
+ final float expansionFraction = 0.2f;
+ final float overExpansion = 50f;
+
+ mStackScroller.setQsExpansionFraction(0f);
+ mAmbientState.setExpansionFraction(expansionFraction);
+ mAmbientState.setOverExpansion(overExpansion);
+ when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+ mStackScroller.setExpandedHeight(100f);
+
+ float expected = MathUtils.lerp(0, overExpansion, expansionFraction);
+ assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+ }
+
+ @Test
public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
final float dozeAmount = 0.5f;
mAmbientState.setDozeAmount(dozeAmount);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 1305d79..4ea1c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -18,10 +18,13 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -51,10 +54,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
/**
* Tests for {@link NotificationSwipeHelper}.
*/
@@ -74,7 +82,11 @@
private Runnable mFalsingCheck;
private FeatureFlags mFeatureFlags;
- @Rule public MockitoRule mockito = MockitoJUnit.rule();
+ private static final int FAKE_ROW_WIDTH = 20;
+ private static final int FAKE_ROW_HEIGHT = 20;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
@@ -444,8 +456,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mView).getWidth();
- doReturn(20).when(mView).getHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mView).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mView).getHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -472,8 +484,8 @@
doReturn(5f).when(mEvent).getRawX();
doReturn(10f).when(mEvent).getRawY();
- doReturn(20).when(mNotificationRow).getWidth();
- doReturn(20).when(mNotificationRow).getActualHeight();
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getWidth();
+ doReturn(FAKE_ROW_HEIGHT).when(mNotificationRow).getActualHeight();
Answer answer = (Answer) invocation -> {
int[] arr = invocation.getArgument(0);
@@ -491,4 +503,56 @@
assertFalse("Touch is not within the view",
mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
}
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenNotificationIsNotDismissible() {
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, false);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaRemainsUnchangedWhenFeatureFlagIsDisabled() {
+
+ // Returning true prevents alpha fade. In an unmocked scenario the callback is instantiated
+ // within NotificationStackScrollLayoutController and returns the inverted value of the
+ // feature flag
+ doReturn(true).when(mCallback).updateSwipeProgress(any(), anyBoolean(), anyFloat());
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, 12, true);
+
+ verify(mNotificationRow, never()).setContentAlpha(anyFloat());
+ }
+
+ @Test
+ public void testContentAlphaFadeAnimationSpecs() {
+ // The alpha fade should be linear from 1f to 0f as translation progresses from 0 to 60% of
+ // view-width, and 0f at translations higher than that.
+ doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+
+ List<Integer> translations = Arrays.asList(
+ -FAKE_ROW_WIDTH * 2,
+ -FAKE_ROW_WIDTH,
+ (int) (-FAKE_ROW_WIDTH * 0.3),
+ 0,
+ (int) (FAKE_ROW_WIDTH * 0.3),
+ (int) (FAKE_ROW_WIDTH * 0.6),
+ FAKE_ROW_WIDTH,
+ FAKE_ROW_WIDTH * 2);
+ List<Float> expectedAlphas = translations.stream().map(translation ->
+ mSwipeHelper.getSwipeAlpha(Math.abs((float) translation / FAKE_ROW_WIDTH)))
+ .collect(Collectors.toList());
+
+ for (Integer translation : translations) {
+ mSwipeHelper.onTranslationUpdate(mNotificationRow, translation, true);
+ }
+
+ ArgumentCaptor<Float> capturedValues = ArgumentCaptor.forClass(Float.class);
+ verify(mNotificationRow, times(translations.size())).setContentAlpha(
+ capturedValues.capture());
+ assertEquals(expectedAlphas, capturedValues.getAllValues());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
new file mode 100644
index 0000000..da543d4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.util.Log
+import android.util.Log.TerribleFailureHandler
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlin.math.log2
+import kotlin.math.sqrt
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ViewStateTest : SysuiTestCase() {
+ private val viewState = ViewState()
+
+ private var wtfHandler: TerribleFailureHandler? = null
+ private var wtfCount = 0
+
+ @Suppress("DIVISION_BY_ZERO")
+ @Test
+ fun testWtfs() {
+ interceptWtfs()
+
+ // Setting valid values doesn't cause any wtfs.
+ viewState.alpha = 0.1f
+ viewState.xTranslation = 0f
+ viewState.yTranslation = 10f
+ viewState.zTranslation = 20f
+ viewState.scaleX = 0.5f
+ viewState.scaleY = 0.25f
+
+ expectWtfs(0)
+
+ // Setting NaN values leads to wtfs being logged, and the value not being changed.
+ viewState.alpha = 0.0f / 0.0f
+ expectWtfs(1)
+ Assert.assertEquals(viewState.alpha, 0.1f)
+
+ viewState.xTranslation = Float.NaN
+ expectWtfs(2)
+ Assert.assertEquals(viewState.xTranslation, 0f)
+
+ viewState.yTranslation = log2(-10.0).toFloat()
+ expectWtfs(3)
+ Assert.assertEquals(viewState.yTranslation, 10f)
+
+ viewState.zTranslation = sqrt(-1.0).toFloat()
+ expectWtfs(4)
+ Assert.assertEquals(viewState.zTranslation, 20f)
+
+ viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY
+ expectWtfs(5)
+ Assert.assertEquals(viewState.scaleX, 0.5f)
+
+ viewState.scaleY = Float.POSITIVE_INFINITY * 0
+ expectWtfs(6)
+ Assert.assertEquals(viewState.scaleY, 0.25f)
+ }
+
+ private fun interceptWtfs() {
+ wtfCount = 0
+ wtfHandler =
+ Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean ->
+ Log.e("ViewStateTest", "Observed WTF: $e")
+ wtfCount++
+ }
+ }
+
+ private fun expectWtfs(expectedWtfCount: Int) {
+ Assert.assertNotNull(wtfHandler)
+ Assert.assertEquals(expectedWtfCount, wtfCount)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 9de9db1..996851e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -40,7 +40,6 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -73,7 +72,6 @@
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController;
- @Mock private KeyguardViewMediator mKeyguardViewMediator;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -101,7 +99,7 @@
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
mBatteryController, mScrimController, () -> mBiometricUnlockController,
- mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
+ () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
mAuthController, mNotificationIconAreaController);
@@ -132,19 +130,11 @@
verify(mStatusBarStateController).setIsDozing(eq(false));
}
-
@Test
public void testPulseWhileDozing_updatesScrimController() {
mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
mCentralSurfaces.showKeyguardImpl();
- // Keep track of callback to be able to stop the pulse
-// DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-// doAnswer(invocation -> {
-// pulseCallback[0] = invocation.getArgument(0);
-// return null;
-// }).when(mDozeScrimController).pulse(any(), anyInt());
-
// Starting a pulse should change the scrim controller to the pulsing state
mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
@Override
@@ -210,4 +200,17 @@
}
}
}
+
+ @Test
+ public void testStopPulsing_setPendingPulseToFalse() {
+ // GIVEN a pending pulse
+ mDozeServiceHost.setPulsePending(true);
+
+ // WHEN pulsing is stopped
+ mDozeServiceHost.stopPulsing();
+
+ // THEN isPendingPulse=false, pulseOutNow is called
+ assertFalse(mDozeServiceHost.isPulsePending());
+ verify(mDozeScrimController).pulseOutNow();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index cfaa470..6ec5cf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -47,6 +47,7 @@
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
@@ -123,6 +124,7 @@
private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
+ @Mock private KeyguardLogger mLogger;
private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -172,7 +174,8 @@
mStatusBarUserInfoTracker,
mSecureSettings,
mCommandQueue,
- mFakeExecutor
+ mFakeExecutor,
+ mLogger
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 34399b8..9c56c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -80,6 +81,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -123,12 +125,14 @@
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
contextProvider,
darkIconDispatcher);
}
@@ -169,6 +173,7 @@
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index dcce61b..04ad1f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -33,6 +35,10 @@
import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
@@ -71,6 +77,7 @@
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;
import org.mockito.MockitoAnnotations;
@@ -117,6 +124,12 @@
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+ @Mock private ViewRootImpl mViewRootImpl;
+ @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Captor
+ private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -152,7 +165,14 @@
mFeatureFlags,
mBouncerCallbackInteractor,
mBouncerInteractor,
- mBouncerView);
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+ when(mViewRootImpl.getOnBackInvokedDispatcher())
+ .thenReturn(mOnBackInvokedDispatcher);
mStatusBarKeyguardViewManager.registerCentralSurfaces(
mCentralSurfaces,
mNotificationPanelView,
@@ -507,6 +527,37 @@
}
@Test
+ public void testPredictiveBackCallback_registration() {
+ /* verify that a predictive back callback is registered when the bouncer becomes visible */
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ /* verify that the same callback is unregistered when the bouncer becomes invisible */
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+ eq(mOnBackInvokedCallback.getValue()));
+ }
+
+ @Test
+ public void testPredictiveBackCallback_invocationHidesBouncer() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ /* capture the predictive back callback during registration */
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ mOnBackInvokedCallback.capture());
+
+ when(mBouncer.isShowing()).thenReturn(true);
+ when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+ /* invoke the back callback directly */
+ mOnBackInvokedCallback.getValue().onBackInvoked();
+
+ /* verify that the bouncer will be hidden as a result of the invocation */
+ verify(mCentralSurfaces).setBouncerShowing(eq(false));
+ }
+
+ @Test
public void testReportBouncerOnDreamWhenVisible() {
mBouncerExpansionCallback.onVisibilityChanged(true);
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
@@ -525,4 +576,11 @@
mBouncerExpansionCallback.onVisibilityChanged(false);
verify(mCentralSurfaces).setBouncerShowingOverDream(false);
}
+
+ @Test
+ public void flag_off_DoesNotCallBouncerInteractor() {
+ when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+ mStatusBarKeyguardViewManager.hideBouncer(false);
+ verify(mBouncerInteractor, never()).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 0000000..0d15268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+ private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+ private val _activeMobileDataSubscriptionId =
+ MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+ private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+ override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+ return subIdFlows[subId]
+ ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ }
+
+ fun setSubscriptions(subs: List<SubscriptionInfo>) {
+ _subscriptionsFlow.value = subs
+ }
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ _activeMobileDataSubscriptionId.value = subId
+ }
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+ val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+ subscription.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..6c495c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+ private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+ fun setUserSetup(setup: Boolean) {
+ _isUserSetup.value = setup
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 0000000..316b795
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ MobileSubscriptionRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+ val strength = signalStrength(1, 2, true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_displayInfo() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val ti = mock<TelephonyDisplayInfo>()
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.displayInfo).isEqualTo(ti)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ val state1 = underTest.getFlowForSubId(SUB_1_ID)
+ val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+ assertThat(state1).isEqualTo(state2)
+ }
+
+ @Test
+ fun testFlowForSubId_isRemovedAfterFinish() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+
+ // Start collecting on some flow
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ // There should be once cached flow now
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+ // When the job is canceled, the cache should be cleared
+ job.cancel()
+
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+ getTelephonyCallbackForType()
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..91c233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: UserSetupRepository
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ UserSetupRepositoryImpl(
+ deviceProvisionedController,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testUserSetup_defaultFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testUserSetup_updatesOnChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ val callback = getDeviceProvisionedListener()
+ callback.onUserSetupChanged()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+ val captor = argumentCaptor<DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+ return captor.value!!
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..8ec68f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+ private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+ override val iconGroup = _iconGroup
+
+ private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+ override val isEmergencyOnly = _isEmergencyOnly
+
+ private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val level = _level
+
+ private val _numberOfLevels = MutableStateFlow<Int>(4)
+ override val numberOfLevels = _numberOfLevels
+
+ private val _cutOut = MutableStateFlow<Boolean>(false)
+ override val cutOut = _cutOut
+
+ fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+ _iconGroup.value = group
+ }
+
+ fun setIsEmergencyOnly(emergency: Boolean) {
+ _isEmergencyOnly.value = emergency
+ }
+
+ fun setLevel(level: Int) {
+ _level.value = level
+ }
+
+ fun setNumberOfLevels(num: Int) {
+ _numberOfLevels.value = num
+ }
+
+ fun setCutOut(cutOut: Boolean) {
+ _cutOut.value = cutOut
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
new file mode 100644
index 0000000..2f07d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconInteractor
+ private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+ private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+ @Before
+ fun setUp() {
+ underTest = MobileIconInteractorImpl(sub1Flow)
+ }
+
+ @Test
+ fun gsm_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = true),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ job.cancel()
+ }
+
+ @Test
+ fun gsm_usesGsmLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = true,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = false),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_usesCdmaLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = false,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 0000000..89ad9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconsInteractor
+ private val userSetupRepository = FakeUserSetupRepository()
+ private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ MobileIconsInteractor(
+ subscriptionsRepository,
+ carrierConfigTracker,
+ userSetupRepository,
+ )
+ }
+
+ @After fun tearDown() {}
+
+ @Test
+ fun filteredSubscriptions_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 0000000..b374abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconViewModel
+ private val interactor = FakeMobileIconInteractor()
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ interactor.apply {
+ setLevel(1)
+ setCutOut(false)
+ setIconGroup(TelephonyIcons.THREE_G)
+ setIsEmergencyOnly(false)
+ setNumberOfLevels(4)
+ }
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+ }
+
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
new file mode 100644
index 0000000..773a0d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.telephony.data.repository
+
+import android.telephony.TelephonyCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class TelephonyRepositoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var manager: TelephonyListenerManager
+
+ private lateinit var underTest: TelephonyRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ TelephonyRepositoryImpl(
+ manager = manager,
+ )
+ }
+
+ @Test
+ fun callState() =
+ runBlocking(IMMEDIATE) {
+ var callState: Int? = null
+ val job = underTest.callState.onEach { callState = it }.launchIn(this)
+ val listenerCaptor = kotlinArgumentCaptor<TelephonyCallback.CallStateListener>()
+ verify(manager).addCallStateListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+
+ listener.onCallStateChanged(0)
+ assertThat(callState).isEqualTo(0)
+
+ listener.onCallStateChanged(1)
+ assertThat(callState).isEqualTo(1)
+
+ listener.onCallStateChanged(2)
+ assertThat(callState).isEqualTo(2)
+
+ job.cancel()
+
+ verify(manager).removeCallStateListener(listener)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 921b7ef..e1c1b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.temporarydisplay
import android.content.Context
+import android.graphics.Rect
import android.os.PowerManager
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
@@ -61,6 +63,8 @@
@Mock
private lateinit var powerManager: PowerManager
+ private var shouldIgnoreViewRemoval: Boolean = false
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -205,6 +209,26 @@
verify(windowManager, never()).removeView(any())
}
+ @Test
+ fun removeView_shouldIgnoreRemovalFalse_viewRemoved() {
+ shouldIgnoreViewRemoval = false
+ underTest.displayView(getState())
+
+ underTest.removeView("reason")
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() {
+ shouldIgnoreViewRemoval = true
+ underTest.displayView(getState())
+
+ underTest.removeView("reason")
+
+ verify(windowManager, never()).removeView(any())
+ }
+
private fun getState(name: String = "name") = ViewInfo(name)
private fun getConfigurationListener(): ConfigurationListener {
@@ -237,9 +261,16 @@
override val windowLayoutParams = commonWindowLayoutParams
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
- super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo
}
+
+ override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean {
+ return shouldIgnoreViewRemoval
+ }
+
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ outRect.setEmpty()
+ }
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
new file mode 100644
index 0000000..7586fe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TouchableRegionViewControllerTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.temporarydisplay
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class TouchableRegionViewControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var view: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ }
+
+ @Test
+ fun viewAttached_listenerAdded() {
+ val controller = TouchableRegionViewController(view) { _, _ -> }
+
+ controller.onViewAttached()
+
+ verify(viewTreeObserver).addOnComputeInternalInsetsListener(any())
+ }
+
+ @Test
+ fun viewDetached_listenerRemoved() {
+ val controller = TouchableRegionViewController(view) { _, _ -> }
+
+ controller.onViewDetached()
+
+ verify(viewTreeObserver).removeOnComputeInternalInsetsListener(any())
+ }
+
+ @Test
+ fun listener_usesPassedInFunction() {
+ val controller =
+ TouchableRegionViewController(view) { _, outRect -> outRect.set(1, 2, 3, 4) }
+
+ controller.onViewAttached()
+
+ val captor =
+ ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener::class.java)
+ verify(viewTreeObserver).addOnComputeInternalInsetsListener(captor.capture())
+ val listener = captor.value!!
+
+ val inoutInfo = ViewTreeObserver.InternalInsetsInfo()
+ listener.onComputeInternalInsets(inoutInfo)
+
+ assertThat(inoutInfo.touchableRegion.bounds).isEqualTo(Rect(1, 2, 3, 4))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 50259b5..2a93fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -885,4 +885,31 @@
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
.isEqualTo(new OverlayIdentifier("ff00ff00"));
}
+
+ // Regression test for b/234603929, where a reboot would generate a wallpaper colors changed
+ // event for the already-set colors that would then set the theme incorrectly on screen sleep.
+ @Test
+ public void onWallpaperColorsSetToSame_keepsTheme() {
+ // Set initial colors and verify.
+ WallpaperColors startingColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ WallpaperColors sameColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ clearInvocations(mThemeOverlayApplier);
+
+ // Set to the same colors.
+ mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+ verify(mThemeOverlayApplier, never())
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+
+ // Verify that no change resulted.
+ mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
+ verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
+ any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
new file mode 100644
index 0000000..d951f36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.data.repository
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
+
+ @Before
+ fun setUp() {
+ super.setUp(isRefactored = true)
+ }
+
+ @Test
+ fun userSwitcherSettings() = runSelfCancelingTest {
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = true,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ underTest = create(this)
+
+ var value: UserSwitcherSettingsModel? = null
+ underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = true,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = false,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+ }
+
+ @Test
+ fun refreshUsers() = runSelfCancelingTest {
+ underTest = create(this)
+ val initialExpectedValue =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(initialExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val secondExpectedValue =
+ setUpUsers(
+ count = 4,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(secondExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val selectedNonGuestUserId = selectedUserInfo?.id
+ val thirdExpectedValue =
+ setUpUsers(
+ count = 2,
+ hasGuest = true,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(thirdExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+ assertThat(selectedUserInfo?.isGuest).isTrue()
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+ }
+
+ @Test
+ fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+ underTest = create(this)
+ val unsortedUsers =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ unsortedUsers[0].creationTime = 900
+ unsortedUsers[1].creationTime = 700
+ unsortedUsers[2].creationTime = 999
+ val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(expectedUsers)
+ }
+
+ private fun setUpUsers(
+ count: Int,
+ hasGuest: Boolean = false,
+ selectedIndex: Int = 0,
+ ): List<UserInfo> {
+ val userInfos =
+ (0 until count).map { index ->
+ createUserInfo(
+ index,
+ isGuest = hasGuest && index == count - 1,
+ )
+ }
+ whenever(manager.aliveUsers).thenReturn(userInfos)
+ tracker.set(userInfos, selectedIndex)
+ return userInfos
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ isGuest: Boolean,
+ ): UserInfo {
+ val flags = 0
+ return UserInfo(
+ id,
+ "user_$id",
+ /* iconPath= */ "",
+ flags,
+ if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+ )
+ }
+
+ private fun setUpGlobalSettings(
+ isSimpleUserSwitcher: Boolean = false,
+ isAddUsersFromLockscreen: Boolean = false,
+ isUserSwitcherEnabled: Boolean = true,
+ ) {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+ true,
+ )
+ globalSettings.putIntForUser(
+ UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+ if (isSimpleUserSwitcher) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ if (isAddUsersFromLockscreen) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (isUserSwitcherEnabled) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ }
+
+ private fun assertUserSwitcherSettings(
+ model: UserSwitcherSettingsModel?,
+ expectedSimpleUserSwitcher: Boolean,
+ expectedAddUsersFromLockscreen: Boolean,
+ expectedUserSwitcherEnabled: Boolean,
+ ) {
+ checkNotNull(model)
+ assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+ assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+ assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+ }
+
+ /**
+ * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+ * is then automatically canceled and cleaned-up.
+ */
+ private fun runSelfCancelingTest(
+ block: suspend CoroutineScope.() -> Unit,
+ ) =
+ runBlocking(Dispatchers.Main.immediate) {
+ val scope = CoroutineScope(coroutineContext + Job())
+ block(scope)
+ scope.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 6fec343..dcea83a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,201 +17,54 @@
package com.android.systemui.user.data.repository
-import android.content.pm.UserInfo
import android.os.UserManager
-import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
+import com.android.systemui.util.settings.FakeSettings
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
+import kotlinx.coroutines.test.TestCoroutineScope
import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplTest : SysuiTestCase() {
+abstract class UserRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var manager: UserManager
- @Mock private lateinit var controller: UserSwitcherController
- @Captor
- private lateinit var userSwitchCallbackCaptor:
- ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+ @Mock protected lateinit var manager: UserManager
+ @Mock protected lateinit var controller: UserSwitcherController
- private lateinit var underTest: UserRepositoryImpl
+ protected lateinit var underTest: UserRepositoryImpl
- @Before
- fun setUp() {
+ protected lateinit var globalSettings: FakeSettings
+ protected lateinit var tracker: FakeUserTracker
+ protected lateinit var featureFlags: FakeFeatureFlags
+
+ protected fun setUp(isRefactored: Boolean) {
MockitoAnnotations.initMocks(this)
- whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
- whenever(controller.isGuestUserAutoCreated).thenReturn(false)
- whenever(controller.isGuestUserResetting).thenReturn(false)
- underTest =
- UserRepositoryImpl(
- appContext = context,
- manager = manager,
- controller = controller,
- )
+ globalSettings = FakeSettings()
+ tracker = FakeUserTracker()
+ featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
}
- @Test
- fun `users - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `users - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `users - does not include actions`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- )
- )
- var models: List<UserModel>? = null
- val job = underTest.users.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(3)
- assertThat(models?.get(0)?.id).isEqualTo(0)
- assertThat(models?.get(0)?.isSelected).isTrue()
- assertThat(models?.get(1)?.id).isEqualTo(1)
- assertThat(models?.get(1)?.isSelected).isFalse()
- assertThat(models?.get(2)?.id).isEqualTo(2)
- assertThat(models?.get(2)?.isSelected).isFalse()
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createUserRecord(1),
- createUserRecord(2),
- )
- )
- var id: Int? = null
- val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
- assertThat(id).isEqualTo(0)
-
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0),
- createUserRecord(1),
- createUserRecord(2, isSelected = true),
- )
- )
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
- userSwitchCallbackCaptor.value.onUserSwitched()
- assertThat(id).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun `actions - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `actions - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `actopms - does not include users`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- )
- )
- var models: List<UserActionModel>? = null
- val job = underTest.actions.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(3)
- assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
- assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
- assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
- job.cancel()
- }
-
- private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
- return UserRecord(
- info = UserInfo(id, "name$id", 0),
- isCurrent = isSelected,
- )
- }
-
- private fun createActionRecord(action: UserActionModel): UserRecord {
- return UserRecord(
- isAddUser = action == UserActionModel.ADD_USER,
- isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
- isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+ protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ return UserRepositoryImpl(
+ appContext = context,
+ manager = manager,
+ controller = controller,
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ backgroundDispatcher = IMMEDIATE,
+ globalSettings = globalSettings,
+ tracker = tracker,
+ featureFlags = featureFlags,
)
}
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
+ @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
new file mode 100644
index 0000000..d4b41c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.data.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+
+ @Captor
+ private lateinit var userSwitchCallbackCaptor:
+ ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
+
+ @Before
+ fun setUp() {
+ super.setUp(isRefactored = false)
+
+ whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
+ whenever(controller.isGuestUserAutoCreated).thenReturn(false)
+ whenever(controller.isGuestUserResetting).thenReturn(false)
+
+ underTest = create()
+ }
+
+ @Test
+ fun `users - registers for updates`() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.users.onEach {}.launchIn(this)
+
+ verify(controller).addUserSwitchCallback(any())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `users - unregisters from updates`() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.users.onEach {}.launchIn(this)
+ verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+ job.cancel()
+
+ verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+ }
+
+ @Test
+ fun `users - does not include actions`() =
+ runBlocking(IMMEDIATE) {
+ whenever(controller.users)
+ .thenReturn(
+ arrayListOf(
+ createUserRecord(0, isSelected = true),
+ createActionRecord(UserActionModel.ADD_USER),
+ createUserRecord(1),
+ createUserRecord(2),
+ createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+ createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ )
+ )
+ var models: List<UserModel>? = null
+ val job = underTest.users.onEach { models = it }.launchIn(this)
+
+ assertThat(models).hasSize(3)
+ assertThat(models?.get(0)?.id).isEqualTo(0)
+ assertThat(models?.get(0)?.isSelected).isTrue()
+ assertThat(models?.get(1)?.id).isEqualTo(1)
+ assertThat(models?.get(1)?.isSelected).isFalse()
+ assertThat(models?.get(2)?.id).isEqualTo(2)
+ assertThat(models?.get(2)?.isSelected).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun selectedUser() =
+ runBlocking(IMMEDIATE) {
+ whenever(controller.users)
+ .thenReturn(
+ arrayListOf(
+ createUserRecord(0, isSelected = true),
+ createUserRecord(1),
+ createUserRecord(2),
+ )
+ )
+ var id: Int? = null
+ val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
+
+ assertThat(id).isEqualTo(0)
+
+ whenever(controller.users)
+ .thenReturn(
+ arrayListOf(
+ createUserRecord(0),
+ createUserRecord(1),
+ createUserRecord(2, isSelected = true),
+ )
+ )
+ verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+ userSwitchCallbackCaptor.value.onUserSwitched()
+ assertThat(id).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - unregisters from updates`() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.actions.onEach {}.launchIn(this)
+ verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
+
+ job.cancel()
+
+ verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
+ }
+
+ @Test
+ fun `actions - registers for updates`() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.actions.onEach {}.launchIn(this)
+
+ verify(controller).addUserSwitchCallback(any())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - does not include users`() =
+ runBlocking(IMMEDIATE) {
+ whenever(controller.users)
+ .thenReturn(
+ arrayListOf(
+ createUserRecord(0, isSelected = true),
+ createActionRecord(UserActionModel.ADD_USER),
+ createUserRecord(1),
+ createUserRecord(2),
+ createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
+ createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ )
+ )
+ var models: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { models = it }.launchIn(this)
+
+ assertThat(models).hasSize(3)
+ assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
+ assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
+ assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+ job.cancel()
+ }
+
+ private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
+ return UserRecord(
+ info = UserInfo(id, "name$id", 0),
+ isCurrent = isSelected,
+ )
+ }
+
+ private fun createActionRecord(action: UserActionModel): UserRecord {
+ return UserRecord(
+ isAddUser = action == UserActionModel.ADD_USER,
+ isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
+ isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
new file mode 100644
index 0000000..120bf79
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -0,0 +1,394 @@
+/*
+ * 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.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class GuestUserInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var showDialog: (ShowDialogRequestModel) -> Unit
+ @Mock private lateinit var dismissDialog: () -> Unit
+ @Mock private lateinit var selectUser: (Int) -> Unit
+ @Mock private lateinit var switchUser: (Int) -> Unit
+
+ private lateinit var underTest: GuestUserInteractor
+
+ private lateinit var scope: TestCoroutineScope
+ private lateinit var repository: FakeUserRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(manager.createGuest(any())).thenReturn(GUEST_USER_INFO)
+
+ scope = TestCoroutineScope()
+ repository = FakeUserRepository()
+ repository.setUserInfos(ALL_USERS)
+
+ underTest =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ backgroundDispatcher = IMMEDIATE,
+ manager = manager,
+ repository = repository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ repository = repository,
+ ),
+ uiEventLogger = uiEventLogger,
+ )
+ }
+
+ @Test
+ fun `onDeviceBootCompleted - allowed to add - create guest`() =
+ runBlocking(IMMEDIATE) {
+ setAllowedToAdd()
+
+ underTest.onDeviceBootCompleted()
+
+ verify(manager).createGuest(any())
+ verify(deviceProvisionedController, never()).addCallback(any())
+ }
+
+ @Test
+ fun `onDeviceBootCompleted - await provisioning - and create guest`() =
+ runBlocking(IMMEDIATE) {
+ setAllowedToAdd(isAllowed = false)
+ underTest.onDeviceBootCompleted()
+ val captor =
+ kotlinArgumentCaptor<DeviceProvisionedController.DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+
+ setAllowedToAdd(isAllowed = true)
+ captor.value.onDeviceProvisionedChanged()
+
+ verify(manager).createGuest(any())
+ verify(deviceProvisionedController).removeCallback(captor.value)
+ }
+
+ @Test
+ fun createAndSwitchTo() =
+ runBlocking(IMMEDIATE) {
+ underTest.createAndSwitchTo(
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ selectUser = selectUser,
+ )
+
+ verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+ verify(manager).createGuest(any())
+ verify(dismissDialog).invoke()
+ verify(selectUser).invoke(GUEST_USER_INFO.id)
+ }
+
+ @Test
+ fun `createAndSwitchTo - fails to create - does not switch to`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.createGuest(any())).thenReturn(null)
+
+ underTest.createAndSwitchTo(
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ selectUser = selectUser,
+ )
+
+ verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+ verify(manager).createGuest(any())
+ verify(dismissDialog).invoke()
+ verify(selectUser, never()).invoke(anyInt())
+ }
+
+ @Test
+ fun `exit - returns to target user`() =
+ runBlocking(IMMEDIATE) {
+ repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+ val targetUserId = NON_GUEST_USER_INFO.id
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = targetUserId,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager, never()).markGuestForDeletion(anyInt())
+ verify(manager, never()).removeUser(anyInt())
+ verify(switchUser).invoke(targetUserId)
+ }
+
+ @Test
+ fun `exit - returns to last non-guest`() =
+ runBlocking(IMMEDIATE) {
+ val expectedUserId = NON_GUEST_USER_INFO.id
+ whenever(manager.getUserInfo(expectedUserId)).thenReturn(NON_GUEST_USER_INFO)
+ repository.lastSelectedNonGuestUserId = expectedUserId
+ repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = UserHandle.USER_NULL,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager, never()).markGuestForDeletion(anyInt())
+ verify(manager, never()).removeUser(anyInt())
+ verify(switchUser).invoke(expectedUserId)
+ }
+
+ @Test
+ fun `exit - last non-guest was removed - returns to system`() =
+ runBlocking(IMMEDIATE) {
+ val removedUserId = 310
+ repository.lastSelectedNonGuestUserId = removedUserId
+ repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = UserHandle.USER_NULL,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager, never()).markGuestForDeletion(anyInt())
+ verify(manager, never()).removeUser(anyInt())
+ verify(switchUser).invoke(UserHandle.USER_SYSTEM)
+ }
+
+ @Test
+ fun `exit - guest was ephemeral - it is removed`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+ repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
+ repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
+ val targetUserId = NON_GUEST_USER_INFO.id
+
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = targetUserId,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
+ verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
+ verify(switchUser).invoke(targetUserId)
+ }
+
+ @Test
+ fun `exit - force remove guest - it is removed`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+ repository.setSelectedUserInfo(GUEST_USER_INFO)
+ val targetUserId = NON_GUEST_USER_INFO.id
+
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = targetUserId,
+ forceRemoveGuestOnExit = true,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+ verify(manager).removeUser(GUEST_USER_INFO.id)
+ verify(switchUser).invoke(targetUserId)
+ }
+
+ @Test
+ fun `exit - selected different from guest user - do nothing`() =
+ runBlocking(IMMEDIATE) {
+ repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+ underTest.exit(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = 123,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verifyDidNotExit()
+ }
+
+ @Test
+ fun `exit - selected is actually not a guest user - do nothing`() =
+ runBlocking(IMMEDIATE) {
+ repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+ underTest.exit(
+ guestUserId = NON_GUEST_USER_INFO.id,
+ targetUserId = 123,
+ forceRemoveGuestOnExit = false,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verifyDidNotExit()
+ }
+
+ @Test
+ fun `remove - returns to target user`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+ repository.setSelectedUserInfo(GUEST_USER_INFO)
+
+ val targetUserId = NON_GUEST_USER_INFO.id
+ underTest.remove(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = targetUserId,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
+ verify(manager).removeUser(GUEST_USER_INFO.id)
+ verify(switchUser).invoke(targetUserId)
+ }
+
+ @Test
+ fun `remove - selected different from guest user - do nothing`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+ repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+ underTest.remove(
+ guestUserId = GUEST_USER_INFO.id,
+ targetUserId = 123,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verifyDidNotRemove()
+ }
+
+ @Test
+ fun `remove - selected is actually not a guest user - do nothing`() =
+ runBlocking(IMMEDIATE) {
+ whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
+ repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
+
+ underTest.remove(
+ guestUserId = NON_GUEST_USER_INFO.id,
+ targetUserId = 123,
+ showDialog = showDialog,
+ dismissDialog = dismissDialog,
+ switchUser = switchUser,
+ )
+
+ verifyDidNotRemove()
+ }
+
+ private fun setAllowedToAdd(isAllowed: Boolean = true) {
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(isAllowed)
+ whenever(devicePolicyManager.isDeviceManaged).thenReturn(!isAllowed)
+ }
+
+ private fun verifyDidNotExit() {
+ verifyDidNotRemove()
+ verify(manager, never()).getUserInfo(anyInt())
+ verify(uiEventLogger, never()).log(any())
+ }
+
+ private fun verifyDidNotRemove() {
+ verify(manager, never()).markGuestForDeletion(anyInt())
+ verify(showDialog, never()).invoke(any())
+ verify(dismissDialog, never()).invoke()
+ verify(switchUser, never()).invoke(anyInt())
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private val NON_GUEST_USER_INFO =
+ UserInfo(
+ /* id= */ 818,
+ /* name= */ "non_guest",
+ /* flags= */ 0,
+ )
+ private val GUEST_USER_INFO =
+ UserInfo(
+ /* id= */ 669,
+ /* name= */ "guest",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_FULL_GUEST,
+ )
+ private val EPHEMERAL_GUEST_USER_INFO =
+ UserInfo(
+ /* id= */ 669,
+ /* name= */ "guest",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_EPHEMERAL,
+ UserManager.USER_TYPE_FULL_GUEST,
+ )
+ private val ALL_USERS =
+ listOf(
+ NON_GUEST_USER_INFO,
+ GUEST_USER_INFO,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
new file mode 100644
index 0000000..593ce1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RefreshUsersSchedulerTest : SysuiTestCase() {
+
+ private lateinit var underTest: RefreshUsersScheduler
+
+ private lateinit var repository: FakeUserRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ repository = FakeUserRepository()
+ }
+
+ @Test
+ fun `pause - prevents the next refresh from happening`() =
+ runBlocking(IMMEDIATE) {
+ underTest =
+ RefreshUsersScheduler(
+ applicationScope = this,
+ mainDispatcher = IMMEDIATE,
+ repository = repository,
+ )
+ underTest.pause()
+
+ underTest.refreshIfNotPaused()
+ assertThat(repository.refreshUsersCallCount).isEqualTo(0)
+ }
+
+ @Test
+ fun `unpauseAndRefresh - forces the refresh even when paused`() =
+ runBlocking(IMMEDIATE) {
+ underTest =
+ RefreshUsersScheduler(
+ applicationScope = this,
+ mainDispatcher = IMMEDIATE,
+ repository = repository,
+ )
+ underTest.pause()
+
+ underTest.unpauseAndRefresh()
+
+ assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+ }
+
+ @Test
+ fun `refreshIfNotPaused - refreshes when not paused`() =
+ runBlocking(IMMEDIATE) {
+ underTest =
+ RefreshUsersScheduler(
+ applicationScope = this,
+ mainDispatcher = IMMEDIATE,
+ repository = repository,
+ )
+ underTest.refreshIfNotPaused()
+
+ assertThat(repository.refreshUsersCallCount).isEqualTo(1)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
new file mode 100644
index 0000000..37c378c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -0,0 +1,726 @@
+/*
+ * 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.domain.interactor
+
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorRefactoredTest : UserInteractorTest() {
+
+ override fun isRefactored(): Boolean {
+ return true
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+ overrideResource(R.dimen.max_avatar_size, 10)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_APP_PACKAGE,
+ )
+ whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ }
+
+ @Test
+ fun `onRecordSelected - user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(activityManager).switchUser(userInfos[1].id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - enter guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+ underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(manager).createGuest(any())
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+ verify(dialogShower, never()).dismiss()
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ @Test
+ fun `users - switcher enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 3, includeGuest = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switches to second user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertUsers(models = value, count = 2, selectedIndex = 1)
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switcher not enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun selectedUser() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: UserModel? = null
+ val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+ assertUser(value, id = 0, isSelected = true)
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ assertUser(value, id = 1, isSelected = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user not primary - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user is guest - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ assertThat(userInfos[1].isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked add from lockscreen set - full list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true,
+ )
+ )
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked - only guest action is shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(true)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add user - dialog shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.executeAction(UserActionModel.ADD_USER)
+ assertThat(dialogRequest)
+ .isEqualTo(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = userInfos[0].userHandle,
+ isKeyguardShowing = false,
+ showEphemeralMessage = false,
+ )
+ )
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add supervised user - starts activity`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+ assertThat(intentCaptor.value.action)
+ .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+ }
+
+ @Test
+ fun `executeAction - navigate to manage users`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
+
+ @Test
+ fun `executeAction - guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+ val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+ val showDialogsJob =
+ underTest.dialogShowRequests
+ .onEach {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
+ }
+ }
+ .launchIn(this)
+ val dismissDialogsJob =
+ underTest.dialogDismissRequests
+ .onEach {
+ if (it != null) {
+ underTest.onDialogDismissed()
+ }
+ }
+ .launchIn(this)
+
+ underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+ assertThat(dialogRequests)
+ .contains(
+ ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+ )
+ verify(activityManager).switchUser(guestUserInfo.id)
+
+ showDialogsJob.cancel()
+ dismissDialogsJob.cancel()
+ }
+
+ @Test
+ fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(
+ newlySelectedUserId = guestUserInfo.id,
+ dialogShower = dialogShower,
+ )
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - not currently guest - switches users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest).isNull()
+ verify(activityManager).switchUser(userInfos[1].id)
+ verify(dialogShower).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `Telephony call state changes - refreshes users`() =
+ runBlocking(IMMEDIATE) {
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ telephonyRepository.setCallState(1)
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User switched broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val callback1: UserInteractor.UserCallback = mock()
+ val callback2: UserInteractor.UserCallback = mock()
+ underTest.addCallback(callback1)
+ underTest.addCallback(callback2)
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_SWITCHED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+ )
+ }
+
+ verify(callback1).onUserStateChanged()
+ verify(callback2).onUserStateChanged()
+ assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User info changed broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_INFO_CHANGED),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `System user unlocked broadcast - refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `Non-system user unlocked broadcast - do not refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+ }
+
+ @Test
+ fun userRecords() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ testCoroutineScope.advanceUntilIdle()
+
+ assertRecords(
+ records = underTest.userRecords.value,
+ userIds = listOf(0, 1, 2),
+ selectedUserIndex = 0,
+ includeGuest = false,
+ expectedActions =
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ ),
+ )
+ }
+
+ @Test
+ fun selectedUserRecord() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertRecordForUser(
+ record = underTest.selectedUserRecord.value,
+ id = 0,
+ hasPicture = true,
+ isCurrent = true,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ private fun assertUsers(
+ models: List<UserModel>?,
+ count: Int,
+ selectedIndex: Int = 0,
+ includeGuest: Boolean = false,
+ ) {
+ checkNotNull(models)
+ assertThat(models.size).isEqualTo(count)
+ models.forEachIndexed { index, model ->
+ assertUser(
+ model = model,
+ id = index,
+ isSelected = index == selectedIndex,
+ isGuest = includeGuest && index == count - 1
+ )
+ }
+ }
+
+ private fun assertUser(
+ model: UserModel?,
+ id: Int,
+ isSelected: Boolean = false,
+ isGuest: Boolean = false,
+ ) {
+ checkNotNull(model)
+ assertThat(model.id).isEqualTo(id)
+ assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+ assertThat(model.isSelected).isEqualTo(isSelected)
+ assertThat(model.isSelectable).isTrue()
+ assertThat(model.isGuest).isEqualTo(isGuest)
+ }
+
+ private fun assertRecords(
+ records: List<UserRecord>,
+ userIds: List<Int>,
+ selectedUserIndex: Int = 0,
+ includeGuest: Boolean = false,
+ expectedActions: List<UserActionModel> = emptyList(),
+ ) {
+ assertThat(records.size >= userIds.size).isTrue()
+ userIds.indices.forEach { userIndex ->
+ val record = records[userIndex]
+ assertThat(record.info).isNotNull()
+ val isGuest = includeGuest && userIndex == userIds.size - 1
+ assertRecordForUser(
+ record = record,
+ id = userIds[userIndex],
+ hasPicture = !isGuest,
+ isCurrent = userIndex == selectedUserIndex,
+ isGuest = isGuest,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+ (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+ val record = records[actionIndex]
+ assertThat(record.info).isNull()
+ assertRecordForAction(
+ record = record,
+ type = expectedActions[actionIndex - userIds.size],
+ )
+ }
+ }
+
+ private fun assertRecordForUser(
+ record: UserRecord?,
+ id: Int? = null,
+ hasPicture: Boolean = false,
+ isCurrent: Boolean = false,
+ isGuest: Boolean = false,
+ isSwitchToEnabled: Boolean = false,
+ ) {
+ checkNotNull(record)
+ assertThat(record.info?.id).isEqualTo(id)
+ assertThat(record.picture != null).isEqualTo(hasPicture)
+ assertThat(record.isCurrent).isEqualTo(isCurrent)
+ assertThat(record.isGuest).isEqualTo(isGuest)
+ assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+ }
+
+ private fun assertRecordForAction(
+ record: UserRecord,
+ type: UserActionModel,
+ ) {
+ assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+ assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+ assertThat(record.isAddSupervisedUser)
+ .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+ }
+
+ private fun createUserInfos(
+ count: Int,
+ includeGuest: Boolean,
+ ): List<UserInfo> {
+ return (0 until count).map { index ->
+ val isGuest = includeGuest && index == count - 1
+ createUserInfo(
+ id = index,
+ name =
+ if (isGuest) {
+ "guest"
+ } else {
+ "user_$index"
+ },
+ isPrimary = !isGuest && index == 0,
+ isGuest = isGuest,
+ )
+ }
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ name: String,
+ isPrimary: Boolean = false,
+ isGuest: Boolean = false,
+ ): UserInfo {
+ return UserInfo(
+ id,
+ name,
+ /* iconPath= */ "",
+ /* flags= */ if (isPrimary) {
+ UserInfo.FLAG_PRIMARY
+ } else {
+ 0
+ },
+ if (isGuest) {
+ UserManager.USER_TYPE_FULL_GUEST
+ } else {
+ UserManager.USER_TYPE_FULL_SYSTEM
+ },
+ )
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val GUEST_ICON: Drawable = mock()
+ private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index e914e2e..1680c36c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -17,51 +17,63 @@
package com.android.systemui.user.domain.interactor
-import androidx.test.filters.SmallTest
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.os.UserManager
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import kotlinx.coroutines.test.TestCoroutineScope
import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+abstract class UserInteractorTest : SysuiTestCase() {
- @Mock private lateinit var controller: UserSwitcherController
- @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock protected lateinit var controller: UserSwitcherController
+ @Mock protected lateinit var activityStarter: ActivityStarter
+ @Mock protected lateinit var manager: UserManager
+ @Mock protected lateinit var activityManager: ActivityManager
+ @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock protected lateinit var uiEventLogger: UiEventLogger
+ @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
- private lateinit var underTest: UserInteractor
+ protected lateinit var underTest: UserInteractor
- private lateinit var userRepository: FakeUserRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
+ protected lateinit var testCoroutineScope: TestCoroutineScope
+ protected lateinit var userRepository: FakeUserRepository
+ protected lateinit var keyguardRepository: FakeKeyguardRepository
+ protected lateinit var telephonyRepository: FakeTelephonyRepository
- @Before
- fun setUp() {
+ abstract fun isRefactored(): Boolean
+
+ open fun setUp() {
MockitoAnnotations.initMocks(this)
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
+ telephonyRepository = FakeTelephonyRepository()
+ testCoroutineScope = TestCoroutineScope()
+ val refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testCoroutineScope,
+ mainDispatcher = IMMEDIATE,
+ repository = userRepository,
+ )
underTest =
UserInteractor(
+ applicationContext = context,
repository = userRepository,
controller = controller,
activityStarter = activityStarter,
@@ -69,142 +81,34 @@
KeyguardInteractor(
repository = keyguardRepository,
),
- )
- }
-
- @Test
- fun `actions - not actionable when locked and locked - no actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions).isEmpty()
- job.cancel()
- }
-
- @Test
- fun `actions - not actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
+ },
+ manager = manager,
+ applicationScope = testCoroutineScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = telephonyRepository,
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = IMMEDIATE,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testCoroutineScope,
+ mainDispatcher = IMMEDIATE,
+ backgroundDispatcher = IMMEDIATE,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
)
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
)
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and locked`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun selectUser() {
- val userId = 3
-
- underTest.selectUser(userId)
-
- verify(controller).onUserSelected(eq(userId), nullable())
- }
-
- @Test
- fun `executeAction - guest`() {
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- verify(controller).createAndSwitchToGuestUser(nullable())
- }
-
- @Test
- fun `executeAction - add user`() {
- underTest.executeAction(UserActionModel.ADD_USER)
-
- verify(controller).showAddUserDialog(nullable())
- }
-
- @Test
- fun `executeAction - add supervised user`() {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- verify(controller).startSupervisedUserActivity()
- }
-
- @Test
- fun `executeAction - manage users`() {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- verify(activityStarter).startActivity(any(), anyBoolean())
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
new file mode 100644
index 0000000..c3a9705
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+open class UserInteractorUnrefactoredTest : UserInteractorTest() {
+
+ override fun isRefactored(): Boolean {
+ return false
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ }
+
+ @Test
+ fun `actions - not actionable when locked and locked - no actions`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(UserActionModel.values().toList())
+ userRepository.setActionableWhenLocked(false)
+ keyguardRepository.setKeyguardShowing(true)
+
+ var actions: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions).isEmpty()
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - not actionable when locked and not locked`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
+ )
+ userRepository.setActionableWhenLocked(false)
+ keyguardRepository.setKeyguardShowing(false)
+
+ var actions: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - actionable when locked and not locked`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
+ )
+ userRepository.setActionableWhenLocked(true)
+ keyguardRepository.setKeyguardShowing(false)
+
+ var actions: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - actionable when locked and locked`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
+ )
+ userRepository.setActionableWhenLocked(true)
+ keyguardRepository.setKeyguardShowing(true)
+
+ var actions: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun selectUser() {
+ val userId = 3
+
+ underTest.selectUser(userId)
+
+ verify(controller).onUserSelected(eq(userId), nullable())
+ }
+
+ @Test
+ fun `executeAction - guest`() {
+ underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+ verify(controller).createAndSwitchToGuestUser(nullable())
+ }
+
+ @Test
+ fun `executeAction - add user`() {
+ underTest.executeAction(UserActionModel.ADD_USER)
+
+ verify(controller).showAddUserDialog(nullable())
+ }
+
+ @Test
+ fun `executeAction - add supervised user`() {
+ underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+ verify(controller).startSupervisedUserActivity()
+ }
+
+ @Test
+ fun `executeAction - manage users`() {
+ underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index ef4500d..0344e3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -17,17 +17,28 @@
package com.android.systemui.user.ui.viewmodel
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
import android.graphics.drawable.Drawable
+import android.os.UserManager
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
@@ -38,6 +49,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@@ -52,6 +64,11 @@
@Mock private lateinit var controller: UserSwitcherController
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
private lateinit var underTest: UserSwitcherViewModel
@@ -66,22 +83,60 @@
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
powerRepository = FakePowerRepository()
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
+ val scope = TestCoroutineScope()
+ val refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ repository = userRepository,
+ )
+ val guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = scope,
+ mainDispatcher = IMMEDIATE,
+ backgroundDispatcher = IMMEDIATE,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ )
+
underTest =
UserSwitcherViewModel.Factory(
userInteractor =
UserInteractor(
+ applicationContext = context,
repository = userRepository,
controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
- )
+ ),
+ featureFlags = featureFlags,
+ manager = manager,
+ applicationScope = scope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = IMMEDIATE,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
),
powerInteractor =
PowerInteractor(
repository = powerRepository,
),
+ featureFlags = featureFlags,
+ guestUserInteractor = guestUserInteractor,
)
.create(UserSwitcherViewModel::class.java)
}
@@ -97,6 +152,7 @@
image = USER_IMAGE,
isSelected = true,
isSelectable = true,
+ isGuest = false,
),
UserModel(
id = 1,
@@ -104,6 +160,7 @@
image = USER_IMAGE,
isSelected = false,
isSelectable = true,
+ isGuest = false,
),
UserModel(
id = 2,
@@ -111,6 +168,7 @@
image = USER_IMAGE,
isSelected = false,
isSelectable = false,
+ isGuest = false,
),
)
)
@@ -260,7 +318,7 @@
job.cancel()
}
- private fun setUsers(count: Int) {
+ private suspend fun setUsers(count: Int) {
userRepository.setUsers(
(0 until count).map { index ->
UserModel(
@@ -269,6 +327,7 @@
image = USER_IMAGE,
isSelected = index == 0,
isSelectable = true,
+ isGuest = false,
)
}
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
index dead159..e3cd9b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -1,12 +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.util.view
+import android.graphics.Rect
import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
@@ -25,6 +44,12 @@
location[0] = VIEW_LEFT
location[1] = VIEW_TOP
`when`(view.locationOnScreen).thenReturn(location)
+ doAnswer { invocation ->
+ val pos = invocation.arguments[0] as IntArray
+ pos[0] = VIEW_LEFT
+ pos[1] = VIEW_TOP
+ null
+ }.`when`(view).getLocationInWindow(any())
}
@Test
@@ -64,6 +89,18 @@
fun touchIsWithinView_yTooLarge_returnsFalse() {
assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
}
+
+ @Test
+ fun setRectToViewWindowLocation_rectHasLocation() {
+ val outRect = Rect()
+
+ viewUtil.setRectToViewWindowLocation(view, outRect)
+
+ assertThat(outRect.left).isEqualTo(VIEW_LEFT)
+ assertThat(outRect.right).isEqualTo(VIEW_RIGHT)
+ assertThat(outRect.top).isEqualTo(VIEW_TOP)
+ assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM)
+ }
}
private const val VIEW_LEFT = 30
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index aaf2188..3769f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +100,8 @@
private KeyguardManager mKeyguardManager;
@Mock
private ActivityManager mActivityManager;
+ @Mock
+ private DumpManager mDumpManager;
@Before
@@ -121,7 +124,7 @@
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
- mActivityManager, mCallback);
+ mActivityManager, mDumpManager, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -202,11 +205,12 @@
CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
+ DumpManager dumpManager,
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
- activityManager);
+ activityManager, dumpManager);
mCallbacks = callback;
ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 3434376..c254358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,14 +16,23 @@
package com.android.systemui.wallpapers;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.intThat;
import android.app.WallpaperManager;
import android.content.Context;
@@ -31,17 +40,25 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import org.junit.Before;
@@ -56,7 +73,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-@Ignore
public class ImageWallpaperTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
@@ -66,44 +82,86 @@
private static final int DISPLAY_HEIGHT = 1080;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+ @Mock
+ private DisplayManager mDisplayManager;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
private SurfaceHolder mSurfaceHolder;
@Mock
+ private Surface mSurface;
+ @Mock
private Context mMockContext;
+
@Mock
private Bitmap mWallpaperBitmap;
+ private int mBitmapWidth = 1;
+ private int mBitmapHeight = 1;
+
@Mock
private Handler mHandler;
@Mock
private FeatureFlags mFeatureFlags;
+ FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
+ FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+
private CountDownLatch mEventCountdown;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mEventCountdown = new CountDownLatch(1);
+ //mEventCountdown = new CountDownLatch(1);
- WallpaperManager wallpaperManager = mock(WallpaperManager.class);
+ // set up window manager
+ when(mWindowMetrics.getBounds()).thenReturn(
+ new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+
+ // set up display manager
+ doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
+ when(mMockContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+
+ // set up bitmap
+ when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
+ when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
+ when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
+
+ // set up wallpaper manager
+ when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
+ new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+ when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+ when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
+
+ // set up surface
+ when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
+ doNothing().when(mSurface).hwuiDestroy();
+
+ // TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
Resources resources = mock(Resources.class);
-
- when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
- when(mMockContext.getResources()).thenReturn(resources);
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
-
+ when(mMockContext.getResources()).thenReturn(resources);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
displayInfo.logicalHeight = DISPLAY_HEIGHT;
when(mMockContext.getDisplay()).thenReturn(
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
+ }
- when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
- when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
- when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
+ private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+ mBitmapWidth = bitmapWidth;
+ mBitmapHeight = bitmapHeight;
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFeatureFlags) {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
@@ -130,6 +188,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_normal() {
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
@@ -140,6 +199,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_low_resolution() {
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
@@ -150,6 +210,7 @@
}
@Test
+ @Ignore
public void testBitmapWallpaper_too_small() {
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
// Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
@@ -166,8 +227,7 @@
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
- when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
- when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
+ setBitmapDimensions(bmpWidth, bmpHeight);
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
doReturn(renderer).when(engineSpy).getRendererInstance();
@@ -177,4 +237,116 @@
assertWithMessage("setFixedSizeAllowed should have been called.").that(
mEventCountdown.getCount()).isEqualTo(0);
}
+
+
+ private ImageWallpaper createImageWallpaperCanvas() {
+ return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+ @Override
+ public Engine onCreateEngine() {
+ return new CanvasEngine() {
+ @Override
+ public Context getDisplayContext() {
+ return mMockContext;
+ }
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ @Override
+ public void setFixedSizeAllowed(boolean allowed) {
+ super.setFixedSizeAllowed(allowed);
+ assertWithMessage("mFixedSizeAllowed should be true").that(
+ allowed).isTrue();
+ }
+ };
+ }
+ };
+ }
+
+ private ImageWallpaper.CanvasEngine getSpyEngine() {
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
+ doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+ doAnswer(invocation -> {
+ ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
+ return null;
+ }).when(spyEngine).recomputeColorExtractorMiniBitmap();
+ return spyEngine;
+ }
+
+ @Test
+ public void testMinSurface() {
+
+ // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
+ testMinSurfaceHelper(8, 8);
+ testMinSurfaceHelper(100, 2000);
+ testMinSurfaceHelper(200, 1);
+ testMinSurfaceHelper(0, 1);
+ testMinSurfaceHelper(1, 0);
+ testMinSurfaceHelper(0, 0);
+ }
+
+ private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ engine.onCreate(mSurfaceHolder);
+
+ verify(mSurfaceHolder, times(1)).setFixedSize(
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
+ intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
+ }
+
+ @Test
+ public void testZeroBitmap() {
+ // test that a frame is never drawn with a 0 bitmap
+ testZeroBitmapHelper(0, 1);
+ testZeroBitmapHelper(1, 0);
+ testZeroBitmapHelper(0, 0);
+ }
+
+ private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
+
+ clearInvocations(mSurfaceHolder);
+ setBitmapDimensions(bitmapWidth, bitmapHeight);
+
+ ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
+ ImageWallpaper.CanvasEngine engine =
+ (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine spyEngine = spy(engine);
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ verify(spyEngine, never()).drawFrameOnCanvas(any());
+ }
+
+ @Test
+ public void testLoadDrawAndUnloadBitmap() {
+ setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
+
+ ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
+ spyEngine.onCreate(mSurfaceHolder);
+ spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
+ assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+
+ int n = 0;
+ while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+ n++;
+ assertThat(n).isAtMost(10);
+ mFakeBackgroundExecutor.runNextReady();
+ mFakeMainExecutor.runNextReady();
+ mFakeSystemClock.advanceTime(1000);
+ }
+
+ verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
+ assertThat(spyEngine.isBitmapLoaded()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
deleted file mode 100644
index 93f4f82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/ImageCanvasWallpaperRendererTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.wallpapers.canvas;
-
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.intThat;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.DisplayInfo;
-import android.view.SurfaceHolder;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ImageCanvasWallpaperRendererTest extends SysuiTestCase {
-
- private static final int MOBILE_DISPLAY_WIDTH = 720;
- private static final int MOBILE_DISPLAY_HEIGHT = 1600;
-
- @Mock
- private SurfaceHolder mMockSurfaceHolder;
-
- @Mock
- private DisplayInfo mMockDisplayInfo;
-
- @Mock
- private Bitmap mMockBitmap;
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- MockitoAnnotations.initMocks(this);
- }
-
- private void setDimensions(
- int bitmapWidth, int bitmapHeight,
- int displayWidth, int displayHeight) {
- when(mMockBitmap.getWidth()).thenReturn(bitmapWidth);
- when(mMockBitmap.getHeight()).thenReturn(bitmapHeight);
- mMockDisplayInfo.logicalWidth = displayWidth;
- mMockDisplayInfo.logicalHeight = displayHeight;
- }
-
- private void testMinDimensions(
- int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- renderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, times(1)).setFixedSize(
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_WIDTH)),
- intThat(greaterThanOrEqualTo(ImageCanvasWallpaperRenderer.MIN_SURFACE_HEIGHT)));
- }
-
- @Test
- public void testMinSurface() {
- // test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
- testMinDimensions(8, 8);
-
- testMinDimensions(100, 2000);
-
- testMinDimensions(200, 1);
- }
-
- private void testZeroDimensions(int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mMockSurfaceHolder);
- setDimensions(bitmapWidth, bitmapHeight,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_WIDTH,
- ImageCanvasWallpaperRendererTest.MOBILE_DISPLAY_HEIGHT);
-
- ImageCanvasWallpaperRenderer renderer =
- new ImageCanvasWallpaperRenderer(mMockSurfaceHolder);
- ImageCanvasWallpaperRenderer spyRenderer = spy(renderer);
- spyRenderer.drawFrame(mMockBitmap, true);
-
- verify(mMockSurfaceHolder, never()).setFixedSize(anyInt(), anyInt());
- verify(spyRenderer, never()).drawWallpaperWithCanvas(any());
- }
-
- @Test
- public void testZeroBitmap() {
- // test that updateSurfaceSize is not called with a bitmap of width 0 or height 0
- testZeroDimensions(
- 0, 1
- );
-
- testZeroDimensions(1, 0
- );
-
- testZeroDimensions(0, 0
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
new file mode 100644
index 0000000..76bff1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
@@ -0,0 +1,350 @@
+/*
+ * 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.wallpapers.canvas;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WallpaperColorExtractorTest extends SysuiTestCase {
+ private static final int LOW_BMP_WIDTH = 128;
+ private static final int LOW_BMP_HEIGHT = 128;
+ private static final int HIGH_BMP_WIDTH = 3000;
+ private static final int HIGH_BMP_HEIGHT = 4000;
+ private static final int VERY_LOW_BMP_WIDTH = 1;
+ private static final int VERY_LOW_BMP_HEIGHT = 1;
+ private static final int DISPLAY_WIDTH = 1920;
+ private static final int DISPLAY_HEIGHT = 1080;
+
+ private static final int PAGES_LOW = 4;
+ private static final int PAGES_HIGH = 7;
+
+ private static final int MIN_AREAS = 4;
+ private static final int MAX_AREAS = 10;
+
+ private int mMiniBitmapWidth;
+ private int mMiniBitmapHeight;
+
+ @Mock
+ private Executor mBackgroundExecutor;
+
+ private int mColorsProcessed;
+ private int mMiniBitmapUpdatedCount;
+ private int mActivatedCount;
+ private int mDeactivatedCount;
+
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+ MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mBackgroundExecutor).execute(any(Runnable.class));
+ }
+
+ private void resetCounters() {
+ mColorsProcessed = 0;
+ mMiniBitmapUpdatedCount = 0;
+ mActivatedCount = 0;
+ mDeactivatedCount = 0;
+ }
+
+ private Bitmap getMockBitmap(int width, int height) {
+ Bitmap bitmap = mock(Bitmap.class);
+ when(bitmap.getWidth()).thenReturn(width);
+ when(bitmap.getHeight()).thenReturn(height);
+ return bitmap;
+ }
+
+ private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+
+ WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ mBackgroundExecutor,
+ new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ @Override
+ public void onColorsProcessed(List<RectF> regions,
+ List<WallpaperColors> colors) {
+ assertThat(regions.size()).isEqualTo(colors.size());
+ mColorsProcessed += regions.size();
+ }
+
+ @Override
+ public void onMiniBitmapUpdated() {
+ mMiniBitmapUpdatedCount++;
+ }
+
+ @Override
+ public void onActivated() {
+ mActivatedCount++;
+ }
+
+ @Override
+ public void onDeactivated() {
+ mDeactivatedCount++;
+ }
+ });
+ WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+
+ doAnswer(invocation -> {
+ mMiniBitmapWidth = invocation.getArgument(1);
+ mMiniBitmapHeight = invocation.getArgument(2);
+ return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
+ }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+
+ doAnswer(invocation -> getMockBitmap(
+ invocation.getArgument(1),
+ invocation.getArgument(2)))
+ .when(spyWallpaperColorExtractor)
+ .createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+
+ doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
+ .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+
+ return spyWallpaperColorExtractor;
+ }
+
+ private RectF randomArea() {
+ float width = (float) Math.random();
+ float startX = (float) (Math.random() * (1 - width));
+ float height = (float) Math.random();
+ float startY = (float) (Math.random() * (1 - height));
+ return new RectF(startX, startY, startX + width, startY + height);
+ }
+
+ private List<RectF> listOfRandomAreas(int min, int max) {
+ int nAreas = randomBetween(min, max);
+ List<RectF> result = new ArrayList<>();
+ for (int i = 0; i < nAreas; i++) {
+ result.add(randomArea());
+ }
+ return result;
+ }
+
+ private int randomBetween(int minIncluded, int maxIncluded) {
+ return (int) (Math.random() * ((maxIncluded - minIncluded) + 1)) + minIncluded;
+ }
+
+ /**
+ * Test that for bitmaps of random dimensions, the mini bitmap is always created
+ * with either a width <= SMALL_SIDE or a height <= SMALL_SIDE
+ */
+ @Test
+ public void testMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
+ int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for bitmaps with both width and height <= SMALL_SIDE,
+ * the mini bitmap is always created with both width and height <= SMALL_SIDE
+ */
+ @Test
+ public void testSmallMiniBitmapCreation() {
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
+ int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
+ Bitmap bitmap = getMockBitmap(width, height);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
+ .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ }
+ }
+
+ /**
+ * Test that for a new color extractor with information
+ * (number of pages, display dimensions, wallpaper bitmap) given in random order,
+ * the colors are processed and all the callbacks are properly executed.
+ */
+ @Test
+ public void testNewColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ regions));
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mActivatedCount).isEqualTo(1);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that the method removeLocalColorAreas behaves properly and does not call
+ * the onDeactivated callback unless all color areas are removed.
+ */
+ @Test
+ public void testRemoveColors() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ int nSimulations = 10;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
+ List<Runnable> tasks = Arrays.asList(
+ () -> spyWallpaperColorExtractor.onPageChanged(nPages),
+ () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
+ () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ Collections.shuffle(tasks);
+ tasks.forEach(Runnable::run);
+
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ assertThat(mDeactivatedCount).isEqualTo(0);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+ }
+
+ /**
+ * Test that if we change some information (wallpaper bitmap, number of pages),
+ * the colors are correctly recomputed.
+ * Test that if we remove some color areas in the middle of the process,
+ * only the remaining areas are recomputed.
+ */
+ @Test
+ public void testRecomputeColorExtraction() {
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
+ List<RectF> regions = new ArrayList<>();
+ regions.addAll(regions1);
+ regions.addAll(regions2);
+ spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ assertThat(mActivatedCount).isEqualTo(1);
+ int nPages = PAGES_LOW;
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyWallpaperColorExtractor.onPageChanged(nPages);
+ spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+
+ int nSimulations = 20;
+ for (int i = 0; i < nSimulations; i++) {
+ resetCounters();
+
+ // verify that if we remove some regions, they are not recomputed after other changes
+ if (i == nSimulations / 2) {
+ regions.removeAll(regions2);
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ }
+
+ if (Math.random() >= 0.5) {
+ int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
+ if (nPagesNew == nPages) continue;
+ nPages = nPagesNew;
+ spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ } else {
+ Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ }
+ assertThat(mColorsProcessed).isEqualTo(regions.size());
+ }
+ spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ assertThat(mDeactivatedCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testCleanUp() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ doNothing().when(bitmap).recycle();
+ WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
+ spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
+ spyWallpaperColorExtractor.cleanUp();
+ spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ assertThat(mColorsProcessed).isEqualTo(0);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index c83189d..fa3cc99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -84,9 +84,14 @@
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
- mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
- mock(Executor.class), mock(DumpManager.class),
- mock(BroadcastDispatcherLogger.class), mock(UserTracker.class));
+ mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
+ mContext,
+ mContext.getMainExecutor(),
+ mock(Looper.class),
+ mock(Executor.class),
+ mock(DumpManager.class),
+ mock(BroadcastDispatcherLogger.class),
+ mock(UserTracker.class));
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
Instrumentation inst = spy(mRealInstrumentation);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index 53dcc8d..52e0c982 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -32,15 +32,25 @@
class FakeBroadcastDispatcher(
context: SysuiTestableContext,
- looper: Looper,
- executor: Executor,
+ mainExecutor: Executor,
+ broadcastRunningLooper: Looper,
+ broadcastRunningExecutor: Executor,
dumpManager: DumpManager,
logger: BroadcastDispatcherLogger,
userTracker: UserTracker
-) : BroadcastDispatcher(
- context, looper, executor, dumpManager, logger, userTracker, PendingRemovalStore(logger)) {
+) :
+ BroadcastDispatcher(
+ context,
+ mainExecutor,
+ broadcastRunningLooper,
+ broadcastRunningExecutor,
+ dumpManager,
+ logger,
+ userTracker,
+ PendingRemovalStore(logger)
+ ) {
- private val registeredReceivers = ArraySet<BroadcastReceiver>()
+ val registeredReceivers = ArraySet<BroadcastReceiver>()
override fun registerReceiverWithHandler(
receiver: BroadcastReceiver,
@@ -78,4 +88,4 @@
}
registeredReceivers.clear()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index c56fdb1..5d52be2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -16,6 +16,8 @@
package com.android.systemui.flags
+import java.io.PrintWriter
+
class FakeFeatureFlags : FeatureFlags {
private val booleanFlags = mutableMapOf<Int, Boolean>()
private val stringFlags = mutableMapOf<Int, String>()
@@ -106,6 +108,10 @@
}
}
+ override fun dump(writer: PrintWriter, args: Array<out String>?) {
+ // no-op
+ }
+
private fun flagName(flagId: Int): String {
return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 42b434a..725b1f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -44,6 +44,10 @@
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
+ override fun isKeyguardShowing(): Boolean {
+ return _isKeyguardShowing.value
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index b2b1764..9726bf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -26,20 +26,24 @@
/** A fake [UserTracker] to be used in tests. */
class FakeUserTracker(
- userId: Int = 0,
- userHandle: UserHandle = UserHandle.of(userId),
- userInfo: UserInfo = mock(),
- userProfiles: List<UserInfo> = emptyList(),
+ private var _userId: Int = 0,
+ private var _userHandle: UserHandle = UserHandle.of(_userId),
+ private var _userInfo: UserInfo = mock(),
+ private var _userProfiles: List<UserInfo> = emptyList(),
userContentResolver: ContentResolver = MockContentResolver(),
userContext: Context = mock(),
private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
) : UserTracker {
val callbacks = mutableListOf<UserTracker.Callback>()
- override val userId: Int = userId
- override val userHandle: UserHandle = userHandle
- override val userInfo: UserInfo = userInfo
- override val userProfiles: List<UserInfo> = userProfiles
+ override val userId: Int
+ get() = _userId
+ override val userHandle: UserHandle
+ get() = _userHandle
+ override val userInfo: UserInfo
+ get() = _userInfo
+ override val userProfiles: List<UserInfo>
+ get() = _userProfiles
override val userContentResolver: ContentResolver = userContentResolver
override val userContext: Context = userContext
@@ -55,4 +59,13 @@
override fun createCurrentUserContext(context: Context): Context {
return onCreateCurrentUserContext(context)
}
+
+ fun set(userInfos: List<UserInfo>, selectedUserIndex: Int) {
+ _userProfiles = userInfos
+ _userInfo = userInfos[selectedUserIndex]
+ _userId = _userInfo.id
+ _userHandle = UserHandle.of(_userId)
+
+ callbacks.forEach { it.onUserChanged(_userId, userContext) }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
new file mode 100644
index 0000000..59f24ef
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.telephony.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTelephonyRepository : TelephonyRepository {
+
+ private val _callState = MutableStateFlow(0)
+ override val callState: Flow<Int> = _callState.asStateFlow()
+
+ fun setCallState(value: Int) {
+ _callState.value = value
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 20f1e36..4df8aa4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -17,12 +17,18 @@
package com.android.systemui.user.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
+import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
@@ -34,21 +40,71 @@
private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
+ private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
+ override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
+ _userSwitcherSettings.asStateFlow()
+
+ private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
+ override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
+
+ private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
+ override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+
+ override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+
private val _isActionableWhenLocked = MutableStateFlow(false)
override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
private var _isGuestUserAutoCreated: Boolean = false
override val isGuestUserAutoCreated: Boolean
get() = _isGuestUserAutoCreated
- private var _isGuestUserResetting: Boolean = false
- override val isGuestUserResetting: Boolean
- get() = _isGuestUserResetting
+
+ override var isGuestUserResetting: Boolean = false
+
+ override val isGuestUserCreationScheduled = AtomicBoolean()
+
+ override var secondaryUserId: Int = UserHandle.USER_NULL
+
+ override var isRefreshUsersPaused: Boolean = false
+
+ var refreshUsersCallCount: Int = 0
+ private set
+
+ override fun refreshUsers() {
+ refreshUsersCallCount++
+ }
+
+ override fun getSelectedUserInfo(): UserInfo {
+ return checkNotNull(_selectedUserInfo.value)
+ }
+
+ override fun isSimpleUserSwitcher(): Boolean {
+ return _userSwitcherSettings.value.isSimpleUserSwitcher
+ }
+
+ fun setUserInfos(infos: List<UserInfo>) {
+ _userInfos.value = infos
+ }
+
+ suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+ check(_userInfos.value.contains(userInfo)) {
+ "Cannot select the following user, it is not in the list of user infos: $userInfo!"
+ }
+
+ _selectedUserInfo.value = userInfo
+ yield()
+ }
+
+ suspend fun setSettings(settings: UserSwitcherSettingsModel) {
+ _userSwitcherSettings.value = settings
+ yield()
+ }
fun setUsers(models: List<UserModel>) {
_users.value = models
}
- fun setSelectedUser(userId: Int) {
+ suspend fun setSelectedUser(userId: Int) {
check(_users.value.find { it.id == userId } != null) {
"Cannot select a user with ID $userId - no user with that ID found!"
}
@@ -62,6 +118,7 @@
}
}
)
+ yield()
}
fun setActions(models: List<UserActionModel>) {
@@ -75,8 +132,4 @@
fun setGuestUserAutoCreated(value: Boolean) {
_isGuestUserAutoCreated = value
}
-
- fun setGuestUserResetting(value: Boolean) {
- _isGuestUserResetting = value
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67ed..23c7a61 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -70,6 +70,10 @@
}
@Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ }
+
+ @Override
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 2ab28c6..043aff6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -203,6 +203,6 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
-private const val SPRING_STIFFNESS = 200.0f
+private const val SPRING_STIFFNESS = 600.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index abc4937..a94e4b9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1152,6 +1152,9 @@
}
private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+ if (packageInfo == null) {
+ return;
+ }
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index f523773..451a700 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -54,12 +54,19 @@
private static final String PROPERTY_PRIMARY_TAG =
"android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
- static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
+ @Nullable
+ static PackageInfo getPackageInfo(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
final PackageManager pm = context.getPackageManager();
final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
- return Binder.withCleanCallingIdentity(() ->
- pm.getPackageInfoAsUser(packageName, flags , userId));
+ return Binder.withCleanCallingIdentity(() -> {
+ try {
+ return pm.getPackageInfoAsUser(packageName, flags, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Package [" + packageName + "] is not found.");
+ return null;
+ }
+ });
}
static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index e81bab1..202f4775 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1831,7 +1831,7 @@
if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
|| mWindowManager.isKeyguardShowingAndNotOccluded()
|| !mPowerManager.isInteractive())) {
- Sandman.startDreamWhenDockedIfAppropriate(getContext());
+ mInjector.startDreamWhenDockedIfAppropriate(getContext());
}
}
@@ -2148,5 +2148,9 @@
public int getCallingUid() {
return Binder.getCallingUid();
}
+
+ public void startDreamWhenDockedIfAppropriate(Context context) {
+ Sandman.startDreamWhenDockedIfAppropriate(context);
+ }
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 6b731c3..c128b5e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -89,6 +89,7 @@
import android.os.UserManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -3100,7 +3101,7 @@
*/
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
@@ -3519,7 +3520,7 @@
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
@@ -4870,7 +4871,13 @@
* into launching arbitrary intents on the device via by tricking to click authenticator
* supplied entries in the system Settings app.
*/
- protected boolean checkKeyIntent(int authUid, Intent intent) {
+ protected boolean checkKeyIntent(int authUid, Bundle bundle) {
+ if (!checkKeyIntentParceledCorrectly(bundle)) {
+ EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
+ return false;
+ }
+
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
// Explicitly set an empty ClipData to ensure that we don't offer to
// promote any Uris contained inside for granting purposes
if (intent.getClipData() == null) {
@@ -4905,6 +4912,25 @@
}
}
+ /**
+ * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't
+ * violate our security policy.
+ *
+ * In particular we want to make sure the Authenticator doesn't trick users
+ * into launching arbitrary intents on the device via exploiting any other Parcel read/write
+ * mismatch problems.
+ */
+ private boolean checkKeyIntentParceledCorrectly(Bundle bundle) {
+ Parcel p = Parcel.obtain();
+ p.writeBundle(bundle);
+ p.setDataPosition(0);
+ Bundle simulateBundle = p.readBundle();
+ p.recycle();
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+ return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+ Intent.class)));
+ }
+
private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
String className = activityInfo.name;
return "android".equals(activityInfo.packageName) &&
@@ -5051,7 +5077,7 @@
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
if (!checkKeyIntent(
Binder.getCallingUid(),
- intent)) {
+ result)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"invalid intent in bundle returned");
return;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9840e0f..9669c06 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4219,7 +4219,8 @@
final String procName = r.processName;
HostingRecord hostingRecord = new HostingRecord(
HostingRecord.HOSTING_TYPE_SERVICE, r.instanceName,
- r.definingPackageName, r.definingUid, r.serviceInfo.processName);
+ r.definingPackageName, r.definingUid, r.serviceInfo.processName,
+ getHostingRecordTriggerType(r));
ProcessRecord app;
if (!isolated) {
@@ -4323,6 +4324,14 @@
return null;
}
+ private String getHostingRecordTriggerType(ServiceRecord r) {
+ if (Manifest.permission.BIND_JOB_SERVICE.equals(r.permission)
+ && r.mRecentCallingUid == SYSTEM_UID) {
+ return HostingRecord.TRIGGER_TYPE_JOB;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
+
private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
throws TransactionTooLargeException {
for (int i=r.bindings.size()-1; i>=0; i--) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2bd829d..046d72c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5009,7 +5009,8 @@
hostingRecord.getType(),
hostingRecord.getName(),
shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()));
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
return true;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 606a09c..207c10c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -36,6 +36,7 @@
import android.net.INetworkManagementEventObserver;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.os.BatteryConsumer;
import android.os.BatteryManagerInternal;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
@@ -2282,6 +2283,10 @@
pw.println(" --settings: dump the settings key/values related to batterystats");
pw.println(" --cpu: dump cpu stats for debugging purpose");
pw.println(" --power-profile: dump the power profile constants");
+ pw.println(" --usage: write battery usage stats. Optional arguments:");
+ pw.println(" --proto: output as a binary protobuffer");
+ pw.println(" --model power-profile: use the power profile model"
+ + " even if measured energy is available");
pw.println(" <package.name>: optional name of package to filter output by.");
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
@@ -2325,6 +2330,31 @@
}
}
+ private void dumpUsageStatsToProto(FileDescriptor fd, PrintWriter pw, int model,
+ boolean proto) {
+ awaitCompletion();
+ syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
+
+ BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includePowerModels();
+ if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
+ builder.powerProfileModeledOnly();
+ }
+ BatteryUsageStatsQuery query = builder.build();
+ synchronized (mStats) {
+ mStats.prepareForDumpLocked();
+ BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(query);
+ if (proto) {
+ batteryUsageStats.dumpToProto(fd);
+ } else {
+ batteryUsageStats.dump(pw, "");
+ }
+ }
+ }
+
private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
i++;
if (i >= args.length) {
@@ -2478,6 +2508,35 @@
} else if ("--power-profile".equals(arg)) {
dumpPowerProfile(pw);
return;
+ } else if ("--usage".equals(arg)) {
+ int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
+ boolean proto = false;
+ for (int j = i + 1; j < args.length; j++) {
+ switch (args[j]) {
+ case "--proto":
+ proto = true;
+ break;
+ case "--model": {
+ if (j + 1 < args.length) {
+ j++;
+ if ("power-profile".equals(args[j])) {
+ model = BatteryConsumer.POWER_MODEL_POWER_PROFILE;
+ } else {
+ pw.println("Unknown power model: " + args[j]);
+ dumpHelp(pw);
+ return;
+ }
+ } else {
+ pw.println("--model without a value");
+ dumpHelp(pw);
+ return;
+ }
+ break;
+ }
+ }
+ }
+ dumpUsageStatsToProto(fd, pw, model, proto);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 5856949..c235e05 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -519,7 +519,7 @@
final Object curReceiver = r.receivers.get(curIndex);
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- ActivityManagerService.getShortAction(r.intent.getAction()),
+ r.intent.getAction(),
curReceiver instanceof BroadcastFilter
? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
: BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
@@ -692,7 +692,7 @@
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- ActivityManagerService.getShortAction(intent.getAction()),
+ intent.getAction(),
BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
dispatchDelay, receiveDelay, 0 /* finish_delay */);
@@ -1921,7 +1921,7 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction()),
+ r.intent.getAction(), getHostingRecordTriggerType(r)),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -1944,6 +1944,16 @@
mPendingBroadcastRecvIndex = recIdx;
}
+ private String getHostingRecordTriggerType(BroadcastRecord r) {
+ if (r.alarm) {
+ return HostingRecord.TRIGGER_TYPE_ALARM;
+ } else if (r.pushMessage) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+ } else if (r.pushMessageOverQuota) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
@Nullable
private String getTargetPackage(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528b..baaae1d 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -71,6 +71,8 @@
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
+ final boolean pushMessage; // originated from a push message?
+ final boolean pushMessageOverQuota; // originated from a push message which was over quota?
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
@@ -309,6 +311,8 @@
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
+ pushMessage = options != null && options.isPushMessagingBroadcast();
+ pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
}
/**
@@ -362,6 +366,8 @@
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
+ pushMessage = from.pushMessage;
+ pushMessageOverQuota = from.pushMessageOverQuota;
}
/**
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index f88a8ce..30811a1 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -16,10 +16,30 @@
package com.android.server.am;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
-import android.os.ProcessStartTime;
/**
* This class describes various information required to start a process.
@@ -32,6 +52,9 @@
*
* The {@code mHostingZygote} field describes from which Zygote the new process should be spawned.
*
+ * The {@code mTriggerType} field describes the trigger that started this processs. This could be
+ * an alarm or a push-message for a broadcast, for example. This is purely for logging and stats.
+ *
* {@code mDefiningPackageName} contains the packageName of the package that defines the
* component we want to start; this can be different from the packageName and uid in the
* ApplicationInfo that we're creating the process with, in case the service is a
@@ -71,7 +94,13 @@
public static final String HOSTING_TYPE_TOP_ACTIVITY = "top-activity";
public static final String HOSTING_TYPE_EMPTY = "";
- private @NonNull final String mHostingType;
+ public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
+ public static final String TRIGGER_TYPE_ALARM = "alarm";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
+ public static final String TRIGGER_TYPE_JOB = "job";
+
+ @NonNull private final String mHostingType;
private final String mHostingName;
private final int mHostingZygote;
private final String mDefiningPackageName;
@@ -79,11 +108,12 @@
private final boolean mIsTopApp;
private final String mDefiningProcessName;
@Nullable private final String mAction;
+ @NonNull private final String mTriggerType;
public HostingRecord(@NonNull String hostingType) {
this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
-1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
- null /* action */);
+ null /* action */, TRIGGER_TYPE_UNKNOWN);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName) {
@@ -91,22 +121,24 @@
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
- @Nullable String action) {
+ @Nullable String action, @Nullable String triggerType) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */,
- null /* definingProcessName */, action);
+ null /* definingProcessName */, action, triggerType);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
- String definingPackageName, int definingUid, String definingProcessName) {
+ String definingPackageName, int definingUid, String definingProcessName,
+ String triggerType) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
- definingUid, false /* isTopApp */, definingProcessName, null /* action */);
+ definingUid, false /* isTopApp */, definingProcessName, null /* action */,
+ triggerType);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
- null /* definingProcessName */, null /* action */);
+ null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
public HostingRecord(@NonNull String hostingType, String hostingName) {
@@ -121,12 +153,12 @@
private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote) {
this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
-1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
- null /* action */);
+ null /* action */, TRIGGER_TYPE_UNKNOWN);
}
private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote,
String definingPackageName, int definingUid, boolean isTopApp,
- String definingProcessName, @Nullable String action) {
+ String definingProcessName, @Nullable String action, String triggerType) {
mHostingType = hostingType;
mHostingName = hostingName;
mHostingZygote = hostingZygote;
@@ -135,6 +167,7 @@
mIsTopApp = isTopApp;
mDefiningProcessName = definingProcessName;
mAction = action;
+ mTriggerType = triggerType;
}
public @NonNull String getType() {
@@ -188,6 +221,11 @@
return mAction;
}
+ /** Returns the type of trigger that led to this process start. */
+ public @NonNull String getTriggerType() {
+ return mTriggerType;
+ }
+
/**
* Creates a HostingRecord for a process that must spawn from the webview zygote
* @param hostingName name of the component to be hosted in this process
@@ -197,7 +235,7 @@
String definingPackageName, int definingUid, String definingProcessName) {
return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
WEBVIEW_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
- definingProcessName, null /* action */);
+ definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
/**
@@ -211,7 +249,7 @@
int definingUid, String definingProcessName) {
return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
APP_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
- definingProcessName, null /* action */);
+ definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
/**
@@ -236,35 +274,55 @@
public static int getHostingTypeIdStatsd(@NonNull String hostingType) {
switch(hostingType) {
case HOSTING_TYPE_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
case HOSTING_TYPE_ADDED_APPLICATION:
- return ProcessStartTime.HOSTING_TYPE_ADDED_APPLICATION;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
case HOSTING_TYPE_BACKUP:
- return ProcessStartTime.HOSTING_TYPE_BACKUP;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
case HOSTING_TYPE_BROADCAST:
- return ProcessStartTime.HOSTING_TYPE_BROADCAST;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
case HOSTING_TYPE_CONTENT_PROVIDER:
- return ProcessStartTime.HOSTING_TYPE_CONTENT_PROVIDER;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
case HOSTING_TYPE_LINK_FAIL:
- return ProcessStartTime.HOSTING_TYPE_LINK_FAIL;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
case HOSTING_TYPE_ON_HOLD:
- return ProcessStartTime.HOSTING_TYPE_ON_HOLD;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
case HOSTING_TYPE_NEXT_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_NEXT_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
case HOSTING_TYPE_NEXT_TOP_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
case HOSTING_TYPE_RESTART:
- return ProcessStartTime.HOSTING_TYPE_RESTART;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
case HOSTING_TYPE_SERVICE:
- return ProcessStartTime.HOSTING_TYPE_SERVICE;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
case HOSTING_TYPE_SYSTEM:
- return ProcessStartTime.HOSTING_TYPE_SYSTEM;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
case HOSTING_TYPE_TOP_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_TOP_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
case HOSTING_TYPE_EMPTY:
- return ProcessStartTime.HOSTING_TYPE_EMPTY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
default:
- return ProcessStartTime.HOSTING_TYPE_UNKNOWN;
+ return PROCESS_START_TIME__TYPE__UNKNOWN;
+ }
+ }
+
+ /**
+ * Map the string triggerType to enum TriggerType defined in ProcessStartTime proto.
+ * @param triggerType
+ * @return enum TriggerType defined in ProcessStartTime proto
+ */
+ public static int getTriggerTypeForStatsd(@NonNull String triggerType) {
+ switch(triggerType) {
+ case TRIGGER_TYPE_ALARM:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+ case TRIGGER_TYPE_PUSH_MESSAGE:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE;
+ case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ case TRIGGER_TYPE_JOB:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_JOB;
+ default:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 733a412..617f891 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,10 +41,12 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
@@ -1185,6 +1187,8 @@
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
@@ -1202,7 +1206,7 @@
mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
device -> onMuteAwaitConnectionTimeout(device));
- mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+ mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1308,6 +1312,7 @@
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+ intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
Context.RECEIVER_EXPORTED);
@@ -1927,13 +1932,7 @@
if (state == AudioService.CONNECTION_STATE_CONNECTED) {
// DEVICE_OUT_HDMI is now connected
if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
if (isPlatformTelevision()) {
@@ -3822,8 +3821,9 @@
}
private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+ private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
- private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
+ private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
new IPlaybackConfigDispatcher.Stub() {
@Override
public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3836,19 +3836,26 @@
private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
boolean voiceActive = false;
+ boolean mediaActive = false;
for (AudioPlaybackConfiguration config : configs) {
final int usage = config.getAudioAttributes().getUsage();
- if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
- || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
- && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (!config.isActive()) {
+ continue;
+ }
+ if (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+ || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
voiceActive = true;
- break;
+ }
+ if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+ mediaActive = true;
}
}
if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
updateHearingAidVolumeOnVoiceActivityUpdate();
}
-
+ if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
+ scheduleMusicActiveCheck();
+ }
// Update playback active state for all apps in audio mode stack.
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
@@ -6031,30 +6038,52 @@
return mContentResolver;
}
+ private void scheduleMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ cancelMusicActiveCheck();
+ mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+ new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime()
+ + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+ }
+ }
+
+ private void cancelMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (mMusicActiveIntent != null) {
+ mAlarmManager.cancel(mMusicActiveIntent);
+ mMusicActiveIntent = null;
+ }
+ }
+ }
private void onCheckMusicActive(String caller) {
synchronized (mSafeMediaVolumeStateLock) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-
- if (mSafeMediaVolumeDevices.contains(device)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ if (mSafeMediaVolumeDevices.contains(device)
+ && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ scheduleMusicActiveCheck();
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
- && (index > safeMediaVolumeIndex(device))) {
+ if (index > safeMediaVolumeIndex(device)) {
// Approximate cumulative active music time
- mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+ long curTimeMs = SystemClock.elapsedRealtime();
+ if (mLastMusicActiveTimeMs != 0) {
+ mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+ }
+ mLastMusicActiveTimeMs = curTimeMs;
+ Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
setSafeMediaVolumeEnabled(true, caller);
mMusicActiveMs = 0;
}
saveMusicActiveMs();
}
+ } else {
+ cancelMusicActiveCheck();
+ mLastMusicActiveTimeMs = 0;
}
}
}
@@ -6123,6 +6152,7 @@
} else {
// We have existing playback time recorded, already confirmed.
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mLastMusicActiveTimeMs = 0;
}
}
} else {
@@ -8632,13 +8662,7 @@
@VisibleForTesting
public void checkMusicActive(int deviceType, String caller) {
if (mSafeMediaVolumeDevices.contains(deviceType)) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
@@ -8763,6 +8787,8 @@
suspendedPackages[i], suspendedUids[i]);
}
}
+ } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
+ onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
}
}
} // end class AudioServiceBroadcastReceiver
@@ -9708,12 +9734,20 @@
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
private int mMusicActiveMs;
+ private long mLastMusicActiveTimeMs = 0;
+ private PendingIntent mMusicActiveIntent = null;
+ private AlarmManager mAlarmManager;
+
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
// check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
+ private static final String ACTION_CHECK_MUSIC_ACTIVE =
+ AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
private int safeMediaVolumeIndex(int device) {
if (!mSafeMediaVolumeDevices.contains(device)) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -9735,14 +9769,9 @@
} else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
mMusicActiveMs = 1; // nonzero = confirmed
+ mLastMusicActiveTimeMs = 0;
saveMusicActiveMs();
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
+ scheduleMusicActiveCheck();
}
}
}
@@ -9784,7 +9813,9 @@
public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
synchronized (mSafeMediaVolumeStateLock) {
+ final long identity = Binder.clearCallingIdentity();
setSafeMediaVolumeEnabled(false, callingPackage);
+ Binder.restoreCallingIdentity(identity);
if (mPendingVolumeCommand != null) {
onSetStreamVolume(mPendingVolumeCommand.mStreamType,
mPendingVolumeCommand.mIndex,
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a7d3729..40e28da 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -592,10 +592,14 @@
}
pw.println();
+ pw.println(" mAmbientBrightnessThresholds=");
mAmbientBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholds=");
mScreenBrightnessThresholds.dump(pw);
+ pw.println(" mScreenBrightnessThresholdsIdle=");
mScreenBrightnessThresholdsIdle.dump(pw);
- mScreenBrightnessThresholdsIdle.dump(pw);
+ pw.println(" mAmbientBrightnessThresholdsIdle=");
+ mAmbientBrightnessThresholdsIdle.dump(pw);
}
private String configStateToString(int state) {
@@ -860,6 +864,7 @@
Slog.d(TAG, "updateAmbientLux: "
+ ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4165186..1ae37bb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -27,6 +27,7 @@
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.Spline;
import android.view.DisplayAddress;
@@ -51,7 +52,7 @@
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.Thresholds;
+import com.android.server.display.config.ThresholdPoint;
import com.android.server.display.config.XmlParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -188,42 +189,153 @@
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
* <ambientLightHorizonShort>2001</ambientLightHorizonShort>
*
- * <displayBrightnessChangeThresholds> // Thresholds for screen changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholds>
- *
- * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
- * <brighteningThresholds> // Thresholds for active mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholds>
- *
- * <displayBrightnessChangeThresholdsIdle> // Thresholds for screen changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
- * </darkeningThresholds>
- * </displayBrightnessChangeThresholdsIdle>
- *
- * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
- * <brighteningThresholds> // Thresholds for idle mode brightness changes.
- * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
- * </brighteningThresholds>
- * <darkeningThresholds>
- * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
- * </darkeningThresholds>
- * </ambientBrightnessChangeThresholdsIdle>
- *
+ * <ambientBrightnessChangeThresholds> // Thresholds for lux changes
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen.
+ * <minimum>10</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>100</threshold><percentage>14</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>200</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+* // Minimum change needed in ambient brightness to darken screen.
+ * <minimum>30</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>15</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>300</threshold><percentage>16</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>400</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholds>
+ * <displayBrightnessChangeThresholds> // Thresholds for screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen.
+ * <minimum>0.1</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold>
+ * <percentage>9</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.10</threshold>
+ * <percentage>10</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.20</threshold>
+ * <percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen.
+ * <minimum>0.3</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>11</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.11</threshold><percentage>12</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.21</threshold><percentage>13</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholds>
+ * <ambientBrightnessChangeThresholdsIdle> // Thresholds for lux changes in idle mode
+ * <brighteningThresholds>
+ * // Minimum change needed in ambient brightness to brighten screen in idle mode
+ * <minimum>20</minimum>
+ * // Percentage increase of lux needed to increase the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>500</threshold><percentage>22</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>600</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in ambient brightness to darken screen in idle mode
+ * <minimum>40</minimum>
+ * // Percentage increase of lux needed to decrease the screen brightness at a lux range
+ * // above the specified threshold whilst in idle mode.
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>23</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>700</threshold><percentage>24</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>800</threshold><percentage>25</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholdsIdle>
+ * <displayBrightnessChangeThresholdsIdle> // Thresholds for idle screen brightness changes
+ * <brighteningThresholds>
+ * // Minimum change needed in screen brightness to brighten screen in idle mode
+ * <minimum>0.2</minimum>
+ * // Percentage increase of screen brightness needed to increase the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>17</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.12</threshold><percentage>18</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.22</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * // Minimum change needed in screen brightness to darken screen in idle mode
+ * <minimum>0.4</minimum>
+ * // Percentage increase of screen brightness needed to decrease the screen brightness
+ * // at a lux range above the specified threshold whilst in idle mode
+ * <brightnessThresholdPoints>
+ * <brightnessThresholdPoint>
+ * <threshold>0</threshold><percentage>19</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.13</threshold><percentage>20</percentage>
+ * </brightnessThresholdPoint>
+ * <brightnessThresholdPoint>
+ * <threshold>0.23</threshold><percentage>21</percentage>
+ * </brightnessThresholdPoint>
+ * </brightnessThresholdPoints>
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholdsIdle>
* </displayConfiguration>
* }
* </pre>
@@ -247,6 +359,13 @@
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+ private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
@@ -344,6 +463,31 @@
private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
private float mAmbientLuxDarkeningMinThreshold = 0.0f;
private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
+
+ // Screen brightness thresholds levels & percentages
+ private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Screen brightness thresholds levels & percentages for idle mode
+ private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
+ private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
+ private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages
+ private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+ // Ambient brightness thresholds levels & percentages for idle mode
+ private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
+ private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
+ private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
@@ -684,7 +828,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThreshold() {
return mAmbientLuxBrighteningMinThreshold;
@@ -693,7 +837,7 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThreshold() {
return mAmbientLuxDarkeningMinThreshold;
@@ -702,7 +846,7 @@
/**
* The minimum value for the ambient lux increase for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxBrighteningMinThresholdIdle() {
return mAmbientLuxBrighteningMinThresholdIdle;
@@ -711,12 +855,262 @@
/**
* The minimum value for the ambient lux decrease for a screen brightness change to actually
* occur while in idle screen brightness mode.
- * @return float value in brightness scale of 0 - 1.
+ * @return float value in lux.
*/
public float getAmbientLuxDarkeningMinThresholdIdle() {
return mAmbientLuxDarkeningMinThresholdIdle;
}
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n]
+ * level[MAX] <= value = mScreenBrighteningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentages applies
+ */
+ public float[] getScreenBrighteningLevels() {
+ return mScreenBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenBrighteningPercentages() {
+ return mScreenBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n]
+ * level[MAX] <= value = mScreenDarkeningPercentages[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentages applies
+ */
+ public float[] getScreenDarkeningLevels() {
+ return mScreenDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getScreenDarkeningPercentages() {
+ return mScreenDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold
+ * percentage applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentages applies
+ */
+ public float[] getAmbientBrighteningLevels() {
+ return mAmbientBrighteningLevels;
+ }
+
+ /**
+ * The array that describes the ambient brightening threshold percentage change at each ambient
+ * brightness level described in mAmbientBrighteningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentages() {
+ return mAmbientBrighteningPercentages;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentages[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentages applies
+ */
+ public float[] getAmbientDarkeningLevels() {
+ return mAmbientDarkeningLevels;
+ }
+
+ /**
+ * The array that describes the ambient darkening threshold percentage change at each ambient
+ * brightness level described in mAmbientDarkeningLevels.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentages() {
+ return mAmbientDarkeningPercentages;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenBrighteningPercentagesIdle applies
+ */
+ public float[] getScreenBrighteningLevelsIdle() {
+ return mScreenBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen brightening threshold percentage change at each screen
+ * brightness level described in mScreenBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness increase required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenBrighteningPercentagesIdle() {
+ return mScreenBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of screen brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current screen brightness value
+ * level = mScreenDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX]
+ *
+ * @return the screen brightness levels between 0.0 and 1.0 for which each
+ * mScreenDarkeningPercentagesIdle applies
+ */
+ public float[] getScreenDarkeningLevelsIdle() {
+ return mScreenDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the screen darkening threshold percentage change at each screen
+ * brightness level described in mScreenDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of brightness decrease required in order for the
+ * screen brightness to change while in idle mode.
+ */
+ public float[] getScreenDarkeningPercentagesIdle() {
+ return mScreenDarkeningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientBrighteningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientBrighteningPercentagesIdle applies
+ */
+ public float[] getAmbientBrighteningLevelsIdle() {
+ return mAmbientBrighteningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientBrighteningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness increase required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientBrighteningPercentagesIdle() {
+ return mAmbientBrighteningPercentagesIdle;
+ }
+
+ /**
+ * The array that describes the range of ambient brightness that each threshold percentage
+ * applies within whilst in idle screen brightness mode.
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current ambient brightness value
+ * level = mAmbientDarkeningLevelsIdle
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n]
+ * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX]
+ *
+ * @return the ambient brightness levels from 0 lux upwards for which each
+ * mAmbientDarkeningPercentagesIdle applies
+ */
+ public float[] getAmbientDarkeningLevelsIdle() {
+ return mAmbientDarkeningLevelsIdle;
+ }
+
+ /**
+ * The array that describes the ambient brightness threshold percentage change whilst in
+ * idle screen brightness mode at each ambient brightness level described in
+ * mAmbientDarkeningLevelsIdle.
+ *
+ * @return the percentages between 0 and 100 of ambient brightness decrease required in order
+ * for the screen brightness to change
+ */
+ public float[] getAmbientDarkeningPercentagesIdle() {
+ return mAmbientDarkeningPercentagesIdle;
+ }
+
SensorData getAmbientLightSensor() {
return mAmbientLightSensor;
}
@@ -812,14 +1206,17 @@
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ + "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
+ ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
+ ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
+ ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
+ + "\n"
+ ", mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ + "\n"
+ ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+ ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
+ ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
@@ -829,6 +1226,41 @@
+ ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
+ ", mAmbientLuxBrighteningMinThresholdIdle="
+ mAmbientLuxBrighteningMinThresholdIdle
+ + "\n"
+ + ", mScreenBrighteningLevels=" + Arrays.toString(
+ mScreenBrighteningLevels)
+ + ", mScreenBrighteningPercentages=" + Arrays.toString(
+ mScreenBrighteningPercentages)
+ + ", mScreenDarkeningLevels=" + Arrays.toString(
+ mScreenDarkeningLevels)
+ + ", mScreenDarkeningPercentages=" + Arrays.toString(
+ mScreenDarkeningPercentages)
+ + ", mAmbientBrighteningLevels=" + Arrays.toString(
+ mAmbientBrighteningLevels)
+ + ", mAmbientBrighteningPercentages=" + Arrays.toString(
+ mAmbientBrighteningPercentages)
+ + ", mAmbientDarkeningLevels=" + Arrays.toString(
+ mAmbientDarkeningLevels)
+ + ", mAmbientDarkeningPercentages=" + Arrays.toString(
+ mAmbientDarkeningPercentages)
+ + "\n"
+ + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+ mAmbientBrighteningLevelsIdle)
+ + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
+ mAmbientBrighteningPercentagesIdle)
+ + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
+ mAmbientDarkeningLevelsIdle)
+ + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
+ mAmbientDarkeningPercentagesIdle)
+ + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
+ mScreenBrighteningLevelsIdle)
+ + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
+ mScreenBrighteningPercentagesIdle)
+ + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
+ mScreenDarkeningLevelsIdle)
+ + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
+ mScreenDarkeningPercentagesIdle)
+ + "\n"
+ ", mAmbientLightSensor=" + mAmbientLightSensor
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -914,6 +1346,7 @@
loadBrightnessMapFromConfigXml();
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
+ loadBrightnessChangeThresholdsFromXml();
setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
mLoadedFrom = "<config.xml>";
@@ -1454,91 +1887,287 @@
}
}
+ private void loadBrightnessChangeThresholdsFromXml() {
+ loadBrightnessChangeThresholds(/* config= */ null);
+ }
+
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
- Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
- Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
- Thresholds displayBrightnessThresholdsIdle =
- config.getDisplayBrightnessChangeThresholdsIdle();
- Thresholds ambientBrightnessThresholdsIdle =
- config.getAmbientBrightnessChangeThresholdsIdle();
-
- loadDisplayBrightnessThresholds(displayBrightnessThresholds);
- loadAmbientBrightnessThresholds(ambientBrightnessThresholds);
- loadIdleDisplayBrightnessThresholds(displayBrightnessThresholdsIdle);
- loadIdleAmbientBrightnessThresholds(ambientBrightnessThresholdsIdle);
+ loadDisplayBrightnessThresholds(config);
+ loadAmbientBrightnessThresholds(config);
+ loadDisplayBrightnessThresholdsIdle(config);
+ loadAmbientBrightnessThresholdsIdle(config);
}
- private void loadDisplayBrightnessThresholds(Thresholds displayBrightnessThresholds) {
- if (displayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreen =
- displayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreen =
- displayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreen = null;
+ BrightnessThresholds darkeningScreen = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
+ brighteningScreen =
+ config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningScreen =
+ config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
- if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
- mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
- }
- if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
- mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
- }
+ }
+
+ // Screen bright/darkening threshold levels for active mode
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+
+ mScreenBrighteningLevels = screenBrighteningPair.first;
+ mScreenBrighteningPercentages = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreen,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevels = screenDarkeningPair.first;
+ mScreenDarkeningPercentages = screenDarkeningPair.second;
+
+ // Screen bright/darkening threshold minimums for active mode
+ if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
+ mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
+ }
+ if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
+ mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
}
}
- private void loadAmbientBrightnessThresholds(Thresholds ambientBrightnessThresholds) {
- if (ambientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLux =
- ambientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLux =
- ambientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
+ // Ambient Brightness Threshold Levels
+ BrightnessThresholds brighteningAmbientLux = null;
+ BrightnessThresholds darkeningAmbientLux = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
+ brighteningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
+ darkeningAmbientLux =
+ config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
+ }
- final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
- final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+ // Ambient bright/darkening threshold levels for active mode
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevels = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentages = ambientBrighteningPair.second;
- if (ambientBrighteningThreshold != null) {
- mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
- }
- if (ambientDarkeningThreshold != null) {
- mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
- }
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLux,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevels = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentages = ambientDarkeningPair.second;
+
+ // Ambient bright/darkening threshold minimums for active/idle mode
+ if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThreshold =
+ brighteningAmbientLux.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
}
}
- private void loadIdleDisplayBrightnessThresholds(Thresholds idleDisplayBrightnessThresholds) {
- if (idleDisplayBrightnessThresholds != null) {
- BrightnessThresholds brighteningScreenIdle =
- idleDisplayBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningScreenIdle =
- idleDisplayBrightnessThresholds.getDarkeningThresholds();
+ private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningScreenIdle = null;
+ BrightnessThresholds darkeningScreenIdle = null;
+ if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
+ brighteningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningScreenIdle =
+ config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningScreenIdle != null
- && brighteningScreenIdle.getMinimum() != null) {
- mScreenBrighteningMinThresholdIdle =
- brighteningScreenIdle.getMinimum().floatValue();
- }
- if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
- mScreenDarkeningMinThresholdIdle =
- darkeningScreenIdle.getMinimum().floatValue();
- }
+ Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
+ mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
+
+ Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningScreenIdle,
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ /* potentialOldBrightnessScale= */ true);
+ mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
+ mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
+
+ if (brighteningScreenIdle != null
+ && brighteningScreenIdle.getMinimum() != null) {
+ mScreenBrighteningMinThresholdIdle =
+ brighteningScreenIdle.getMinimum().floatValue();
+ }
+ if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
+ mScreenDarkeningMinThresholdIdle =
+ darkeningScreenIdle.getMinimum().floatValue();
}
}
- private void loadIdleAmbientBrightnessThresholds(Thresholds idleAmbientBrightnessThresholds) {
- if (idleAmbientBrightnessThresholds != null) {
- BrightnessThresholds brighteningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getBrighteningThresholds();
- BrightnessThresholds darkeningAmbientLuxIdle =
- idleAmbientBrightnessThresholds.getDarkeningThresholds();
+ private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
+ BrightnessThresholds brighteningAmbientLuxIdle = null;
+ BrightnessThresholds darkeningAmbientLuxIdle = null;
+ if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
+ brighteningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
+ darkeningAmbientLuxIdle =
+ config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
+ }
- if (brighteningAmbientLuxIdle != null
- && brighteningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxBrighteningMinThresholdIdle =
- brighteningAmbientLuxIdle.getMinimum().floatValue();
+ Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
+ brighteningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
+ mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
+ mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
+
+ Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
+ darkeningAmbientLuxIdle,
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
+ mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
+ mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
+
+ if (brighteningAmbientLuxIdle != null
+ && brighteningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxBrighteningMinThresholdIdle =
+ brighteningAmbientLuxIdle.getMinimum().floatValue();
+ }
+
+ if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
+ mAmbientLuxDarkeningMinThresholdIdle =
+ darkeningAmbientLuxIdle.getMinimum().floatValue();
+ }
+ }
+
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
+ float[] defaultPercentage) {
+ return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
+ configFallbackPercentage, defaultLevels, defaultPercentage, false);
+ }
+
+ // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+ // percentages for brightness levels at or above the lux value.
+ // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+ // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+ // rest of the framework. Values were also defined in different units (permille vs percent).
+ private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPermille,
+ float[] defaultLevels, float[] defaultPercentage,
+ boolean potentialOldBrightnessScale) {
+ if (thresholds != null
+ && thresholds.getBrightnessThresholdPoints() != null
+ && thresholds.getBrightnessThresholdPoints()
+ .getBrightnessThresholdPoint().size() != 0) {
+
+ // The level and percentages arrays are equal length in the ddc (new system)
+ List<ThresholdPoint> points =
+ thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+ final int size = points.size();
+
+ float[] thresholdLevels = new float[size];
+ float[] thresholdPercentages = new float[size];
+
+ int i = 0;
+ for (ThresholdPoint point : points) {
+ thresholdLevels[i] = point.getThreshold().floatValue();
+ thresholdPercentages[i] = point.getPercentage().floatValue();
+ i++;
}
- if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxDarkeningMinThresholdIdle =
- darkeningAmbientLuxIdle.getMinimum().floatValue();
+ return new Pair<>(thresholdLevels, thresholdPercentages);
+ } else {
+ // The level and percentages arrays are unequal length in config.xml (old system)
+ // We prefix the array with a 0 value to ensure they can be handled consistently
+ // with the new system.
+
+ // Load levels array
+ int[] configThresholdArray = mContext.getResources().getIntArray(
+ configFallbackThreshold);
+ int configThresholdsSize;
+ if (configThresholdArray == null || configThresholdArray.length == 0) {
+ configThresholdsSize = 1;
+ } else {
+ configThresholdsSize = configThresholdArray.length + 1;
+ }
+
+
+ // Load percentage array
+ int[] configPermille = mContext.getResources().getIntArray(
+ configFallbackPermille);
+
+ // Ensure lengths match up
+ boolean emptyArray = configPermille == null || configPermille.length == 0;
+ if (emptyArray && configThresholdsSize == 1) {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ if (emptyArray || configPermille.length != configThresholdsSize) {
+ throw new IllegalArgumentException(
+ "Brightness threshold arrays do not align in length");
+ }
+
+ // Calculate levels array
+ float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+ // Start at 1, so that 0 index value is 0.0f (default)
+ for (int i = 1; i < configThresholdsSize; i++) {
+ configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+ }
+ if (potentialOldBrightnessScale) {
+ configThresholdWithZeroPrefixed =
+ constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+ }
+
+ // Calculate percentages array
+ float[] configPercentage = new float[configThresholdsSize];
+ for (int i = 0; i < configPermille.length; i++) {
+ configPercentage[i] = configPermille[i] / 10.0f;
+ }
+ return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+ }
+ }
+
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+ * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+ /* maxValueInclusive= */ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ for (float v : configArray) {
+ if (v < minValueInclusive || v > maxValueInclusive) {
+ return false;
}
}
+ return true;
}
private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9734601..ef14300 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -956,53 +956,77 @@
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
- int[] ambientBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientBrighteningThresholds);
- int[] ambientDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_ambientDarkeningThresholds);
- int[] ambientThresholdLevels = resources.getIntArray(
- com.android.internal.R.array.config_ambientThresholdLevels);
+ // Ambient Lux - Active Mode Brightness Thresholds
+ float[] ambientBrighteningThresholds =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+ float[] ambientDarkeningThresholds =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+ float[] ambientBrighteningLevels =
+ mDisplayDeviceConfig.getAmbientBrighteningLevels();
+ float[] ambientDarkeningLevels =
+ mDisplayDeviceConfig.getAmbientDarkeningLevels();
float ambientDarkeningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
- int[] screenBrighteningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenBrighteningThresholds);
- int[] screenDarkeningThresholds = resources.getIntArray(
- com.android.internal.R.array.config_screenDarkeningThresholds);
- float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
- .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
+ // Display - Active Mode Brightness Thresholds
+ float[] screenBrighteningThresholds =
+ mDisplayDeviceConfig.getScreenBrighteningPercentages();
+ float[] screenDarkeningThresholds =
+ mDisplayDeviceConfig.getScreenDarkeningPercentages();
+ float[] screenBrighteningLevels =
+ mDisplayDeviceConfig.getScreenBrighteningLevels();
+ float[] screenDarkeningLevels =
+ mDisplayDeviceConfig.getScreenDarkeningLevels();
float screenDarkeningMinThreshold =
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+ screenBrighteningThresholds, screenDarkeningThresholds,
+ screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+ screenBrighteningMinThreshold);
- // Idle screen thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
- // Idle ambient thresholds
+ // Ambient Lux - Idle Screen Brightness Thresholds
float ambientDarkeningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
float ambientBrighteningMinThresholdIdle =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ float[] ambientBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+ float[] ambientDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+ float[] ambientBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+ float[] ambientDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
- ambientBrighteningMinThresholdIdle);
+ ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+ ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+ ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+
+ // Display - Idle Screen Brightness Thresholds
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ float[] screenBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+ float[] screenDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+ float[] screenBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+ float[] screenDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+ screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index abf8fe3..faa4c3d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -29,52 +29,38 @@
private static final boolean DEBUG = false;
- private final float[] mBrighteningThresholds;
- private final float[] mDarkeningThresholds;
- private final float[] mThresholdLevels;
+ private final float[] mBrighteningThresholdsPercentages;
+ private final float[] mDarkeningThresholdsPercentages;
+ private final float[] mBrighteningThresholdLevels;
+ private final float[] mDarkeningThresholdLevels;
private final float mMinDarkening;
private final float mMinBrightening;
/**
- * Creates a {@code HysteresisLevels} object for ambient brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
- * @param minBrighteningThreshold the minimum value for which the brightening value needs to
- * return.
- * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
- */
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
- throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
- }
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
- mMinDarkening = minDarkeningThreshold;
- mMinBrightening = minBrighteningThreshold;
- }
-
- /**
- * Creates a {@code HysteresisLevels} object for screen brightness.
- * @param brighteningThresholds an array of brightening hysteresis constraint constants.
- * @param darkeningThresholds an array of darkening hysteresis constraint constants.
- * @param thresholdLevels a monotonically increasing array of threshold levels.
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * float arrays.
+ * @param brighteningThresholdsPercentages 0-100 of thresholds
+ * @param darkeningThresholdsPercentages 0-100 of thresholds
+ * @param brighteningThresholdLevels float array of brightness values in the relevant units
+ * @param darkeningThresholdLevels float array of brightness values in the relevant units
* @param minBrighteningThreshold the minimum value for which the brightening value needs to
* return.
* @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
*/
- HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
- float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
- if (brighteningThresholds.length != darkeningThresholds.length
- || darkeningThresholds.length != thresholdLevels.length + 1) {
+ HysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages,
+ float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+ float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+ || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
}
- mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
- mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
- mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+ mBrighteningThresholdsPercentages =
+ setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+ mDarkeningThresholdsPercentages =
+ setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+ mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+ mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
mMinDarkening = minDarkeningThreshold;
mMinBrightening = minBrighteningThreshold;
}
@@ -83,7 +69,9 @@
* Return the brightening hysteresis threshold for the given value level.
*/
public float getBrighteningThreshold(float value) {
- final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+ final float brightConstant = getReferenceLevel(value,
+ mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
@@ -98,7 +86,8 @@
* Return the darkening hysteresis threshold for the given value level.
*/
public float getDarkeningThreshold(float value) {
- final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+ final float darkConstant = getReferenceLevel(value,
+ mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
@@ -111,60 +100,39 @@
/**
* Return the hysteresis constant for the closest threshold value from the given array.
*/
- private float getReferenceLevel(float value, float[] referenceLevels) {
- int index = 0;
- while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
- ++index;
+ private float getReferenceLevel(float value, float[] thresholdLevels,
+ float[] thresholdPercentages) {
+ if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+ return 0.0f;
}
- return referenceLevels[index];
+ int index = 0;
+ while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+ index++;
+ }
+ return thresholdPercentages[index];
}
/**
* Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
*/
- private float[] setArrayFormat(int[] configArray, float divideFactor) {
+ private float[] setArrayFormat(float[] configArray, float divideFactor) {
float[] levelArray = new float[configArray.length];
for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = (float) configArray[index] / divideFactor;
+ levelArray[index] = configArray[index] / divideFactor;
}
return levelArray;
}
- /**
- * This check is due to historical reasons, where screen thresholdLevels used to be
- * integer values in the range of [0-255], but then was changed to be float values from [0,1].
- * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
- * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
- */
- private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
- if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
- 1.0f)) {
- return thresholdLevels;
- }
-
- Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
- float[] thresholdLevelsScaled = new float[thresholdLevels.length];
- for (int index = 0; thresholdLevels.length > index; ++index) {
- thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
- }
- return thresholdLevelsScaled;
- }
-
- private boolean isAllInRange(float[] configArray, float minValueInclusive,
- float maxValueInclusive) {
- int configArraySize = configArray.length;
- for (int index = 0; configArraySize > index; ++index) {
- if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
- return false;
- }
- }
- return true;
- }
void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
- pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
- pw.println(" mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
- pw.println(" mThresholdLevels=" + Arrays.toString(mThresholdLevels));
+ pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
+ pw.println(" mBrighteningThresholdsPercentages="
+ + Arrays.toString(mBrighteningThresholdsPercentages));
+ pw.println(" mMinBrightening=" + mMinBrightening);
+ pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
+ pw.println(" mDarkeningThresholdsPercentages="
+ + Arrays.toString(mDarkeningThresholdsPercentages));
+ pw.println(" mMinDarkening=" + mMinDarkening);
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 4e4f454..b8af1bf 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -118,7 +118,7 @@
public void startDream(Binder token, ComponentName name,
boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
- ComponentName overlayComponentName) {
+ ComponentName overlayComponentName, String reason) {
stopDream(true /*immediate*/, "starting new dream");
Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
@@ -128,7 +128,7 @@
Slog.i(TAG, "Starting dream: name=" + name
+ ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
- + ", userId=" + userId);
+ + ", userId=" + userId + ", reason='" + reason + "'");
mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fee1f5c..7964fd5 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -60,6 +60,7 @@
import android.view.Display;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -84,6 +85,9 @@
private static final boolean DEBUG = false;
private static final String TAG = "DreamManagerService";
+ private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
+ private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
+
private final Object mLock = new Object();
private final Context mContext;
@@ -98,17 +102,11 @@
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
- private Binder mCurrentDreamToken;
- private ComponentName mCurrentDreamName;
- private int mCurrentDreamUserId;
- private boolean mCurrentDreamIsPreview;
- private boolean mCurrentDreamCanDoze;
- private boolean mCurrentDreamIsDozing;
- private boolean mCurrentDreamIsWaking;
+ @GuardedBy("mLock")
+ private DreamRecord mCurrentDream;
+
private boolean mForceAmbientDisplayEnabled;
- private boolean mDreamsOnlyEnabledForSystemUser;
- private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
- private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private final boolean mDreamsOnlyEnabledForSystemUser;
// A temporary dream component that, when present, takes precedence over user configured dream
// component.
@@ -116,7 +114,7 @@
private ComponentName mDreamOverlayServiceName;
- private AmbientDisplayConfiguration mDozeConfig;
+ private final AmbientDisplayConfiguration mDozeConfig;
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@@ -132,8 +130,14 @@
final boolean activityAllowed = activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_DREAM
|| activityType == ACTIVITY_TYPE_ASSISTANT;
- if (mCurrentDreamToken != null && !mCurrentDreamIsWaking
- && !mCurrentDreamIsDozing && !activityAllowed) {
+
+ boolean shouldRequestAwaken;
+ synchronized (mLock) {
+ shouldRequestAwaken = mCurrentDream != null && !mCurrentDream.isWaking
+ && !mCurrentDream.isDozing && !activityAllowed;
+ }
+
+ if (shouldRequestAwaken) {
requestAwakenInternal(
"stopping dream due to activity start: " + activityInfo.name);
}
@@ -149,7 +153,7 @@
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
- mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
+ mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
@@ -197,43 +201,38 @@
}
private void dumpInternal(PrintWriter pw) {
- pw.println("DREAM MANAGER (dumpsys dreams)");
- pw.println();
- pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
- pw.println("mCurrentDreamName=" + mCurrentDreamName);
- pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
- pw.println("mCurrentDreamIsPreview=" + mCurrentDreamIsPreview);
- pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
- pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
- pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
- pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
- pw.println("mCurrentDreamDozeScreenState="
- + Display.stateToString(mCurrentDreamDozeScreenState));
- pw.println("mCurrentDreamDozeScreenBrightness=" + mCurrentDreamDozeScreenBrightness);
- pw.println("getDozeComponent()=" + getDozeComponent());
- pw.println();
+ synchronized (mLock) {
+ pw.println("DREAM MANAGER (dumpsys dreams)");
+ pw.println();
+ pw.println("mCurrentDream=" + mCurrentDream);
+ pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+ pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("getDozeComponent()=" + getDozeComponent());
+ pw.println();
- DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
- @Override
- public void dump(PrintWriter pw, String prefix) {
- mController.dump(pw);
- }
- }, pw, "", 200);
+ DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
+ }
}
/** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
- return mCurrentDreamToken != null && !mCurrentDreamIsPreview
- && !mCurrentDreamIsWaking;
+ return mCurrentDream != null && !mCurrentDream.isPreview
+ && !mCurrentDream.isWaking;
+ }
+ }
+
+ /** Whether a doze is occurring. */
+ private boolean isDozingInternal() {
+ synchronized (mLock) {
+ return mCurrentDream != null && mCurrentDream.isDozing;
}
}
/** Whether a real dream, or a dream preview is occurring. */
private boolean isDreamingOrInPreviewInternal() {
synchronized (mLock) {
- return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+ return mCurrentDream != null && !mCurrentDream.isWaking;
}
}
@@ -273,7 +272,7 @@
// locks are held and the user activity timeout has expired then the
// device may simply go to sleep.
synchronized (mLock) {
- if (mCurrentDreamToken == token) {
+ if (mCurrentDream != null && mCurrentDream.token == token) {
stopDreamLocked(immediate, "finished self");
}
}
@@ -281,16 +280,17 @@
private void testDreamInternal(ComponentName dream, int userId) {
synchronized (mLock) {
- startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId);
+ startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId,
+ "test dream" /*reason*/);
}
}
- private void startDreamInternal(boolean doze) {
+ private void startDreamInternal(boolean doze, String reason) {
final int userId = ActivityManager.getCurrentUser();
final ComponentName dream = chooseDreamForUser(doze, userId);
if (dream != null) {
synchronized (mLock) {
- startDreamLocked(dream, false /*isPreviewMode*/, doze, userId);
+ startDreamLocked(dream, false /*isPreviewMode*/, doze, userId, reason);
}
}
}
@@ -314,13 +314,13 @@
}
synchronized (mLock) {
- if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
- mCurrentDreamDozeScreenState = screenState;
- mCurrentDreamDozeScreenBrightness = screenBrightness;
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
+ mCurrentDream.dozeScreenState = screenState;
+ mCurrentDream.dozeScreenBrightness = screenBrightness;
mPowerManagerInternal.setDozeOverrideFromDreamManager(
screenState, screenBrightness);
- if (!mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = true;
+ if (!mCurrentDream.isDozing) {
+ mCurrentDream.isDozing = true;
mDozeWakeLock.acquire();
}
}
@@ -333,8 +333,8 @@
}
synchronized (mLock) {
- if (mCurrentDreamToken == token && mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = false;
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
+ mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT);
@@ -403,7 +403,7 @@
ComponentName[] components = componentsFromString(names);
// first, ensure components point to valid services
- List<ComponentName> validComponents = new ArrayList<ComponentName>();
+ List<ComponentName> validComponents = new ArrayList<>();
if (components != null) {
for (ComponentName component : components) {
if (validateDream(component)) {
@@ -439,8 +439,9 @@
mSystemDreamComponent = componentName;
// Switch dream if currently dreaming and not dozing.
- if (isDreamingInternal() && !mCurrentDreamIsDozing) {
- startDreamInternal(false);
+ if (isDreamingInternal() && !isDozingInternal()) {
+ startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set")
+ + " system dream component" /*reason*/);
}
}
}
@@ -478,13 +479,16 @@
}
}
+ @GuardedBy("mLock")
private void startDreamLocked(final ComponentName name,
- final boolean isPreviewMode, final boolean canDoze, final int userId) {
- if (!mCurrentDreamIsWaking
- && Objects.equals(mCurrentDreamName, name)
- && mCurrentDreamIsPreview == isPreviewMode
- && mCurrentDreamCanDoze == canDoze
- && mCurrentDreamUserId == userId) {
+ final boolean isPreviewMode, final boolean canDoze, final int userId,
+ final String reason) {
+ if (mCurrentDream != null
+ && !mCurrentDream.isWaking
+ && Objects.equals(mCurrentDream.name, name)
+ && mCurrentDream.isPreview == isPreviewMode
+ && mCurrentDream.canDoze == canDoze
+ && mCurrentDream.userId == userId) {
Slog.i(TAG, "Already in target dream.");
return;
}
@@ -493,73 +497,60 @@
Slog.i(TAG, "Entering dreamland.");
- final Binder newToken = new Binder();
- mCurrentDreamToken = newToken;
- mCurrentDreamName = name;
- mCurrentDreamIsPreview = isPreviewMode;
- mCurrentDreamCanDoze = canDoze;
- mCurrentDreamUserId = userId;
+ mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
// TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START);
mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_START,
- mCurrentDreamName.flattenToString());
+ mCurrentDream.name.flattenToString());
}
PowerManager.WakeLock wakeLock = mPowerManager
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DREAM_WAKE_LOCK_TAG);
+ final Binder dreamToken = mCurrentDream.token;
mHandler.post(wakeLock.wrap(() -> {
mAtmInternal.notifyDreamStateChanged(true);
- mController.startDream(newToken, name, isPreviewMode, canDoze, userId, wakeLock,
- mDreamOverlayServiceName);
+ mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId, wakeLock,
+ mDreamOverlayServiceName, reason);
}));
}
+ @GuardedBy("mLock")
private void stopDreamLocked(final boolean immediate, String reason) {
- if (mCurrentDreamToken != null) {
+ if (mCurrentDream != null) {
if (immediate) {
Slog.i(TAG, "Leaving dreamland.");
cleanupDreamLocked();
- } else if (mCurrentDreamIsWaking) {
+ } else if (mCurrentDream.isWaking) {
return; // already waking
} else {
Slog.i(TAG, "Gently waking up from dream.");
- mCurrentDreamIsWaking = true;
+ mCurrentDream.isWaking = true;
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Slog.i(TAG, "Performing gentle wake from dream.");
- mController.stopDream(immediate, reason);
- }
- });
+ mHandler.post(() -> mController.stopDream(immediate, reason));
}
}
+ @GuardedBy("mLock")
private void cleanupDreamLocked() {
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ mHandler.post(() -> mAtmInternal.notifyDreamStateChanged(false /*dreaming*/));
+
+ if (mCurrentDream == null) {
+ return;
+ }
+
+ if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
// TODO(b/213906448): Remove when metrics based on new atom are fully rolled out.
mUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP);
mDreamUiEventLogger.log(DreamUiEventLogger.DreamUiEventEnum.DREAM_STOP,
- mCurrentDreamName.flattenToString());
+ mCurrentDream.name.flattenToString());
}
- mCurrentDreamToken = null;
- mCurrentDreamName = null;
- mCurrentDreamIsPreview = false;
- mCurrentDreamCanDoze = false;
- mCurrentDreamUserId = 0;
- mCurrentDreamIsWaking = false;
- if (mCurrentDreamIsDozing) {
- mCurrentDreamIsDozing = false;
+ if (mCurrentDream.isDozing) {
mDozeWakeLock.release();
}
- mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
- mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
- mHandler.post(() -> {
- mAtmInternal.notifyDreamStateChanged(false);
- });
+ mCurrentDream = null;
}
private void checkPermission(String permission) {
@@ -606,7 +597,7 @@
@Override
public void onDreamStopped(Binder token) {
synchronized (mLock) {
- if (mCurrentDreamToken == token) {
+ if (mCurrentDream != null && mCurrentDream.token == token) {
cleanupDreamLocked();
}
}
@@ -624,7 +615,7 @@
* Handler for asynchronous operations performed by the dream manager.
* Ensures operations to {@link DreamController} are single-threaded.
*/
- private final class DreamHandler extends Handler {
+ private static final class DreamHandler extends Handler {
public DreamHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@@ -865,13 +856,13 @@
private final class LocalService extends DreamManagerInternal {
@Override
- public void startDream(boolean doze) {
- startDreamInternal(doze);
+ public void startDream(boolean doze, String reason) {
+ startDreamInternal(doze, reason);
}
@Override
- public void stopDream(boolean immediate) {
- stopDreamInternal(immediate, "requested stopDream");
+ public void stopDream(boolean immediate, String reason) {
+ stopDreamInternal(immediate, reason);
}
@Override
@@ -890,13 +881,47 @@
}
}
+ private static final class DreamRecord {
+ public final Binder token = new Binder();
+ public final ComponentName name;
+ public final int userId;
+ public final boolean isPreview;
+ public final boolean canDoze;
+ public boolean isDozing = false;
+ public boolean isWaking = false;
+ public int dozeScreenState = Display.STATE_UNKNOWN;
+ public int dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+
+ DreamRecord(ComponentName name, int userId, boolean isPreview, boolean canDoze) {
+ this.name = name;
+ this.userId = userId;
+ this.isPreview = isPreview;
+ this.canDoze = canDoze;
+ }
+
+ @Override
+ public String toString() {
+ return "DreamRecord{"
+ + "token=" + token
+ + ", name=" + name
+ + ", userId=" + userId
+ + ", isPreview=" + isPreview
+ + ", canDoze=" + canDoze
+ + ", isDozing=" + isDozing
+ + ", isWaking=" + isWaking
+ + ", dozeScreenState=" + dozeScreenState
+ + ", dozeScreenBrightness=" + dozeScreenBrightness
+ + '}';
+ }
+ }
+
private final Runnable mSystemPropertiesChanged = new Runnable() {
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "System properties changed");
synchronized (mLock) {
- if (mCurrentDreamName != null && mCurrentDreamCanDoze
- && !mCurrentDreamName.equals(getDozeComponent())) {
+ if (mCurrentDream != null && mCurrentDream.name != null && mCurrentDream.canDoze
+ && !mCurrentDream.name.equals(getDozeComponent())) {
// May have updated the doze component, wake up
mPowerManager.wakeUp(SystemClock.uptimeMillis(),
"android.server.dreams:SYSPROP");
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
new file mode 100644
index 0000000..c57d7e7
--- /dev/null
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -0,0 +1,235 @@
+/*
+ * 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.input;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.input.IInputDeviceBatteryListener;
+import android.hardware.input.InputManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the battery state
+ * of input devices.
+ */
+final class BatteryController {
+ private static final String TAG = BatteryController.class.getSimpleName();
+
+ // To enable these logs, run:
+ // 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final NativeInputManagerService mNative;
+
+ // Maps a pid to the registered listener record for that process. There can only be one battery
+ // listener per process.
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
+
+ BatteryController(Context context, NativeInputManagerService nativeService) {
+ mContext = context;
+ mNative = nativeService;
+ }
+
+ /**
+ * Register the battery listener for the given input device and start monitoring its battery
+ * state.
+ */
+ @BinderThread
+ void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+ int pid) {
+ synchronized (mLock) {
+ ListenerRecord listenerRecord = mListenerRecords.get(pid);
+
+ if (listenerRecord == null) {
+ listenerRecord = new ListenerRecord(pid, listener);
+ try {
+ listener.asBinder().linkToDeath(listenerRecord.mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Client died before battery listener could be registered.");
+ return;
+ }
+ mListenerRecords.put(pid, listenerRecord);
+ if (DEBUG) Slog.d(TAG, "Battery listener added for pid " + pid);
+ }
+
+ if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+ throw new SecurityException(
+ "Cannot register a new battery listener when there is already another "
+ + "registered listener for pid "
+ + pid);
+ }
+ if (!listenerRecord.mMonitoredDevices.add(deviceId)) {
+ throw new IllegalArgumentException(
+ "The battery listener for pid " + pid
+ + " is already monitoring deviceId " + deviceId);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Battery listener for pid " + pid
+ + " is monitoring deviceId " + deviceId);
+ }
+
+ notifyBatteryListener(deviceId, listenerRecord);
+ }
+ }
+
+ private void notifyBatteryListener(int deviceId, ListenerRecord record) {
+ final long eventTime = SystemClock.uptimeMillis();
+ try {
+ record.mListener.onBatteryStateChanged(
+ deviceId,
+ hasBattery(deviceId),
+ mNative.getBatteryStatus(deviceId),
+ mNative.getBatteryCapacity(deviceId) / 100.f,
+ eventTime);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify listener", e);
+ }
+ }
+
+ private boolean hasBattery(int deviceId) {
+ final InputDevice device =
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .getInputDevice(deviceId);
+ return device != null && device.hasBattery();
+ }
+
+ /**
+ * Unregister the battery listener for the given input device and stop monitoring its battery
+ * state. If there are no other input devices that this listener is monitoring, the listener is
+ * removed.
+ */
+ @BinderThread
+ void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
+ int pid) {
+ synchronized (mLock) {
+ final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+ if (listenerRecord == null) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: No listener registered for pid "
+ + pid);
+ }
+
+ if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: The listener is not the one that "
+ + "is registered for pid "
+ + pid);
+ }
+
+ if (!listenerRecord.mMonitoredDevices.contains(deviceId)) {
+ throw new IllegalArgumentException(
+ "Cannot unregister battery callback: The device is not being "
+ + "monitored for deviceId " + deviceId);
+ }
+
+ unregisterRecordLocked(listenerRecord, deviceId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId) {
+ final int pid = listenerRecord.mPid;
+
+ if (!listenerRecord.mMonitoredDevices.remove(deviceId)) {
+ throw new IllegalStateException("Cannot unregister battery callback: The deviceId "
+ + deviceId
+ + " is not being monitored by pid "
+ + pid);
+ }
+
+ if (listenerRecord.mMonitoredDevices.isEmpty()) {
+ // There are no more devices being monitored by this listener.
+ listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0);
+ mListenerRecords.remove(pid);
+ if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
+ }
+ }
+
+ private void handleListeningProcessDied(int pid) {
+ synchronized (mLock) {
+ final ListenerRecord listenerRecord = mListenerRecords.get(pid);
+ if (listenerRecord == null) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Removing battery listener for pid " + pid + " because the process died");
+ }
+ for (final int deviceId : listenerRecord.mMonitoredDevices) {
+ unregisterRecordLocked(listenerRecord, deviceId);
+ }
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + TAG + ": " + mListenerRecords.size()
+ + " battery listeners");
+ for (int i = 0; i < mListenerRecords.size(); i++) {
+ pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i));
+ }
+ }
+ }
+
+ @SuppressWarnings("all")
+ void monitor() {
+ synchronized (mLock) {
+ return;
+ }
+ }
+
+ // A record of a registered battery listener from one process.
+ private class ListenerRecord {
+ final int mPid;
+ final IInputDeviceBatteryListener mListener;
+ final IBinder.DeathRecipient mDeathRecipient;
+ // The set of deviceIds that are currently being monitored by this listener.
+ final Set<Integer> mMonitoredDevices;
+
+ ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
+ mPid = pid;
+ mListener = listener;
+ mMonitoredDevices = new ArraySet<>();
+ mDeathRecipient = () -> handleListeningProcessDied(pid);
+ }
+
+ @Override
+ public String toString() {
+ return "pid=" + mPid
+ + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 72612a0..91d5698 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayViewport;
+import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
@@ -318,6 +319,9 @@
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+ // Manages battery state for input devices.
+ private final BatteryController mBatteryController;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -425,6 +429,7 @@
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
+ mBatteryController = new BatteryController(mContext, mNative);
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -2674,6 +2679,18 @@
}
@Override
+ public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener);
+ mBatteryController.registerBatteryListener(deviceId, listener, Binder.getCallingPid());
+ }
+
+ @Override
+ public void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ Objects.requireNonNull(listener);
+ mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -2686,7 +2703,8 @@
pw.println("Input Manager Service (Java) State:");
dumpAssociations(pw, " " /*prefix*/);
dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
- dumpDisplayInputPropertiesValues(pw, " " /* prefix */);
+ dumpDisplayInputPropertiesValues(pw, " " /*prefix*/);
+ mBatteryController.dump(pw, " " /*prefix*/);
}
private void dumpAssociations(PrintWriter pw, String prefix) {
@@ -2797,6 +2815,7 @@
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
+ mBatteryController.monitor();
mNative.monitor();
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 9a89efa..232a69b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -177,11 +177,13 @@
NOTIFICATION_CANCEL_USER_PEEK(190),
@UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
NOTIFICATION_CANCEL_USER_AOD(191),
+ @UiEvent(doc = "Notification was canceled due to user dismissal from a bubble")
+ NOTIFICATION_CANCEL_USER_BUBBLE(1228),
+ @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
+ NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
@UiEvent(doc = "Notification was canceled due to user dismissal from the notification"
+ " shade.")
NOTIFICATION_CANCEL_USER_SHADE(192),
- @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
- NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
@UiEvent(doc = "Notification was canceled due to an assistant adjustment update.")
NOTIFICATION_CANCEL_ASSISTANT(906);
@@ -232,6 +234,10 @@
return NOTIFICATION_CANCEL_USER_AOD;
case NotificationStats.DISMISSAL_SHADE:
return NOTIFICATION_CANCEL_USER_SHADE;
+ case NotificationStats.DISMISSAL_BUBBLE:
+ return NOTIFICATION_CANCEL_USER_BUBBLE;
+ case NotificationStats.DISMISSAL_LOCKSCREEN:
+ return NOTIFICATION_CANCEL_USER_LOCKSCREEN;
default:
if (NotificationManagerService.DBG) {
throw new IllegalArgumentException("Unexpected surface for user-dismiss "
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 8e944b7..39d1188 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -217,6 +217,7 @@
synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
Connection c = null;
+ int iteratorId = -1;
try {
c = connect();
final IIdmap2 service = c.getIdmap2();
@@ -225,9 +226,9 @@
return Collections.emptyList();
}
- service.acquireFabricatedOverlayIterator();
+ iteratorId = service.acquireFabricatedOverlayIterator();
List<FabricatedOverlayInfo> infos;
- while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) {
+ while (!(infos = service.nextFabricatedOverlayInfos(iteratorId)).isEmpty()) {
allInfos.addAll(infos);
}
return allInfos;
@@ -235,8 +236,8 @@
Slog.wtf(TAG, "failed to get all fabricated overlays", e);
} finally {
try {
- if (c.getIdmap2() != null) {
- c.getIdmap2().releaseFabricatedOverlayIterator();
+ if (c.getIdmap2() != null && iteratorId != -1) {
+ c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
}
} catch (RemoteException e) {
// ignore
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0c601bf..890c891 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1962,10 +1962,15 @@
continue;
case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName,
- shortcutUser.getUserId(), fromBackup);
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
+ try {
+ final ShortcutInfo si = parseShortcut(parser, packageName,
+ shortcutUser.getUserId(), fromBackup);
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ } catch (Exception e) {
+ // b/246540168 malformed shortcuts should be ignored
+ Slog.e(TAG, "Failed parsing shortcut.", e);
+ }
continue;
case TAG_SHARE_TARGET:
ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 05cb429..32ef014 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -63,7 +63,6 @@
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
-import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
@@ -1597,7 +1596,7 @@
// If there's a dream running then use home to escape the dream
// but don't actually go home.
if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
- mDreamManagerInternal.stopDream(false /*immediate*/);
+ mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
return;
}
@@ -2816,9 +2815,8 @@
break;
case KeyEvent.KEYCODE_S:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
- int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
- : TAKE_SCREENSHOT_FULLSCREEN;
- interceptScreenshotChord(type, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(
+ TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
return key_consumed;
}
break;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 5a2fb18..dad9584 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, int event, int uid) {
+ public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+ int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac..9fe53fb 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
+ @PowerManager.UserActivityEvent
+ private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -244,7 +246,7 @@
return true;
}
- boolean dozeLocked(long eventTime, int uid, int reason) {
+ boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -253,9 +255,14 @@
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ long millisSinceLastUserActivity = eventTime - Math.max(
+ mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
- + ", uid= " + uid + ")...");
+ + PowerManager.sleepReasonToString(reason)
+ + " (groupId= " + getGroupId() + ", uid= " + uid
+ + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+ + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+ mLastUserActivityEvent) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@
return true;
}
- boolean sleepLocked(long eventTime, int uid, int reason) {
+ boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+ Slog.i(TAG,
+ "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+ + PowerManager.sleepReasonToString(reason) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTime = lastUserActivityTime;
+ mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTimeNoChangeLights = time;
+ mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dbf05f1..f200564 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1169,6 +1169,7 @@
return;
}
+ Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1888,12 +1889,13 @@
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+ int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime, int event, int flags,
- int uid) {
+ private void userActivityInternal(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1944,11 +1946,12 @@
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- int event, int flags, int uid) {
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime + ", event=" + event
+ + ", eventTime=" + eventTime
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -1983,7 +1986,7 @@
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1993,7 +1996,7 @@
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime);
+ powerGroup.setLastUserActivityTimeLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2020,7 +2023,8 @@
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2043,11 +2047,11 @@
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- int reason, int uid) {
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
- + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2058,10 +2062,12 @@
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
- int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2122,8 +2128,10 @@
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ")...");
-
+ + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+ + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+ + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+ + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
mDozeStartInProgress = true;
@@ -3218,8 +3226,10 @@
if (mDreamManager != null) {
// Restart the dream whenever the sandman is summoned.
if (startDreaming) {
- mDreamManager.stopDream(/* immediate= */ false);
- mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
+ mDreamManager.stopDream(/* immediate= */ false,
+ "power manager request before starting dream" /*reason*/);
+ mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING,
+ "power manager request" /*reason*/);
}
isDreaming = mDreamManager.isDreaming();
} else {
@@ -3304,7 +3314,7 @@
// Stop dream.
if (isDreaming) {
- mDreamManager.stopDream(/* immediate= */ false);
+ mDreamManager.stopDream(/* immediate= */ false, "power manager request" /*reason*/);
}
}
@@ -4207,7 +4217,7 @@
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis());
+ mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
}
}
@@ -5590,7 +5600,8 @@
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime, int event, int flags) {
+ public void userActivity(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 8b30995..d8e6c26 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -53,7 +53,7 @@
private static class DataElement {
private static final int LENGTH_FIELD_WIDTH = 4;
- private static final int MAX_DATA_ELEMENT_SIZE = 1000;
+ private static final int MAX_DATA_ELEMENT_SIZE = 32768;
private byte[] mData;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f888ff6..2888b9a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -602,9 +602,12 @@
synchronized (mUserTrustState) {
wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+ boolean isAutomotive = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
boolean renewingTrust = wasTrustable && (
(flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
- boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+ boolean canMoveToTrusted =
+ alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive;
boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
if (trustedByAtLeastOneAgent && wasTrusted) {
@@ -687,7 +690,7 @@
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -2084,7 +2087,7 @@
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d2a00af..eca2e74 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -27,6 +27,8 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
@@ -332,8 +334,8 @@
}
@Override
- public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
- Intent resultData) {
+ public boolean navigateUpTo(IBinder token, Intent destIntent, String resolvedType,
+ int resultCode, Intent resultData) {
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
@@ -348,7 +350,7 @@
synchronized (mGlobalLock) {
return r.getRootTask().navigateUpTo(
- r, destIntent, destGrants, resultCode, resultData, resultGrants);
+ r, destIntent, resolvedType, destGrants, resultCode, resultData, resultGrants);
}
}
@@ -707,7 +709,26 @@
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- return r != null && r.setOccludesParent(true);
+ // Create a transition if the activity is playing in case the below activity didn't
+ // commit invisible. That's because if any activity below this one has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r != null
+ && r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r != null && r.setOccludesParent(true);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -728,7 +749,25 @@
if (under != null) {
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
- return r.setOccludesParent(false);
+ // Create a transition if the activity is playing in case the current activity
+ // didn't commit invisible. That's because if this activity has changed its
+ // visibility while playing transition, there won't able to commit visibility until
+ // the running transition finish.
+ final Transition transition = r.mTransitionController.inPlayingTransition(r)
+ ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+ if (transition != null) {
+ r.mTransitionController.requestStartTransition(transition, null /*startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ }
+ final boolean changed = r.setOccludesParent(false);
+ if (transition != null) {
+ if (changed) {
+ r.mTransitionController.setReady(r.getDisplayContent());
+ } else {
+ transition.abort();
+ }
+ }
+ return changed;
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8486e7..9c080e8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -120,6 +120,8 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
@@ -658,7 +660,7 @@
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
- boolean mUseTransferredAnimation;
+ int mTransitionChangeFlags;
/** Whether we need to setup the animation to animate only within the letterbox. */
private boolean mNeedsLetterboxedAnimation;
@@ -4395,10 +4397,10 @@
// When transferring an animation, we no longer need to apply an animation to
// the token we transfer the animation over. Thus, set this flag to indicate
// we've transferred the animation.
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
} else if (mTransitionController.getTransitionPlayer() != null) {
// In the new transit system, just set this every time we transfer the window
- mUseTransferredAnimation = true;
+ mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
}
// Post cleanup after the visibility and animation are transferred.
fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow);
@@ -5247,6 +5249,10 @@
// If in a transition, defer commits for activities that are going invisible
if (!visible && inTransition()) {
+ if (mTransitionController.inPlayingTransition(this)
+ && mTransitionController.isCollecting(this)) {
+ mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ }
return;
}
// If we are preparing an app transition, then delay changing
@@ -5297,7 +5303,7 @@
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
- if (mUseTransferredAnimation) {
+ if ((mTransitionChangeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return false;
}
// If it was set to true, reset the last request to force the transition.
@@ -5370,7 +5376,7 @@
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
- mUseTransferredAnimation = false;
+ mTransitionChangeFlags = 0;
postApplyAnimation(visible, fromTransition);
}
@@ -5812,8 +5818,8 @@
// in untrusted mode. Traverse bottom to top with boundary so that it will only check
// activities above this activity.
final ActivityRecord differentUidOverlayActivity = getTask().getActivity(
- a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */,
- false /* traverseTopToBottom */);
+ a -> !a.finishing && a.getUid() != getUid(), this /* boundary */,
+ false /* includeBoundary */, false /* traverseTopToBottom */);
return differentUidOverlayActivity != null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index d131457..8cbd9fc 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
import android.view.WindowManager;
+import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -566,14 +567,39 @@
return false;
}
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
- final ActivityMetricsLogger.LaunchingState launchingState =
- mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
- final Task task = r.getTask();
- mService.deferWindowLayout();
- try {
+ final RemoteTransition remote = options.getRemoteTransition();
+ if (remote != null && rootTask.mTransitionController.isCollecting()) {
+ final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, rootTask.mTransitionController,
+ mService.mWindowManager.mSyncEngine);
+ // Special case: we are entering recents while an existing transition is running. In
+ // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+ // that it can get its own transition and thus update launcher correctly.
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> rootTask.mTransitionController.moveToCollecting(transition),
+ () -> {
+ final Task task = r.getTask();
+ task.mTransitionController.requestStartTransition(transition,
+ task, remote, null /* displayChange */);
+ task.mTransitionController.collect(task);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ });
+ } else {
+ final Task task = r.getTask();
task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
0 /* flags */, task, task /* readyGroupRef */,
options.getRemoteTransition(), null /* displayChange */);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ }
+ return true;
+ }
+
+ void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+ ActivityRecord r, Task task, Task rootTask) {
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ mService.deferWindowLayout();
+ try {
r.mTransitionController.setTransientLaunch(r,
TaskDisplayArea.getRootTaskAbove(rootTask));
task.moveToFront("startExistingRecents");
@@ -585,7 +611,6 @@
task.mInResumeTopActivity = false;
mService.continueWindowLayout();
}
- return true;
}
void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index fd6c974..b160af6a 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -98,7 +98,7 @@
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
return mService.getRecentTasks().createRecentTaskInfo(task,
- false /* stripExtras */);
+ false /* stripExtras */, true /* getTasksAllowed */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 8c5f053..7d9ae87 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,8 +202,7 @@
// target windows. But the windows still need to use sync transaction to keep the appearance
// in previous rotation, so request a no-op sync to keep the state.
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
- && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) {
+ if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
// Expect a screenshot layer will cover the non seamless windows.
continue;
}
@@ -489,7 +488,7 @@
return false;
}
final Operation op = mTargetWindowTokens.get(w.mToken);
- if (op == null) return false;
+ if (op == null || op.canDrawBeforeStartTransaction()) return false;
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (op.mDrawTransaction == null) {
if (w.isClientLocal()) {
@@ -554,5 +553,14 @@
Operation(@Action int action) {
mAction = action;
}
+
+ /**
+ * Returns {@code true} if the corresponding window can draw its latest content before the
+ * start transaction of rotation transition is applied.
+ */
+ boolean canDrawBeforeStartTransaction() {
+ return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
+ && mAction != ACTION_SEAMLESS;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4c69f87..42a3ec6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2710,7 +2710,7 @@
*
* @param screenshotType The type of screenshot, for example either
* {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
- * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+ * {@link WindowManager#TAKE_SCREENSHOT_PROVIDED_IMAGE}
* @param source Where the screenshot originated from (see WindowManager.ScreenshotSource)
*/
public void takeScreenshot(int screenshotType, int source) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 317c93e..ea82417 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -481,7 +481,7 @@
}
private void updateRoundedCorners(WindowState mainWindow) {
- final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+ final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
if (windowSurface != null && windowSurface.isValid()) {
final Transaction transaction = mActivityRecord.getSyncTransaction();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4860762..1fc061b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
}
return res;
}
@@ -1895,7 +1895,8 @@
/**
* Creates a new RecentTaskInfo from a Task.
*/
- ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+ boolean getTasksAllowed) {
final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
// If the recent Task is detached, we consider it will be re-attached to the default
// TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1907,6 +1908,9 @@
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+ if (!getTasksAllowed) {
+ Task.trimIneffectiveInfo(tr, rti);
+ }
// Fill in organized child task info for the task created by organizer.
if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0..0e60274 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -142,6 +142,10 @@
task.fillTaskInfo(rti, !mKeepIntentExtra);
// Fill in some deprecated values
rti.id = rti.taskId;
+
+ if (!mAllowed) {
+ Task.trimIneffectiveInfo(task, rti);
+ }
return rti;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d6f295e..01d9c26 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3447,6 +3447,27 @@
info.isSleeping = shouldSleepActivities();
}
+ /**
+ * Removes the activity info if the activity belongs to a different uid, which is
+ * different from the app that hosts the task.
+ */
+ static void trimIneffectiveInfo(Task task, TaskInfo info) {
+ final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+ false /* traverseTopToBottom */);
+ final int baseActivityUid =
+ baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+ if (info.topActivityInfo != null
+ && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+ info.topActivity = null;
+ info.topActivityInfo = null;
+ }
+
+ if (task.effectiveUid != baseActivityUid) {
+ info.baseActivity = null;
+ }
+ }
+
@Nullable PictureInPictureParams getPictureInPictureParams() {
final Task topTask = getTopMostTask();
if (topTask == null) return null;
@@ -3526,12 +3547,16 @@
* {@link android.window.TaskFragmentOrganizer}
*/
TaskFragmentParentInfo getTaskFragmentParentInfo() {
- return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+ return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
+ shouldBeVisible(null /* starting */));
}
@Override
void onActivityVisibleRequestedChanged() {
- if (mVisibleRequested != isVisibleRequested()) {
+ final boolean prevVisibleRequested = mVisibleRequested;
+ // mVisibleRequested is updated in super method.
+ super.onActivityVisibleRequestedChanged();
+ if (prevVisibleRequested != mVisibleRequested) {
sendTaskFragmentParentInfoChangedIfNeeded();
}
}
@@ -5309,8 +5334,9 @@
return false;
}
- boolean navigateUpTo(ActivityRecord srec, Intent destIntent, NeededUriGrants destGrants,
- int resultCode, Intent resultData, NeededUriGrants resultGrants) {
+ boolean navigateUpTo(ActivityRecord srec, Intent destIntent, String resolvedType,
+ NeededUriGrants destGrants, int resultCode, Intent resultData,
+ NeededUriGrants resultGrants) {
if (!srec.attachedToProcess()) {
// Nothing to do if the caller is not attached, because this method should be called
// from an alive activity.
@@ -5403,28 +5429,22 @@
srec.packageName);
}
} else {
- try {
- ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
- destIntent.getComponent(), ActivityManagerService.STOCK_PM_FLAGS,
- srec.mUserId);
- // TODO(b/64750076): Check if calling pid should really be -1.
- final int res = mAtmService.getActivityStartController()
- .obtainStarter(destIntent, "navigateUpTo")
- .setCaller(srec.app.getThread())
- .setActivityInfo(aInfo)
- .setResultTo(parent.token)
- .setCallingPid(-1)
- .setCallingUid(callingUid)
- .setCallingPackage(srec.packageName)
- .setCallingFeatureId(parent.launchedFromFeatureId)
- .setRealCallingPid(-1)
- .setRealCallingUid(callingUid)
- .setComponentSpecified(true)
- .execute();
- foundParentInTask = res == ActivityManager.START_SUCCESS;
- } catch (RemoteException e) {
- foundParentInTask = false;
- }
+ // TODO(b/64750076): Check if calling pid should really be -1.
+ final int res = mAtmService.getActivityStartController()
+ .obtainStarter(destIntent, "navigateUpTo")
+ .setResolvedType(resolvedType)
+ .setUserId(srec.mUserId)
+ .setCaller(srec.app.getThread())
+ .setResultTo(parent.token)
+ .setCallingPid(-1)
+ .setCallingUid(callingUid)
+ .setCallingPackage(srec.packageName)
+ .setCallingFeatureId(parent.launchedFromFeatureId)
+ .setRealCallingPid(-1)
+ .setRealCallingUid(callingUid)
+ .setComponentSpecified(true)
+ .execute();
+ foundParentInTask = res == ActivityManager.START_SUCCESS;
parent.finishIfPossible(resultCode, resultData, resultGrants,
"navigate-top", true /* oomAdj */);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index ec67414..3404e7b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2690,12 +2690,26 @@
return;
}
mVisibleRequested = isVisibleRequested;
- final TaskFragment parentTf = getParent().asTaskFragment();
+ final WindowContainer<?> parent = getParent();
+ if (parent == null) {
+ return;
+ }
+ final TaskFragment parentTf = parent.asTaskFragment();
if (parentTf != null) {
parentTf.onActivityVisibleRequestedChanged();
}
}
+ @Nullable
+ @Override
+ TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+ final TaskFragment taskFragment = super.getTaskFragment(callback);
+ if (taskFragment != null) {
+ return taskFragment;
+ }
+ return callback.test(this) ? this : null;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2d5c989..867833a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -920,6 +920,7 @@
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
+ // TODO(b/251132298): move visibility check to the client side.
if (task != null && (task.lastActiveTime <= event.mDeferTime
|| !(isTaskVisible(task, visibleTasks, invisibleTasks)
|| shouldSendEventWhenTaskInvisible(event)))) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 147c9cb..526a366 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -55,7 +55,6 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
@@ -1331,7 +1330,7 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" sibling is a participant with mode %s",
TransitionInfo.modeToString(siblingMode));
- if (mode != siblingMode) {
+ if (reduceMode(mode) != reduceMode(siblingMode)) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" SKIP: common mode mismatch. was %s",
TransitionInfo.modeToString(mode));
@@ -1341,6 +1340,16 @@
return true;
}
+ /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
+ @TransitionInfo.TransitionMode
+ private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
+ switch (mode) {
+ case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
+ case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
+ default: return mode;
+ }
+ }
+
/**
* Go through topTargets and try to promote (see {@link #canPromote}) one of them.
*
@@ -1842,7 +1851,7 @@
@TransitionInfo.TransitionMode
int getTransitMode(@NonNull WindowContainer wc) {
if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
- return TRANSIT_CLOSE;
+ return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
}
final boolean nowVisible = wc.isVisibleRequested();
if (nowVisible == mVisible) {
@@ -1879,12 +1888,10 @@
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
parentTask = record.getTask();
- if (record.mUseTransferredAnimation) {
- flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
- }
if (record.mVoiceInteraction) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ flags |= record.mTransitionChangeFlags;
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4d37e08..ac720be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6007,7 +6007,11 @@
if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
&& mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- w.setOrientationChanging(true);
+ // WindowsState#reportResized won't tell invisible requested window to redraw,
+ // so do not set it as changing orientation to avoid affecting draw state.
+ if (w.isVisibleRequested()) {
+ w.setOrientationChanging(true);
+ }
if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e1477d..949fa96 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -242,8 +243,18 @@
}
@Override
- public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ public IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {
+ return startTransition(type, null /* transitionToken */, t);
+ }
+
+ @Override
+ public void startTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
+ startTransition(-1 /* unused type */, transitionToken, t);
+ }
+
+ private IBinder startTransition(@WindowManager.TransitionType int type,
+ @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
enforceTaskPermission("startTransition()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
@@ -1557,10 +1568,6 @@
return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
}
- private void enforceTaskPermission(String func) {
- mService.enforceTaskPermission(func);
- }
-
private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
.isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 42d2861..f61bccf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3869,7 +3869,7 @@
// configuration update when the window has requested to be hidden. Doing so can lead to
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
- if (useLatestConfig || (relayoutVisible && (shouldCheckTokenVisibleRequested()
+ if (useLatestConfig || (relayoutVisible && (!shouldCheckTokenVisibleRequested()
|| mToken.isVisibleRequested()))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 267cff6..4a51b83 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -351,6 +351,35 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:sequence>
+ <!-- Thresholds as tenths of percent of current brightness level, at each level of
+ brightness -->
+ <xs:element name="brightnessThresholdPoints" type="thresholdPoints" maxOccurs="1" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoints">
+ <xs:sequence>
+ <xs:element type="thresholdPoint" name="brightnessThresholdPoint" maxOccurs="unbounded" minOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="thresholdPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="threshold">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="percentage">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
</xs:complexType>
<xs:complexType name="autoBrightness">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f8bff75..748ef4b 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -13,7 +13,9 @@
public class BrightnessThresholds {
ctor public BrightnessThresholds();
+ method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
method @NonNull public final java.math.BigDecimal getMinimum();
+ method public final void setBrightnessThresholdPoints(com.android.server.display.config.ThresholdPoints);
method public final void setMinimum(@NonNull java.math.BigDecimal);
}
@@ -204,6 +206,19 @@
method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
}
+ public class ThresholdPoint {
+ ctor public ThresholdPoint();
+ method @NonNull public final java.math.BigDecimal getPercentage();
+ method @NonNull public final java.math.BigDecimal getThreshold();
+ method public final void setPercentage(@NonNull java.math.BigDecimal);
+ method public final void setThreshold(@NonNull java.math.BigDecimal);
+ }
+
+ public class ThresholdPoints {
+ ctor public ThresholdPoints();
+ method @NonNull public final java.util.List<com.android.server.display.config.ThresholdPoint> getBrightnessThresholdPoint();
+ }
+
public class Thresholds {
ctor public Thresholds();
method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 20d9cce..75c8b7d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7783,6 +7783,7 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
@@ -7823,6 +7824,7 @@
Preconditions.checkCallAuthorization(
isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
+ Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..4f2b613 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -422,13 +422,13 @@
@Test
public void testHysteresisLevels() {
- int[] ambientBrighteningThresholds = {100, 200};
- int[] ambientDarkeningThresholds = {400, 500};
- int[] ambientThresholdLevels = {500};
+ float[] ambientBrighteningThresholds = {50, 100};
+ float[] ambientDarkeningThresholds = {10, 20};
+ float[] ambientThresholdLevels = {0, 500};
float ambientDarkeningMinChangeThreshold = 3.0f;
float ambientBrighteningMinChangeThreshold = 1.5f;
HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
- ambientDarkeningThresholds, ambientThresholdLevels,
+ ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
// test low, activate minimum change thresholds.
@@ -437,16 +437,17 @@
assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
// test max
- assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
- assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+ // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+ assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+ assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
// test just below threshold
- assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
- assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+ assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
// test at (considered above) threshold
- assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
- assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..a719f52 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,6 +50,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayDeviceConfigTest {
private DisplayDeviceConfig mDisplayDeviceConfig;
+ private static final float ZERO_DELTA = 0.0f;
+ private static final float SMALL_DELTA = 0.0001f;
+
@Mock
private Context mContext;
@@ -69,26 +74,74 @@
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
- assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+ ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
+ ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
- 0.0f);
- assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
- assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+ ZERO_DELTA);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 50.0f, 80.0f}, 0.0f);
+ float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{45.32f, 75.43f}, 0.0f);
+ float[]{45.32f, 75.43f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
+ ZERO_DELTA);
+ assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{9, 10, 11},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{11, 12, 13},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 100, 200},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{13, 14, 15},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 300, 400},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{15, 16, 17},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{17, 18, 19},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{19, 20, 21},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 500, 600},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{21, 22, 23},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 700, 800},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{23, 24, 25},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -97,9 +150,60 @@
public void testConfigValuesFromConfigResource() {
setupDisplayDeviceConfigFromConfigResourceFile();
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, 0.0f);
+ float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 0.0f, 110.0f, 500.0f}, 0.0f);
+ float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+
+ // Test thresholds
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+ ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+ // screen levels will be considered "old screen brightness scale"
+ // and therefore will divide by 255
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{35, 36, 37},
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+ assertArrayEquals(new float[]{37, 38, 39},
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{27, 28, 29},
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+ assertArrayEquals(new float[]{29, 30, 31},
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -154,11 +258,126 @@
+ "<ambientBrightnessChangeThresholds>\n"
+ "<brighteningThresholds>\n"
+ "<minimum>10</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>100</threshold><percentage>14</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>200</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</brighteningThresholds>\n"
+ "<darkeningThresholds>\n"
- + "<minimum>2</minimum>\n"
+ + "<minimum>30</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>15</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>300</threshold><percentage>16</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>400</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ "</darkeningThresholds>\n"
+ "</ambientBrightnessChangeThresholds>\n"
+ + "<displayBrightnessChangeThresholds>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.1</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold>\n"
+ + "<percentage>9</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.10</threshold>\n"
+ + "<percentage>10</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.20</threshold>\n"
+ + "<percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.3</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>11</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.11</threshold><percentage>12</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.21</threshold><percentage>13</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholds>\n"
+ + "<ambientBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>20</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>500</threshold><percentage>22</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>600</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>40</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>23</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>700</threshold><percentage>24</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>800</threshold><percentage>25</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</ambientBrightnessChangeThresholdsIdle>\n"
+ + "<displayBrightnessChangeThresholdsIdle>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>0.2</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>17</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.12</threshold><percentage>18</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.22</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>0.4</minimum>\n"
+ + "<brightnessThresholdPoints>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0</threshold><percentage>19</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.13</threshold><percentage>20</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "<brightnessThresholdPoint>\n"
+ + "<threshold>0.23</threshold><percentage>21</percentage>\n"
+ + "</brightnessThresholdPoint>\n"
+ + "</brightnessThresholdPoints>\n"
+ + "</darkeningThresholds>\n"
+ + "</displayBrightnessChangeThresholdsIdle>\n"
+ "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+ "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> "
+ "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
@@ -171,18 +390,6 @@
+ "</screenBrightnessRampDecreaseMaxMillis>"
+ "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+ "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
- + "<displayBrightnessChangeThresholds>"
- + "<brighteningThresholds>"
- + "<minimum>"
- + "0.001"
- + "</minimum>"
- + "</brighteningThresholds>"
- + "<darkeningThresholds>"
- + "<minimum>"
- + "0.002"
- + "</minimum>"
- + "</darkeningThresholds>"
- + "</displayBrightnessChangeThresholds>"
+ "<screenBrightnessRampIncreaseMaxMillis>"
+ "2000"
+ "</screenBrightnessRampIncreaseMaxMillis>\n"
@@ -241,8 +448,24 @@
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(screenBrightnessLevelLux);
- mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+ // Thresholds
+ // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+ // of length N+1
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+ .thenReturn(new int[]{30, 31});
+ when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+ .thenReturn(new int[]{42, 43});
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_ambientBrighteningThresholds))
+ .thenReturn(new int[]{270, 280, 290});
+ when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+ .thenReturn(new int[]{290, 300, 310});
+ when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+ .thenReturn(new int[]{350, 360, 370});
+ when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+ .thenReturn(new int[]{370, 380, 390});
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
private TypedArray createFloatTypedArray(float[] vals) {
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
new file mode 100644
index 0000000..246aa90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.BatteryState.STATUS_CHARGING
+import android.hardware.BatteryState.STATUS_FULL
+import android.hardware.input.IInputDeviceBatteryListener
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.Binder
+import android.os.IBinder
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.notNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link InputDeviceBatteryController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputDeviceBatteryControllerTests
+ */
+@Presubmit
+class BatteryControllerTests {
+ companion object {
+ const val PID = 42
+ const val DEVICE_ID = 13
+ const val SECOND_DEVICE_ID = 11
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ @Mock
+ private lateinit var iInputManager: IInputManager
+
+ private lateinit var batteryController: BatteryController
+ private lateinit var context: Context
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ val inputManager = InputManager.resetInstance(iInputManager)
+ `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
+ `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID))
+ `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
+ .thenReturn(createInputDevice(SECOND_DEVICE_ID))
+
+ batteryController = BatteryController(context, native)
+ }
+
+ private fun createInputDevice(deviceId: Int): InputDevice =
+ InputDevice(deviceId, 0 /*generation*/, 0 /*controllerNumber*/,
+ "Device $deviceId" /*name*/, 0 /*vendorId*/, 0 /*productId*/, "descriptor$deviceId",
+ true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/, null /*keyCharacterMap*/,
+ false /*hasVibrator*/, false /*hasMicrophone*/, false /*hasButtonUnderPad*/,
+ false /*hasSensor*/, true /*hasBattery*/)
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ private fun createMockListener(): IInputDeviceBatteryListener {
+ val listener = mock(IInputDeviceBatteryListener::class.java)
+ val binder = mock(Binder::class.java)
+ `when`(listener.asBinder()).thenReturn(binder)
+ return listener
+ }
+
+ @Test
+ fun testRegisterAndUnregisterBinderLifecycle() {
+ val listener = createMockListener()
+ // Ensure the binder lifecycle is tracked when registering a listener.
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
+ batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
+
+ // Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
+ batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
+ batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+ }
+
+ @Test
+ fun testOneListenerPerProcess() {
+ val listener1 = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
+ verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
+
+ // If a second listener is added for the same process, a security exception is thrown.
+ val listener2 = createMockListener()
+ try {
+ batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
+ fail("Expected security exception when registering more than one listener per process")
+ } catch (ignored: SecurityException) {
+ }
+ }
+
+ @Test
+ fun testProcessDeathRemovesListener() {
+ val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
+
+ // When the binder dies, the callback is unregistered.
+ deathRecipient.value!!.binderDied(listener.asBinder())
+ verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
+
+ // It is now possible to register the same listener again.
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
+ }
+
+ @Test
+ fun testRegisteringListenerNotifiesStateImmediately() {
+ `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
+ `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
+ verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
+ eq(STATUS_FULL), eq(1f), anyLong())
+
+ `when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
+ `when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
+ batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
+ verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
+ eq(STATUS_CHARGING), eq(0.78f), anyLong())
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 9ff7d69..2a6e6d8 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -731,7 +731,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
setMinimumScreenOffTimeoutConfig(5);
createService();
@@ -753,7 +753,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
setMinimumScreenOffTimeoutConfig(5);
createService();
@@ -1168,7 +1168,7 @@
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
return null;
- }).when(mDreamManagerInternalMock).startDream(anyBoolean());
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
final String pkg = mContextSpy.getOpPackageName();
final Binder token = new Binder();
@@ -1662,7 +1662,7 @@
forceDozing();
// Allow handleSandman() to be called asynchronously
advanceTime(500);
- verify(mDreamManagerInternalMock).startDream(eq(true));
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
}
@Test
@@ -1700,7 +1700,7 @@
// Allow handleSandman() to be called asynchronously
advanceTime(500);
- verify(mDreamManagerInternalMock).startDream(eq(true));
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 617a34f..91c2fe0 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -54,6 +54,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -83,7 +84,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -101,6 +101,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Spy;
import java.time.LocalDateTime;
import java.time.LocalTime;
@@ -137,8 +138,8 @@
private PackageManager mPackageManager;
@Mock
private IBinder mBinder;
- @Mock
- private DreamManagerInternal mDreamManager;
+ @Spy
+ private TestInjector mInjector;
@Captor
private ArgumentCaptor<Intent> mOrderedBroadcastIntent;
@Captor
@@ -207,10 +208,10 @@
addLocalService(WindowManagerInternal.class, mWindowManager);
addLocalService(PowerManagerInternal.class, mLocalPowerManager);
addLocalService(TwilightManager.class, mTwilightManager);
- addLocalService(DreamManagerInternal.class, mDreamManager);
-
+
+ mInjector = spy(new TestInjector());
mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
- mTwilightManager, new TestInjector());
+ mTwilightManager, mInjector);
try {
mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
} catch (SecurityException e) {/* ignore for permission denial */}
@@ -1321,84 +1322,53 @@
@Test
public void dreamWhenDocked() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
-
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
- }
-
- @Test
- public void noDreamWhenDocked_dreamsDisabled() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(false);
-
- triggerDockIntent();
- verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
- }
-
- @Test
- public void noDreamWhenDocked_dreamsWhenDockedDisabled() {
- setScreensaverActivateOnDock(false);
- setScreensaverEnabled(true);
-
- triggerDockIntent();
- verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void noDreamWhenDocked_keyguardNotShowing_interactive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
when(mPowerManager.isInteractive()).thenReturn(true);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager, never()).requestDream();
+ verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardShowing_interactive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardNotShowing_notInteractive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(false);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
@Test
public void dreamWhenDocked_keyguardShowing_notInteractive() {
- setScreensaverActivateOnDock(true);
- setScreensaverEnabled(true);
mUiManagerService.setStartDreamImmediatelyOnDock(false);
when(mWindowManager.isKeyguardShowingAndNotOccluded()).thenReturn(true);
when(mPowerManager.isInteractive()).thenReturn(false);
triggerDockIntent();
verifyAndSendResultBroadcast();
- verify(mDreamManager).requestDream();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
private void triggerDockIntent() {
@@ -1435,22 +1405,6 @@
mOrderedBroadcastIntent.getValue());
}
- private void setScreensaverEnabled(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.SCREENSAVER_ENABLED,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- private void setScreensaverActivateOnDock(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
private void requestAllPossibleProjectionTypes() throws RemoteException {
for (int i = 0; i < Integer.SIZE; ++i) {
mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
@@ -1467,11 +1421,17 @@
}
public TestInjector(int callingUid) {
- this.callingUid = callingUid;
+ this.callingUid = callingUid;
}
+ @Override
public int getCallingUid() {
return callingUid;
}
+
+ @Override
+ public void startDreamWhenDockedIfAppropriate(Context context) {
+ // do nothing
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c..0462e1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1220,20 +1221,34 @@
@Test
public void testCreateRecentTaskInfo_detachedTask() {
- final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+ final Task task = createTaskBuilder(".Task").build();
+ new ActivityBuilder(mSupervisor.mService)
+ .setTask(task)
+ .setUid(NOBODY_UID)
+ .setComponent(getUniqueComponentName())
+ .build();
final TaskDisplayArea tda = task.getDisplayArea();
assertTrue(task.isAttached());
assertTrue(task.supportsMultiWindow());
- RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+ RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ false /* getTasksAllowed */);
+
+ assertTrue(info.topActivity == null);
+ assertTrue(info.topActivityInfo == null);
+ assertTrue(info.baseActivity == null);
+
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
@@ -1242,7 +1257,8 @@
doReturn(false).when(tda).supportsNonResizableMultiWindow();
doReturn(false).when(task).isResizeable();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertFalse(info.supportsMultiWindow);
@@ -1250,7 +1266,8 @@
// the device supports it.
doReturn(true).when(tda).supportsNonResizableMultiWindow();
- info = mRecentTasks.createRecentTaskInfo(task, true);
+ info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+ true /* getTasksAllowed */);
assertTrue(info.supportsMultiWindow);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 6128428..b46e90d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1312,13 +1312,15 @@
secondActivity.app.setThread(null);
// This should do nothing from a non-attached caller.
assertFalse(task.navigateUpTo(secondActivity /* source record */,
- firstActivity.intent /* destIntent */, null /* destGrants */,
- 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+ firstActivity.intent /* destIntent */, null /* resolvedType */,
+ null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+ null /* resultGrants */));
secondActivity.app.setThread(thread);
assertTrue(task.navigateUpTo(secondActivity /* source record */,
- firstActivity.intent /* destIntent */, null /* destGrants */,
- 0 /* resultCode */, null /* resultData */, null /* resultGrants */));
+ firstActivity.intent /* destIntent */, null /* resolvedType */,
+ null /* destGrants */, 0 /* resultCode */, null /* resultData */,
+ null /* resultGrants */));
// The firstActivity uses default launch mode, so the activities between it and itself will
// be finished.
assertTrue(secondActivity.finishing);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1404de2..0b23359 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -195,6 +195,7 @@
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(mTask);
assertTaskFragmentAppearedTransaction();
}
@@ -365,6 +366,7 @@
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
+ assertTaskFragmentParentInfoChangedTransaction(task);
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
}
@@ -1205,7 +1207,8 @@
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
- * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
+ * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
+ * transaction,
*/
private void createTaskFragmentFromOrganizer(WindowContainerTransaction wct,
ActivityRecord ownerActivity, IBinder fragmentToken) {
@@ -1239,8 +1242,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // Appeared will come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1253,8 +1256,8 @@
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- // InfoChanged may come with parent info changed.
- final TaskFragmentTransaction.Change change = changes.get(changes.size() - 1);
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_INFO_CHANGED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1266,7 +1269,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_VANISHED, change.getType());
assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo());
assertEquals(mFragmentToken, change.getTaskFragmentToken());
@@ -1278,7 +1283,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
@@ -1290,7 +1297,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_TASK_FRAGMENT_ERROR, change.getType());
assertEquals(mErrorToken, change.getErrorCallbackToken());
final Bundle errorBundle = change.getErrorBundle();
@@ -1306,7 +1315,9 @@
final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
assertFalse(changes.isEmpty());
- final TaskFragmentTransaction.Change change = changes.get(0);
+
+ // Use remove to verify multiple transaction changes.
+ final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
assertEquals(intent, change.getActivityIntent());
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 76cd19b..68ac1d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1454,6 +1454,21 @@
verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean());
}
+ @Test
+ public void testGetTaskFragment() {
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment tf0 = createTaskFragmentWithParentTask(parentTask);
+ final TaskFragment tf1 = createTaskFragmentWithParentTask(parentTask);
+
+ assertNull("Could not find it because there's no organized TaskFragment",
+ parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+
+ doReturn(true).when(tf0).isOrganizedTaskFragment();
+
+ assertEquals("tf0 must be return because it's the organized TaskFragment.",
+ tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 45d8e22..7342c49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -393,6 +392,70 @@
}
@Test
+ public void testCreateInfo_PromoteSimilarClose() {
+ final Transition transition = createTestTransition(TRANSIT_CLOSE);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task topTask = createTask(mDisplayContent);
+ final Task belowTask = createTask(mDisplayContent);
+ final ActivityRecord showing = createActivityRecord(belowTask);
+ final ActivityRecord hiding = createActivityRecord(topTask);
+ final ActivityRecord closing = createActivityRecord(topTask);
+ // Start states.
+ changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ fillChangeMap(changes, topTask);
+ // End states.
+ showing.mVisibleRequested = true;
+ closing.mVisibleRequested = false;
+ hiding.mVisibleRequested = false;
+
+ participants.add(belowTask);
+ participants.add(hiding);
+ participants.add(closing);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ assertEquals(2, targets.size());
+ assertTrue(targets.contains(belowTask));
+ assertTrue(targets.contains(topTask));
+ }
+
+ @Test
+ public void testCreateInfo_PromoteSimilarOpen() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task topTask = createTask(mDisplayContent);
+ final Task belowTask = createTask(mDisplayContent);
+ final ActivityRecord showing = createActivityRecord(topTask);
+ final ActivityRecord opening = createActivityRecord(topTask);
+ final ActivityRecord closing = createActivityRecord(belowTask);
+ // Start states.
+ changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ fillChangeMap(changes, topTask);
+ // End states.
+ showing.mVisibleRequested = true;
+ opening.mVisibleRequested = true;
+ closing.mVisibleRequested = false;
+
+ participants.add(belowTask);
+ participants.add(showing);
+ participants.add(opening);
+ ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ assertEquals(2, targets.size());
+ assertTrue(targets.contains(belowTask));
+ assertTrue(targets.contains(topTask));
+ }
+
+ @Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
@@ -732,6 +795,11 @@
assertTrue(asyncRotationController.isTargetToken(decorToken));
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
+ if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+ // Only seamless window syncs its draw transaction with transition.
+ assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
+ assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
+ }
screenDecor.setOrientationChanging(false);
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
// executed until the transaction is committed, so it is still in target tokens.
@@ -1160,10 +1228,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
@@ -1205,10 +1271,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
@@ -1236,10 +1300,8 @@
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
- // Set to multi-windowing mode in order to set bounds.
- task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
- task.setBounds(taskBounds);
+ task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
activity.mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index b8da8cc..5c1c193 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -43,6 +43,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -195,14 +196,25 @@
mWm.mWindowMap.put(win.mClient.asBinder(), win);
final int w = 100;
final int h = 200;
+ final ClientWindowFrames outFrames = new ClientWindowFrames();
+ final MergedConfiguration outConfig = new MergedConfiguration();
+ final SurfaceControl outSurfaceControl = new SurfaceControl();
+ final InsetsState outInsetsState = new InsetsState();
+ final InsetsSourceControl[] outControls = new InsetsSourceControl[0];
+ final Bundle outBundle = new Bundle();
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
- new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
- new InsetsState(), new InsetsSourceControl[0], new Bundle());
+ outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
// Because the window is already invisible, it doesn't need to apply exiting animation
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
assertNull(win.mWinAnimator.mSurfaceController);
+
+ doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction);
+ // Invisible requested activity should not get the last config even if its view is visible.
+ mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
+ outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
+ assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index cfc0da7..9bcc136 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -731,6 +731,15 @@
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
assertFalse(startingApp.getOrientationChanging());
+
+ // Even if the display is frozen, invisible requested window should not be affected.
+ startingApp.mActivityRecord.mVisibleRequested = false;
+ mWm.startFreezingDisplay(0, 0, mDisplayContent);
+ doReturn(true).when(mWm.mPolicy).isScreenOn();
+ startingApp.getWindowFrames().setInsetsChanged(true);
+ startingApp.updateResizingWindowIfNeeded();
+ assertTrue(startingApp.isDrawn());
+ assertFalse(startingApp.getOrientationChanging());
}
@UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ef532f5..b99fd16 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -718,6 +718,10 @@
activity.mVisibleRequested = true;
}
+ static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask) {
+ return createTaskFragmentWithParentTask(parentTask, false /* createEmbeddedTask */);
+ }
+
/**
* Creates a {@link TaskFragment} and attach it to the {@code parentTask}.
*
@@ -1727,7 +1731,7 @@
}
void startTransition() {
- mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
+ mOrganizer.startTransition(mLastTransit, null);
}
void onTransactionReady(SurfaceControl.Transaction t) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
new file mode 100644
index 0000000..efb92f2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.provider.Settings
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assert.assertNotNull
+
+class AssistantAppHelper @JvmOverloads constructor(
+ val instr: Instrumentation,
+ val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
+) {
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
+ protected val defaultAssistant: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT)
+ protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE)
+
+ fun setDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ component.flattenToString())
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ component.flattenToString())
+ }
+
+ fun resetDefaultAssistant() {
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE,
+ defaultVoiceInteractionService)
+ Settings.Secure.putString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.ASSISTANT,
+ defaultAssistant)
+ }
+
+ /**
+ * Open Assistance UI.
+ *
+ * @param longpress open the UI by long pressing power button.
+ * Otherwise open the UI through vioceinteraction shell command directly.
+ */
+ @JvmOverloads
+ fun openUI(longpress: Boolean = false) {
+ if (longpress) {
+ uiDevice.executeShellCommand("input keyevent --longpress KEYCODE_POWER")
+ } else {
+ uiDevice.executeShellCommand("cmd voiceinteraction show")
+ }
+ val ui = uiDevice.wait(
+ Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+ FIND_TIMEOUT)
+ assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index d11ca49..fa83f22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -64,7 +64,7 @@
wmHelper: WindowManagerStateHelper,
direction: Direction
): Boolean {
- val ratioForScreenBottom = 0.97
+ val ratioForScreenBottom = 0.99
val fullView = wmHelper.getWindowRegion(component)
require(!fullView.isEmpty) { "Target $component view not found." }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 45a4730..e90eed1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -80,20 +80,21 @@
public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".NotificationActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".NotificationActivity");
public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
- public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
- FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+ public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
- new ComponentName(FLICKER_APP_PACKAGE,
- FLICKER_APP_PACKAGE + ".GameActivity");
+ new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".GameActivity");
+
+ public static final ComponentName ASSISTANT_SERVICE_COMPONENT_NAME =
+ new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".AssistantInteractionService");
}