Merge "Check orientation when retrieving FP location"
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 448a808..e7f0327 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -43,6 +43,7 @@
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Handler;
@@ -163,6 +164,7 @@
@GuardedBy("mLock")
private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+ private volatile boolean mHasBattery = true;
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
private volatile boolean mExemptListLoaded;
@@ -204,6 +206,15 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
+ case Intent.ACTION_BATTERY_CHANGED: {
+ final boolean hasBattery =
+ intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, mHasBattery);
+ if (mHasBattery != hasBattery) {
+ mHasBattery = hasBattery;
+ mConfigObserver.updateEnabledStatus();
+ }
+ }
+ break;
case Intent.ACTION_BATTERY_LEVEL_CHANGED:
onBatteryLevelChanged();
break;
@@ -713,6 +724,12 @@
return packages;
}
+ private boolean isTareSupported() {
+ // TARE is presently designed for devices with batteries. Don't enable it on
+ // battery-less devices for now.
+ return mHasBattery;
+ }
+
@GuardedBy("mLock")
private void loadInstalledPackageListLocked() {
mPkgCache.clear();
@@ -731,6 +748,7 @@
private void registerListeners() {
final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
@@ -765,6 +783,7 @@
return;
}
synchronized (mLock) {
+ mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties());
loadInstalledPackageListLocked();
final boolean isFirstSetup = !mScribe.recordExists();
if (isFirstSetup) {
@@ -803,10 +822,7 @@
if (mBootPhase < PHASE_THIRD_PARTY_APPS_CAN_START || !mIsEnabled) {
return;
}
- synchronized (mLock) {
- mHandler.post(this::setupHeavyWork);
- mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties());
- }
+ mHandler.post(this::setupHeavyWork);
}
private void onBootPhaseBootCompleted() {
@@ -985,7 +1001,7 @@
@Override
public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName,
@NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
- if (isSystem(userId, pkgName)) {
+ if (!isTareSupported() || isSystem(userId, pkgName)) {
// The system's affordability never changes.
return;
}
@@ -1008,6 +1024,9 @@
@Override
public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener) {
+ if (!isTareSupported()) {
+ return;
+ }
mStateChangeListeners.add(listener);
}
@@ -1180,11 +1199,10 @@
private void updateEnabledStatus() {
// User setting should override DeviceConfig setting.
- // NOTE: There's currently no way for a user to reset the value (via UI), so if a user
- // manually toggles TARE via UI, we'll always defer to the user's current setting
final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE,
KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1);
- final boolean isTareEnabled = Settings.Global.getInt(mContentResolver,
+ final boolean isTareEnabled = isTareSupported()
+ && Settings.Global.getInt(mContentResolver,
Settings.Global.ENABLE_TARE, isTareEnabledDC ? 1 : 0) == 1;
if (mIsEnabled != isTareEnabled) {
mIsEnabled = isTareEnabled;
@@ -1268,6 +1286,10 @@
}
private void dumpInternal(final IndentingPrintWriter pw, final boolean dumpAll) {
+ if (!isTareSupported()) {
+ pw.print("Unsupported by device");
+ return;
+ }
synchronized (mLock) {
pw.print("Is enabled: ");
pw.println(mIsEnabled);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 368087c..73c6664 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3376,6 +3376,7 @@
method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
method public int describeContents();
+ method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 5bdd966..1333f0d 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
package android.hardware;
-parcelable HardwareBuffer;
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index c614cdb..72d8122 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1433,10 +1433,17 @@
}
@NonNull
- private static float[] createEnrollStageThresholds(@NonNull Context context) {
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ private float[] createEnrollStageThresholds(@NonNull Context context) {
// TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
- final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
- com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+ final String[] enrollStageThresholdStrings;
+ if (isPowerbuttonFps()) {
+ enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_sfps_enroll_stage_thresholds);
+ } else {
+ enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+ }
final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
for (int i = 0; i < enrollStageThresholds.length; i++) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index da20626..0ad596b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1767,6 +1767,49 @@
}
/**
+ * Measured energy delta from the previous reading.
+ */
+ public static final class MeasuredEnergyDetails {
+ /**
+ * Description of the energy consumer, such as CPU, DISPLAY etc
+ */
+ public static final class EnergyConsumer {
+ /**
+ * See android.hardware.power.stats.EnergyConsumerType
+ */
+ public int type;
+ /**
+ * Used when there are multipe energy consumers of the same type, such
+ * as CPU clusters, multiple displays on foldable devices etc.
+ */
+ public int ordinal;
+ /**
+ * Human-readable name of the energy consumer, e.g. "CPU"
+ */
+ public String name;
+ }
+ public EnergyConsumer[] consumers;
+ public long[] chargeUC;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < consumers.length; i++) {
+ if (chargeUC[i] == POWER_DATA_UNAVAILABLE) {
+ continue;
+ }
+ if (sb.length() != 0) {
+ sb.append(' ');
+ }
+ sb.append(consumers[i].name);
+ sb.append('=');
+ sb.append(chargeUC[i]);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
* Battery history record.
*/
public static final class HistoryItem {
@@ -1886,6 +1929,7 @@
public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
public static final int STATE2_CELLULAR_HIGH_TX_POWER_FLAG = 1 << 19;
public static final int STATE2_USB_DATA_LINK_FLAG = 1 << 18;
+ public static final int STATE2_EXTENSIONS_FLAG = 1 << 17;
public static final int MOST_INTERESTING_STATES2 =
STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
@@ -1905,6 +1949,9 @@
// Non-null when there is more detailed information at this step.
public HistoryStepDetails stepDetails;
+ // Non-null when there is measured energy information
+ public MeasuredEnergyDetails measuredEnergyDetails;
+
public static final int EVENT_FLAG_START = 0x8000;
public static final int EVENT_FLAG_FINISH = 0x4000;
@@ -2113,6 +2160,7 @@
eventCode = EVENT_NONE;
eventTag = null;
tagsFirstOccurrence = false;
+ measuredEnergyDetails = null;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2162,6 +2210,7 @@
}
tagsFirstOccurrence = o.tagsFirstOccurrence;
currentTime = o.currentTime;
+ measuredEnergyDetails = o.measuredEnergyDetails;
}
public boolean sameNonEvent(HistoryItem o) {
@@ -6951,6 +7000,14 @@
item.append("\"");
}
}
+ if ((rec.states2 & HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
+ if (!checkin) {
+ item.append(" ext=");
+ if (rec.measuredEnergyDetails != null) {
+ item.append("E");
+ }
+ }
+ }
if (rec.eventCode != HistoryItem.EVENT_NONE) {
item.append(checkin ? "," : " ");
if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
@@ -7075,6 +7132,25 @@
item.append("\n");
}
}
+ if (rec.measuredEnergyDetails != null) {
+ if (!checkin) {
+ item.append(" Energy: ");
+ item.append(rec.measuredEnergyDetails);
+ item.append("\n");
+ } else {
+ item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
+ item.append(HISTORY_DATA); item.append(",0,XE");
+ for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
+ if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+ item.append(',');
+ item.append(rec.measuredEnergyDetails.consumers[i].name);
+ item.append('=');
+ item.append(rec.measuredEnergyDetails.chargeUC[i]);
+ }
+ }
+ item.append("\n");
+ }
+ }
oldState = rec.states;
oldState2 = rec.states2;
// Clear High Tx Power Flag for volta positioning
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 57ba7e9..f9bb880 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -653,7 +653,7 @@
break;
case MotionEvent.ACTION_MOVE:
- if (mInLongPress || mInContextClick) {
+ if ((mCurrentDownEvent == null) || mInLongPress || mInContextClick) {
break;
}
@@ -736,6 +736,9 @@
break;
case MotionEvent.ACTION_UP:
+ if (mCurrentDownEvent == null) {
+ break;
+ }
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f53dd0c..e39b7cd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9085,6 +9085,12 @@
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
+
+ ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
+ gestures.add(SelectGesture.class);
+ gestures.add(DeleteGesture.class);
+ gestures.add(InsertGesture.class);
+ outAttrs.setSupportedHandwritingGestures(gestures);
return ic;
}
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 884ca77..3250dd8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -57,6 +57,12 @@
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
- void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken,
- in WindowContainerTransaction wct);
+ void onTransactionHandled(in IBinder transactionToken, in WindowContainerTransaction wct,
+ int transitionType, boolean shouldApplyIndependently);
+
+ /**
+ * Requests the server to apply the given {@link WindowContainerTransaction}.
+ */
+ void applyTransaction(in WindowContainerTransaction wct, int transitionType,
+ boolean shouldApplyIndependently);
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 40bffde..3aee472 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,23 +16,32 @@
package android.window;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.RemoteAnimationDefinition;
+import android.view.WindowManager;
import java.util.List;
import java.util.concurrent.Executor;
@@ -160,21 +169,111 @@
* {@link #onTransactionReady(TaskFragmentTransaction)}
* @param wct {@link WindowContainerTransaction} that the server should apply for
* update of the transaction.
- * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission
- * requirement.
+ * @param transitionType {@link WindowManager.TransitionType} if it needs to start a
+ * transition.
+ * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
+ * transition, which will be queued until the sync engine is
+ * free if there is any other active sync. If {@code false},
+ * the {@code wct} will be directly applied to the active sync.
+ * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
+ * for permission enforcement.
* @hide
*/
public void onTransactionHandled(@NonNull IBinder transactionToken,
- @NonNull WindowContainerTransaction wct) {
+ @NonNull WindowContainerTransaction wct,
+ @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
wct.setTaskFragmentOrganizer(mInterface);
try {
- getController().onTransactionHandled(mInterface, transactionToken, wct);
+ getController().onTransactionHandled(transactionToken, wct, transitionType,
+ shouldApplyIndependently);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
+ * Routes to {@link ITaskFragmentOrganizerController#applyTransaction} instead of
+ * {@link IWindowOrganizerController#applyTransaction} for the different transition options.
+ *
+ * @see #applyTransaction(WindowContainerTransaction, int, boolean, boolean)
+ */
+ @Override
+ public void applyTransaction(@NonNull WindowContainerTransaction wct) {
+ // TODO(b/207070762) doing so to keep CTS compatibility. Remove in the next release.
+ applyTransaction(wct, getTransitionType(wct), false /* shouldApplyIndependently */);
+ }
+
+ /**
+ * Requests the server to apply the given {@link WindowContainerTransaction}.
+ *
+ * @param wct {@link WindowContainerTransaction} to apply.
+ * @param transitionType {@link WindowManager.TransitionType} if it needs to start a
+ * transition.
+ * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
+ * transition, which will be queued until the sync engine is
+ * free if there is any other active sync. If {@code false},
+ * the {@code wct} will be directly applied to the active sync.
+ * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission
+ * for permission enforcement.
+ * @hide
+ */
+ public void applyTransaction(@NonNull WindowContainerTransaction wct,
+ @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
+ if (wct.isEmpty()) {
+ return;
+ }
+ wct.setTaskFragmentOrganizer(mInterface);
+ try {
+ getController().applyTransaction(wct, transitionType, shouldApplyIndependently);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the default {@link WindowManager.TransitionType} based on the requested
+ * {@link WindowContainerTransaction}.
+ * @hide
+ */
+ // TODO(b/207070762): let Extensions to set the transition type instead.
+ @WindowManager.TransitionType
+ public static int getTransitionType(@NonNull WindowContainerTransaction wct) {
+ if (wct.isEmpty()) {
+ return TRANSIT_NONE;
+ }
+ for (WindowContainerTransaction.Change change : wct.getChanges().values()) {
+ if ((change.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
+ // Treat as TRANSIT_CHANGE when there is TaskFragment resizing.
+ return TRANSIT_CHANGE;
+ }
+ }
+ boolean containsCreatingTaskFragment = false;
+ boolean containsDeleteTaskFragment = false;
+ final List<WindowContainerTransaction.HierarchyOp> ops = wct.getHierarchyOps();
+ for (int i = ops.size() - 1; i >= 0; i--) {
+ final int type = ops.get(i).getType();
+ if (type == HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT) {
+ // Treat as TRANSIT_CHANGE when there is activity reparent.
+ return TRANSIT_CHANGE;
+ }
+ if (type == HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT) {
+ containsCreatingTaskFragment = true;
+ } else if (type == HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT) {
+ containsDeleteTaskFragment = true;
+ }
+ }
+ if (containsCreatingTaskFragment) {
+ return TRANSIT_OPEN;
+ }
+ if (containsDeleteTaskFragment) {
+ return TRANSIT_CLOSE;
+ }
+
+ // Use TRANSIT_CHANGE as default.
+ return TRANSIT_CHANGE;
+ }
+
+ /**
* Called when a TaskFragment is created and organized by this organizer.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
@@ -309,22 +408,8 @@
}
// Notify the server, and the server should apply the WindowContainerTransaction.
- onTransactionHandled(transaction.getTransactionToken(), wct);
- }
-
- @Override
- public void applyTransaction(@NonNull WindowContainerTransaction t) {
- t.setTaskFragmentOrganizer(mInterface);
- super.applyTransaction(t);
- }
-
- // Suppress the lint because it is not a registration method.
- @SuppressWarnings("ExecutorRegistration")
- @Override
- public int applySyncTransaction(@NonNull WindowContainerTransaction t,
- @NonNull WindowContainerTransactionCallback callback) {
- t.setTaskFragmentOrganizer(mInterface);
- return super.applySyncTransaction(t, callback);
+ onTransactionHandled(transaction.getTransactionToken(), wct, getTransitionType(wct),
+ false /* shouldApplyIndependently */);
}
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index db58704..a99c6be 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -443,6 +443,17 @@
}
/**
+ * Finds and removes a task and its children using its container token. The task is removed
+ * from recents.
+ * @param containerToken ContainerToken of Task to be removed
+ */
+ @NonNull
+ public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
* Sends a pending intent in sync.
* @param sender The PendingIntent sender.
* @param intent The fillIn intent to patch over the sender's base intent.
@@ -740,10 +751,8 @@
* @hide
*/
@NonNull
- WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
- if (mTaskFragmentOrganizer != null) {
- throw new IllegalStateException("Can't set multiple organizers for one transaction.");
- }
+ public WindowContainerTransaction setTaskFragmentOrganizer(
+ @NonNull ITaskFragmentOrganizer organizer) {
mTaskFragmentOrganizer = organizer;
return this;
}
@@ -1141,6 +1150,7 @@
public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
+ public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1270,6 +1280,13 @@
.build();
}
+ /** create a hierarchy op for deleting a task **/
+ public static HierarchyOp createForRemoveTask(@NonNull IBinder container) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ .setContainer(container)
+ .build();
+ }
+
/** Only creates through {@link Builder}. */
private HierarchyOp(int type) {
mType = type;
@@ -1453,6 +1470,8 @@
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
return "{setAlwaysOnTop: container=" + mContainer
+ " alwaysOnTop=" + mAlwaysOnTop + "}";
+ case HIERARCHY_OP_TYPE_REMOVE_TASK:
+ return "{RemoveTask: task=" + mContainer + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 6909965..7001c69 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.HistoryStepDetails;
import android.os.BatteryStats.HistoryTag;
+import android.os.BatteryStats.MeasuredEnergyDetails;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Process;
@@ -113,6 +114,9 @@
// therefore the tag value is written in the parcel
static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+ static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
+ static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
+
private final Parcel mHistoryBuffer;
private final File mSystemDir;
private final HistoryStepDetailsCalculator mStepDetailsCalculator;
@@ -183,6 +187,7 @@
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
private long mHistoryBaseTimeMs;
+ private boolean mMeasuredEnergyHeaderWritten = false;
private byte mLastHistoryStepLevel = 0;
@@ -293,6 +298,7 @@
mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
+ mMeasuredEnergyHeaderWritten = false;
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -933,6 +939,16 @@
}
/**
+ * Records measured energy data.
+ */
+ public void recordMeasuredEnergyDetails(long elapsedRealtimeMs, long uptimeMs,
+ MeasuredEnergyDetails measuredEnergyDetails) {
+ mHistoryCur.measuredEnergyDetails = measuredEnergyDetails;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
* Records a history item with the amount of charge consumed by WiFi. Used on certain devices
* equipped with on-device power metering.
*/
@@ -1219,6 +1235,8 @@
for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
}
+ mMeasuredEnergyHeaderWritten = false;
+
// Make a copy of mHistoryCur.
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
@@ -1256,6 +1274,7 @@
cur.eventCode = HistoryItem.EVENT_NONE;
cur.eventTag = null;
cur.tagsFirstOccurrence = false;
+ cur.measuredEnergyDetails = null;
if (DEBUG) {
Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
@@ -1348,6 +1367,7 @@
return;
}
+ int extensionFlags = 0;
final long deltaTime = cur.time - last.time;
final int lastBatteryLevelInt = buildBatteryLevelInt(last);
final int lastStateInt = buildStateInt(last);
@@ -1374,6 +1394,17 @@
if (stateIntChanged) {
firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
}
+ if (cur.measuredEnergyDetails != null) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG;
+ if (!mMeasuredEnergyHeaderWritten) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
+ }
+ }
+ if (extensionFlags != 0) {
+ cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ } else {
+ cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG;
+ }
final boolean state2IntChanged = cur.states2 != last.states2;
if (state2IntChanged) {
firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
@@ -1491,6 +1522,28 @@
}
dest.writeDouble(cur.modemRailChargeMah);
dest.writeDouble(cur.wifiRailChargeMah);
+ if (extensionFlags != 0) {
+ dest.writeInt(extensionFlags);
+ if (cur.measuredEnergyDetails != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.measuredEnergyDetails);
+ }
+ if (!mMeasuredEnergyHeaderWritten) {
+ MeasuredEnergyDetails.EnergyConsumer[] consumers =
+ cur.measuredEnergyDetails.consumers;
+ dest.writeInt(consumers.length);
+ for (MeasuredEnergyDetails.EnergyConsumer consumer : consumers) {
+ dest.writeInt(consumer.type);
+ dest.writeInt(consumer.ordinal);
+ dest.writeString(consumer.name);
+ }
+ mMeasuredEnergyHeaderWritten = true;
+ }
+ for (long chargeUC : cur.measuredEnergyDetails.chargeUC) {
+ dest.writeLong(chargeUC);
+ }
+ }
+ }
}
private int buildBatteryLevelInt(HistoryItem h) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 1bf878cb..ee3d15b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -33,6 +33,7 @@
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
+ private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails;
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
mBatteryStatsHistory = history;
@@ -198,6 +199,40 @@
}
cur.modemRailChargeMah = src.readDouble();
cur.wifiRailChargeMah = src.readDouble();
+ if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
+ final int extensionFlags = src.readInt();
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG) != 0) {
+ if (mMeasuredEnergyDetails == null) {
+ mMeasuredEnergyDetails = new BatteryStats.MeasuredEnergyDetails();
+ }
+
+ final int consumerCount = src.readInt();
+ mMeasuredEnergyDetails.consumers =
+ new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
+ mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
+ for (int i = 0; i < consumerCount; i++) {
+ BatteryStats.MeasuredEnergyDetails.EnergyConsumer consumer =
+ new BatteryStats.MeasuredEnergyDetails.EnergyConsumer();
+ consumer.type = src.readInt();
+ consumer.ordinal = src.readInt();
+ consumer.name = src.readString();
+ mMeasuredEnergyDetails.consumers[i] = consumer;
+ }
+ }
+
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG) != 0) {
+ if (mMeasuredEnergyDetails == null) {
+ throw new IllegalStateException("MeasuredEnergyDetails without a header");
+ }
+
+ for (int i = 0; i < mMeasuredEnergyDetails.chargeUC.length; i++) {
+ mMeasuredEnergyDetails.chargeUC[i] = src.readLong();
+ }
+ cur.measuredEnergyDetails = mMeasuredEnergyDetails;
+ }
+ } else {
+ cur.measuredEnergyDetails = null;
+ }
}
private boolean readHistoryTag(Parcel src, int index, BatteryStats.HistoryTag outTag) {
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index c5b3d8a..8305bd0 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -111,10 +111,10 @@
ScopedUtfChars path(env, filePath);
::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (rfd.get() < 0) {
- return rfd.get();
+ return -errno;
}
- if (auto err = ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data); err < 0) {
- return err;
+ if (::ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
+ return -errno;
}
if (data->digest_algorithm != FS_VERITY_HASH_ALG_SHA256) {
diff --git a/core/proto/android/app/notificationmanager.proto b/core/proto/android/app/notificationmanager.proto
index b88315e..3342c79 100644
--- a/core/proto/android/app/notificationmanager.proto
+++ b/core/proto/android/app/notificationmanager.proto
@@ -45,6 +45,8 @@
MEDIA = 7;
// System (catch-all for non-never suppressible sounds) are prioritized.
SYSTEM = 8;
+ // Priority conversations are prioritized
+ CONVERSATIONS = 9;
}
repeated Category priority_categories = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5b4fc56..4d41c30 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -923,7 +923,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.SEND_SMS"
@@ -937,7 +937,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.RECEIVE_SMS"
@@ -951,7 +951,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.READ_SMS"
@@ -965,7 +965,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.RECEIVE_WAP_PUSH"
@@ -979,7 +979,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.RECEIVE_MMS"
@@ -1014,7 +1014,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
@hide Pending API council approval -->
@@ -1059,7 +1059,7 @@
targetSdkVersion}</a> is 4 or higher.
<p> This is a soft restricted permission which cannot be held by an app it its
- full form until the installer on record whitelists the permission.
+ full form until the installer on record allowlists the permission.
Specifically, if the permission is allowlisted the holder app can access
external storage and the visual and aural media collections while if the
permission is not allowlisted the holder app can only access to the visual
@@ -1239,7 +1239,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -1301,7 +1301,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.READ_CALL_LOG"
@@ -1325,7 +1325,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
-->
<permission android:name="android.permission.WRITE_CALL_LOG"
@@ -1341,7 +1341,7 @@
<p>Protection level: dangerous
<p> This is a hard restricted permission which cannot be held by an app until
- the installer on record whitelists the permission. For more details see
+ the installer on record allowlists the permission. For more details see
{@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
@deprecated Applications should use {@link android.telecom.CallRedirectionService} instead
@@ -4771,7 +4771,7 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to allowlist restricted permissions
- on any of the whitelists.
+ on any of the allowlists.
@hide -->
<permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
android:protectionLevel="signature|installer" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e7cae76..12e8544 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4859,6 +4859,14 @@
<item>0.875</item>
</string-array>
+ <!-- When each intermediate SFPS enroll stage ends, as a fraction of total progress. -->
+ <string-array name="config_sfps_enroll_stage_thresholds" translatable="false">
+ <item>0</item> <!-- [-1 // <0/25] No animation 1x -->
+ <item>0.36</item> <!-- [0 to 8 // <9/25] Pad center 9x -->
+ <item>0.52</item> <!-- [9 to 12 // <13/25] Tip 4x -->
+ <item>0.76</item> <!-- [13 to 18 // <19/25] Left 6x -->
+ </string-array> <!-- [19 to 24 // <25/25] Right 6x -->
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
@@ -5066,6 +5074,10 @@
<!-- If true, the wallpaper will scale regardless of the value of shouldZoomOutWallpaper() -->
<bool name="config_alwaysScaleWallpaper">false</bool>
+ <!-- Set to true to offset the wallpaper when using multiple displays so that it's centered
+ at the same position as in the largest display.-->
+ <bool name="config_offsetWallpaperToCenterOfLargestDisplay">false</bool>
+
<!-- Package name that will receive an explicit manifest broadcast for
android.os.action.POWER_SAVE_MODE_CHANGED. -->
<string name="config_powerSaveModeChangedListenerPackage" translatable="false"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aa0bae6..b571a53 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2701,7 +2701,7 @@
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
-
+ <java-symbol type="array" name="config_sfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" />
@@ -4373,6 +4373,10 @@
<!-- The max scale for the wallpaper when it's zoomed in -->
<java-symbol type="dimen" name="config_wallpaperMaxScale"/>
+ <!-- Set to true to offset the wallpaper when using multiple displays so that it's centered
+ at the same position than in the largest display. -->
+ <java-symbol type="bool" name="config_offsetWallpaperToCenterOfLargestDisplay" />
+
<!-- Set to true to enable the user switcher on the keyguard. -->
<java-symbol type="bool" name="config_keyguardUserSwitcher" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 3170177..dfff75b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1,12 +1,6 @@
{
"version": "1.0.0",
"messages": {
- "-2146181682": {
- "message": "Releasing screen wakelock, obscured by %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_KEEP_SCREEN_ON",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-2127842445": {
"message": "Clearing startingData for token=%s",
"level": "VERBOSE",
@@ -1783,6 +1777,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-384639879": {
+ "message": "Acquiring screen wakelock due to %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_KEEP_SCREEN_ON",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-381522987": {
"message": "Display %d state is now (%d), so update recording?",
"level": "VERBOSE",
@@ -1825,6 +1825,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-353495930": {
+ "message": "TaskFragmentTransaction changes are not collected in transition because there is an ongoing sync for applySyncTransaction().",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+ },
"-347866078": {
"message": "Setting move animation on %s",
"level": "VERBOSE",
@@ -2389,6 +2395,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "191486492": {
+ "message": "handleNotObscuredLocked: %s was holding screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_KEEP_SCREEN_ON",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"200829729": {
"message": "ScreenRotationAnimation onAnimationEnd",
"level": "DEBUG",
@@ -2509,6 +2521,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "286170861": {
+ "message": "Creating Pending Transition for TaskFragment: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+ },
"288485303": {
"message": "Attempted to set remove mode to a display that does not exist: %d",
"level": "WARN",
@@ -3055,6 +3073,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "782864973": {
+ "message": "Releasing screen wakelock, obscured by %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_KEEP_SCREEN_ON",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"791468751": {
"message": "Pausing rotation during re-position",
"level": "DEBUG",
@@ -4303,18 +4327,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "2088592090": {
- "message": "handleNotObscuredLocked: %s was holding screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_KEEP_SCREEN_ON",
- "at": "com\/android\/server\/wm\/RootWindowContainer.java"
- },
- "2096635066": {
- "message": "Acquiring screen wakelock due to %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_KEEP_SCREEN_ON",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"2100457473": {
"message": "Task=%d contains embedded TaskFragment. Disabled all input during TaskFragment remote animation.",
"level": "DEBUG",
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 4102732..5727b91 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
+import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -109,7 +110,7 @@
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler;
- private final Object mLock = new Object();
+ final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
public SplitController() {
@@ -209,8 +210,10 @@
}
}
- // Notify the server, and the server should apply the WindowContainerTransaction.
- mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct);
+ // Notify the server, and the server should apply and merge the
+ // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
+ mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
+ getTransitionType(wct), false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -221,6 +224,9 @@
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is created.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
@@ -245,6 +251,9 @@
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed.
* @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@@ -430,6 +439,9 @@
* transaction operation.
* @param exception exception from the server side.
*/
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
@VisibleForTesting
@GuardedBy("mLock")
void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@@ -869,23 +881,23 @@
* Called when we have been waiting too long for the TaskFragment to become non-empty after
* creation.
*/
+ @GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- onTaskFragmentAppearEmptyTimeout(wct, container);
- mPresenter.applyTransaction(wct);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ onTaskFragmentAppearEmptyTimeout(wct, container);
+ // Can be applied independently as a timeout callback.
+ mPresenter.applyTransaction(wct, getTransitionType(wct),
+ true /* shouldApplyIndependently */);
}
/**
* Called when we have been waiting too long for the TaskFragment to become non-empty after
* creation.
*/
+ @GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- synchronized (mLock) {
- mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
- }
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
/**
@@ -1714,7 +1726,9 @@
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityCreated(wct, activity);
- mPresenter.applyTransaction(wct);
+ // The WCT should be applied and merged to the activity launch transition.
+ mPresenter.applyTransaction(wct, getTransitionType(wct),
+ false /* shouldApplyIndependently */);
}
}
@@ -1723,7 +1737,10 @@
synchronized (mLock) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
SplitController.this.onActivityConfigurationChanged(wct, activity);
- mPresenter.applyTransaction(wct);
+ // The WCT should be applied and merged to the Task change transition so that the
+ // placeholder is launched in the same transition.
+ mPresenter.applyTransaction(wct, getTransitionType(wct),
+ false /* shouldApplyIndependently */);
}
}
@@ -1775,7 +1792,10 @@
final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct,
taskId, intent, launchingActivity);
if (launchedInTaskFragment != null) {
- mPresenter.applyTransaction(wct);
+ // Make sure the WCT is applied immediately instead of being queued so that the
+ // TaskFragment will be ready before activity attachment.
+ mPresenter.applyTransaction(wct, getTransitionType(wct),
+ false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 344ffc7..2843b14 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -28,6 +28,7 @@
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -251,6 +252,7 @@
return mInfo;
}
+ @GuardedBy("mController.mLock")
void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
@@ -258,10 +260,12 @@
// it is still empty after timeout.
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
mAppearEmptyTimeout = () -> {
- mAppearEmptyTimeout = null;
- // Call without the pass-in wct when timeout. We need to applyWct directly
- // in this case.
- mController.onTaskFragmentAppearEmptyTimeout(this);
+ synchronized (mController.mLock) {
+ mAppearEmptyTimeout = null;
+ // Call without the pass-in wct when timeout. We need to applyWct directly
+ // in this case.
+ mController.onTaskFragmentAppearEmptyTimeout(this);
+ }
};
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index d0eaf34..58a627b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -20,7 +20,6 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -33,7 +32,6 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
-import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentTransaction;
@@ -67,10 +65,7 @@
private WindowContainerTransaction mTransaction;
@Mock
private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
- @Mock
private SplitController mSplitController;
- @Mock
- private Handler mHandler;
private JetpackTaskFragmentOrganizer mOrganizer;
@Before
@@ -78,8 +73,9 @@
MockitoAnnotations.initMocks(this);
mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
mOrganizer.registerOrganizer();
+ mSplitController = new SplitController();
spyOn(mOrganizer);
- doReturn(mHandler).when(mSplitController).getHandler();
+ spyOn(mSplitController);
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index f743610..4dbbc04 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -127,7 +127,7 @@
mSplitPresenter = mSplitController.mPresenter;
spyOn(mSplitController);
spyOn(mSplitPresenter);
- doNothing().when(mSplitPresenter).applyTransaction(any());
+ doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
@@ -1000,7 +1000,8 @@
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentAppeared(any(), eq(info));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
@Test
@@ -1014,7 +1015,8 @@
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
@Test
@@ -1028,7 +1030,8 @@
mSplitController.onTransactionReady(transaction);
verify(mSplitController).onTaskFragmentVanished(any(), eq(info));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
@Test
@@ -1043,7 +1046,8 @@
verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
eq(taskConfig));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
@Test
@@ -1062,7 +1066,8 @@
verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType),
eq(exception));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
@Test
@@ -1078,7 +1083,8 @@
verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent),
eq(activityToken));
- verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+ verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
}
/** Creates a mock activity in the organizer process. */
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 dd50fa0..fd4c85fa 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
@@ -92,6 +92,12 @@
@Override
+ public void startRemoveTransition(WindowContainerTransaction wct) {
+ final int type = WindowManager.TRANSIT_CLOSE;
+ mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+ }
+
+ @Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index c947cf1..8da4c6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -40,4 +40,12 @@
*
*/
void startMinimizedModeTransition(WindowContainerTransaction wct);
+
+ /**
+ * Starts close window transition
+ *
+ * @param wct the {@link WindowContainerTransaction} that closes the task
+ *
+ */
+ void startRemoveTransition(WindowContainerTransaction wct);
}
\ No newline at end of file
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 f1465f4..e9f9bb5 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
@@ -287,6 +287,9 @@
}
private void releaseWindowDecor(T windowDecor) {
+ if (windowDecor == null) {
+ return;
+ }
try {
windowDecor.close();
} catch (Exception e) {
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 ad53956..83aa539 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
@@ -148,7 +148,13 @@
public void onClick(View v) {
final int id = v.getId();
if (id == R.id.close_window) {
- mActivityTaskManager.removeTask(mTaskId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.removeTask(mTaskToken);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startRemoveTransition(wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
} else if (id == R.id.maximize_window) {
WindowContainerTransaction wct = new WindowContainerTransaction();
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index cb74315..cc987dc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -151,15 +151,15 @@
assertLayers {
if (landscapePosLeft) {
this.splitAppLayerBoundsSnapToDivider(
- component, landscapePosLeft, portraitPosTop, endRotation)
+ component, landscapePosLeft, portraitPosTop, endRotation)
+ } else {
+ this.splitAppLayerBoundsSnapToDivider(
+ component, landscapePosLeft, portraitPosTop, endRotation)
.then()
.isInvisible(component)
.then()
.splitAppLayerBoundsSnapToDivider(
component, landscapePosLeft, portraitPosTop, endRotation)
- } else {
- this.splitAppLayerBoundsSnapToDivider(
- component, landscapePosLeft, portraitPosTop, endRotation)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 87a863d..52e5d7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -97,13 +97,29 @@
secondaryApp: IComponentMatcher,
) {
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withWindowSurfaceAppeared(primaryApp)
.withWindowSurfaceAppeared(secondaryApp)
.withSplitDividerVisible()
.waitForAndVerify()
}
+ fun enterSplit(
+ wmHelper: WindowManagerStateHelper,
+ tapl: LauncherInstrumentation,
+ primaryApp: SplitScreenHelper,
+ secondaryApp: SplitScreenHelper
+ ) {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ splitFromOverview(tapl)
+ waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
fun splitFromOverview(tapl: LauncherInstrumentation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
@@ -267,24 +283,35 @@
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 2 / 3, displayBounds.height * 2 / 3))
+ dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
.waitForAndVerify()
}
fun dragDividerToDismissSplit(
device: UiDevice,
- wmHelper: WindowManagerStateHelper
+ wmHelper: WindowManagerStateHelper,
+ dragToRight: Boolean,
+ dragToBottom: Boolean
) {
val displayBounds = wmHelper.currentState.layerState
.displays.firstOrNull { !it.isVirtual }
?.layerStackSpace
?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width * 4 / 5, displayBounds.height * 4 / 5))
+ dividerBar.drag(Point(
+ if (dragToRight) {
+ displayBounds.width * 4 / 5
+ } else {
+ displayBounds.width * 1 / 5
+ },
+ if (dragToBottom) {
+ displayBounds.height * 4 / 5
+ } else {
+ displayBounds.height * 1 / 5
+ }))
}
fun doubleTapDividerToSwitch(device: UiDevice) {
@@ -296,7 +323,7 @@
dividerBar.click()
}
- fun copyContentFromLeftToRight(
+ fun copyContentInSplit(
instrumentation: Instrumentation,
device: UiDevice,
sourceApp: IComponentNameMatcher,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 6cbb685..102a78b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -30,8 +30,6 @@
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,30 +47,18 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- protected val textEditApp = SplitScreenHelper.getIme(instrumentation)
-
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
+ private val textEditApp = SplitScreenHelper.getIme(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- textEditApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(primaryApp.appName)
- .dragToSplitscreen(primaryApp.`package`, textEditApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, textEditApp, primaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
}
}
transitions {
- SplitScreenHelper.copyContentFromLeftToRight(
+ SplitScreenHelper.copyContentInSplit(
instrumentation, device, primaryApp, textEditApp)
}
}
@@ -92,12 +78,12 @@
@Presubmit
@Test
fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
- primaryApp, landscapePosLeft = true, portraitPosTop = true)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
- textEditApp, landscapePosLeft = false, portraitPosTop = false)
+ textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 581826e..bf91292 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -26,6 +26,7 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -33,14 +34,11 @@
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-
/**
* Test dismiss split screen by dragging the divider bar.
*
@@ -53,31 +51,23 @@
@Group1
class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- tapl.goHome()
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
}
transitions {
- SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper)
+ if (tapl.isTablet) {
+ SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+ dragToRight = false, dragToBottom = true)
+ } else {
+ SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+ dragToRight = true, dragToBottom = true)
+ }
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withFullScreenApp(secondaryApp)
.waitForAndVerify()
}
@@ -98,17 +88,23 @@
@Presubmit
@Test
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsFullscreenAtEnd() {
testSpec.assertLayers {
this.isVisible(secondaryApp)
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
.isInvisible(secondaryApp)
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
- .isVisible(secondaryApp)
+ .isVisible(secondaryApp, isOptional = true)
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT, isOptional = true)
+ .then()
+ .contains(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
.invoke("secondaryAppBoundsIsFullscreenAtEnd") {
val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation)
it.visibleRegion(secondaryApp).coversExactly(displayBounds)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 5c051e8..20a7423 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -30,8 +30,6 @@
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,30 +50,17 @@
testSpec: FlickerTestParameter
) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
}
transitions {
tapl.goHome()
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
}
@@ -96,12 +81,12 @@
@Presubmit
@Test
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 9ca9ab0..8f7673b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -31,8 +31,6 @@
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,25 +49,12 @@
@Group1
class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- tapl.goHome()
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
}
transitions {
@@ -108,12 +93,12 @@
@Presubmit
@Test
fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = true, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+ secondaryApp, landscapePosLeft = false, portraitPosTop = true)
/** {@inheritDoc} */
@Postsubmit
@@ -189,7 +174,7 @@
supportedRotations = listOf(Surface.ROTATION_0),
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 525e09a..58f7b04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -31,8 +31,6 @@
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,24 +49,12 @@
@Group1
class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
}
}
transitions {
@@ -94,12 +80,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = true, portraitPosTop = true)
+ primaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, landscapePosLeft = false, portraitPosTop = false)
+ secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index c030603..0dd6706 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -30,8 +30,6 @@
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,28 +49,15 @@
class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withWindowSurfaceAppeared(thirdApp)
.waitForAndVerify()
}
@@ -98,12 +83,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index b8565f3..dc8ba0c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -30,8 +30,6 @@
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,28 +48,15 @@
@Group1
class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
}
@@ -97,12 +82,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 20d7f2c..e5924c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -30,8 +30,6 @@
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,28 +48,15 @@
@Group1
class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
- // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
- @Before
- open fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
tapl.goHome()
wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
}
@@ -99,12 +84,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
@Presubmit
@Test
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 023d6bf..397975d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -57,6 +57,49 @@
using uirenderer::PaintUtils;
+class SkiaCanvas::Clip {
+public:
+ Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m)
+ : mType(Type::Rect), mOp(op), mMatrix(m), mRRect(SkRRect::MakeRect(rect)) {}
+ Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m)
+ : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
+ Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
+ : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
+
+ void apply(SkCanvas* canvas) const {
+ canvas->setMatrix(mMatrix);
+ switch (mType) {
+ case Type::Rect:
+ // Don't anti-alias rectangular clips
+ canvas->clipRect(mRRect.rect(), mOp, false);
+ break;
+ case Type::RRect:
+ // Ensure rounded rectangular clips are anti-aliased
+ canvas->clipRRect(mRRect, mOp, true);
+ break;
+ case Type::Path:
+ // Ensure path clips are anti-aliased
+ canvas->clipPath(mPath.value(), mOp, true);
+ break;
+ }
+ }
+
+private:
+ enum class Type {
+ Rect,
+ RRect,
+ Path,
+ };
+
+ Type mType;
+ SkClipOp mOp;
+ SkMatrix mMatrix;
+
+ // These are logically a union (tracked separately due to non-POD path).
+ std::optional<SkPath> mPath;
+ SkRRect mRRect;
+};
+
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
return new SkiaCanvas(bitmap);
}
@@ -200,49 +243,6 @@
}
}
-class SkiaCanvas::Clip {
-public:
- Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m)
- : mType(Type::Rect), mOp(op), mMatrix(m), mRRect(SkRRect::MakeRect(rect)) {}
- Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m)
- : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
- Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
- : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
-
- void apply(SkCanvas* canvas) const {
- canvas->setMatrix(mMatrix);
- switch (mType) {
- case Type::Rect:
- // Don't anti-alias rectangular clips
- canvas->clipRect(mRRect.rect(), mOp, false);
- break;
- case Type::RRect:
- // Ensure rounded rectangular clips are anti-aliased
- canvas->clipRRect(mRRect, mOp, true);
- break;
- case Type::Path:
- // Ensure path clips are anti-aliased
- canvas->clipPath(mPath.value(), mOp, true);
- break;
- }
- }
-
-private:
- enum class Type {
- Rect,
- RRect,
- Path,
- };
-
- Type mType;
- SkClipOp mOp;
- SkMatrix mMatrix;
-
- // These are logically a union (tracked separately due to non-POD path).
- std::optional<SkPath> mPath;
- SkRRect mRRect;
-};
-
const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const {
const SaveRec* rec = mSaveStack ? static_cast<const SaveRec*>(mSaveStack->back()) : nullptr;
int currentSaveCount = mCanvas->getSaveCount();
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 62e42b8..19cd7bd 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,8 +53,12 @@
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
- // TODO: Figure out why this workaround is needed, see b/13913604
- // In the meantime this matches the behavior of GLRenderer, so it is not a regression
+ // In case the surface was destroyed (e.g. a previous trimMemory call) we
+ // need to recreate it here.
+ if (!isSurfaceReady() && mNativeWindow) {
+ setSurface(mNativeWindow.get(), mSwapBehavior);
+ }
+
EGLint error = 0;
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
@@ -166,6 +170,9 @@
}
bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+ mNativeWindow = surface;
+ mSwapBehavior = swapBehavior;
+
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
@@ -182,7 +189,8 @@
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
- mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
+ const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
+ ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
return true;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 186998a..a80c613 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -61,7 +61,8 @@
private:
renderthread::EglManager& mEglManager;
EGLSurface mEglSurface = EGL_NO_SURFACE;
- bool mBufferPreserved = false;
+ sp<ANativeWindow> mNativeWindow;
+ renderthread::SwapBehavior mSwapBehavior = renderthread::SwapBehavior::kSwap_discardBuffer;
};
} /* namespace skiapipeline */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 53a4c60..f10bca6 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -55,7 +55,12 @@
}
MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
- return MakeCurrentResult::AlreadyCurrent;
+ // In case the surface was destroyed (e.g. a previous trimMemory call) we
+ // need to recreate it here.
+ if (!isSurfaceReady() && mNativeWindow) {
+ setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default);
+ }
+ return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
}
Frame SkiaVulkanPipeline::getFrame() {
@@ -132,7 +137,11 @@
void SkiaVulkanPipeline::onStop() {}
-bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+// We can safely ignore the swap behavior because VkManager will always operate
+// in a mode equivalent to EGLManager::SwapBehavior::kBufferAge
+bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) {
+ mNativeWindow = surface;
+
if (mVkSurface) {
vulkanManager().destroySurface(mVkSurface);
mVkSurface = nullptr;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index d2bdae5..f3d3613 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -66,6 +66,7 @@
renderthread::VulkanManager& vulkanManager();
renderthread::VulkanSurface* mVkSurface = nullptr;
+ sp<ANativeWindow> mNativeWindow;
};
} /* namespace skiapipeline */
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 60ae604..7419f8f 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -404,7 +404,9 @@
EXPECT_TRUE(pipeline->isSurfaceReady());
renderThread.destroyRenderingContext();
EXPECT_FALSE(pipeline->isSurfaceReady());
- LOG_ALWAYS_FATAL_IF(pipeline->isSurfaceReady());
+
+ pipeline->makeCurrent();
+ EXPECT_TRUE(pipeline->isSurfaceReady());
}
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
index 3233d4d..0d94734 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
@@ -25,6 +25,7 @@
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
val galleryPageProviders = SettingsPageProviderRepository(
@@ -33,6 +34,7 @@
PreferenceMainPageProvider,
PreferencePageProvider,
SwitchPreferencePageProvider,
+ TwoTargetSwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index bd366c3..92b64fb4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -21,6 +21,10 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavType
import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.common.PageArguments
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.toState
@@ -41,41 +45,72 @@
navArgument(INT_PARAM_NAME) { type = NavType.IntType },
)
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments)
+ if (!pageArgs.isValid()) return emptyList()
+
+ val owner = SettingsPageBuilder.create(name, pageArgs.normalize()).build()
+ val entryList = mutableListOf<SettingsEntry>()
+ val stringParamEntry = SettingsEntryBuilder.create("string_param", owner)
+ stringParamEntry.setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "String param value"
+ override val summary = pageArgs.getStringArg(STRING_PARAM_NAME)!!.toState()
+ })
+ }
+ entryList.add(stringParamEntry.build())
+
+ val intParamEntry = SettingsEntryBuilder.create("int_param", owner)
+ intParamEntry.setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Int param value"
+ override val summary = pageArgs.getIntArg(INT_PARAM_NAME)!!.toString().toState()
+ })
+ }
+ entryList.add(intParamEntry.build())
+
+ val entryFoo = buildInjectEntry(pageArgs.buildNextArg("foo"))
+ val entryBar = buildInjectEntry(pageArgs.buildNextArg("bar"))
+ if (entryFoo != null) entryList.add(entryFoo.setLink(fromPage = owner).build())
+ if (entryBar != null) entryList.add(entryBar.setLink(fromPage = owner).build())
+
+ return entryList
+ }
+
+ private fun buildInjectEntry(arguments: Bundle?): SettingsEntryBuilder? {
+ val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments)
+ if (!pageArgs.isValid()) return null
+
+ val seBuilder = SettingsEntryBuilder.createLinkTo("injection", name, pageArgs.normalize())
+ seBuilder.setIsAllowSearch(false)
+
+ seBuilder.setUiLayoutFn {
+ val summaryArray = listOf(
+ "$STRING_PARAM_NAME=" + pageArgs.getStringArg(STRING_PARAM_NAME)!!,
+ "$INT_PARAM_NAME=" + pageArgs.getIntArg(INT_PARAM_NAME)!!
+ )
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = summaryArray.joinToString(", ").toState()
+ override val onClick = navigator(name + pageArgs.navLink())
+ })
+ }
+
+ return seBuilder
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- ArgumentPage(
- stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"),
- intParam = arguments.getInt(INT_PARAM_NAME),
- )
+ RegularScaffold(title = TITLE) {
+ for (entry in buildEntry(arguments)) {
+ entry.uiLayout()
+ }
+ }
}
@Composable
fun EntryItem(stringParam: String, intParam: Int) {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val summary =
- "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState()
- override val onClick = navigator("$name/$stringParam/$intParam")
- })
- }
-}
-
-@Composable
-fun ArgumentPage(stringParam: String, intParam: Int) {
- RegularScaffold(title = TITLE) {
- Preference(object : PreferenceModel {
- override val title = "String param value"
- override val summary = stringParam.toState()
- })
-
- Preference(object : PreferenceModel {
- override val title = "Int param value"
- override val summary = intParam.toString().toState()
- })
-
- ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1)
-
- ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1)
+ buildInjectEntry(buildArgument(stringParam, intParam))?.build()?.uiLayout?.let { it() }
}
}
@@ -83,6 +118,26 @@
@Composable
private fun ArgumentPagePreview() {
SettingsTheme {
- ArgumentPage(stringParam = "foo", intParam = 0)
+ ArgumentPageProvider.Page(buildArgument(stringParam = "foo", intParam = 0))
}
}
+
+private fun PageArguments.isValid(): Boolean {
+ val stringParam = getStringArg(STRING_PARAM_NAME)
+ return (stringParam != null && listOf("foo", "bar").contains(stringParam))
+}
+
+private fun PageArguments.buildNextArg(stringParam: String): Bundle {
+ val intParam = getIntArg(INT_PARAM_NAME)
+ return if (intParam == null)
+ buildArgument(stringParam)
+ else
+ buildArgument(stringParam, intParam + 1)
+}
+
+private fun buildArgument(stringParam: String, intParam: Int? = null): Bundle {
+ val args = Bundle()
+ args.putString(STRING_PARAM_NAME, stringParam)
+ if (intParam != null) args.putInt(INT_PARAM_NAME, intParam)
+ return args
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index d3ca1d3..e71c30c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -48,5 +48,6 @@
RegularScaffold(title = TITLE) {
PreferencePageProvider.EntryItem()
SwitchPreferencePageProvider.EntryItem()
+ TwoTargetSwitchPreferencePageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
new file mode 100644
index 0000000..894692b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import kotlinx.coroutines.delay
+
+private const val TITLE = "Sample TwoTargetSwitchPreference"
+
+object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
+ override val name = "TwoTargetSwitchPreference"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ TwoTargetSwitchPreferencePage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun TwoTargetSwitchPreferencePage() {
+ RegularScaffold(title = TITLE) {
+ SampleTwoTargetSwitchPreference()
+ SampleTwoTargetSwitchPreferenceWithSummary()
+ SampleTwoTargetSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableTwoTargetSwitchPreference()
+ }
+}
+
+@Composable
+private fun SampleTwoTargetSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ TwoTargetSwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "TwoTargetSwitchPreference"
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ }) {}
+}
+
+@Composable
+private fun SampleTwoTargetSwitchPreferenceWithSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ TwoTargetSwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "TwoTargetSwitchPreference"
+ override val summary = stateOf("With summary")
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ }) {}
+}
+
+@Composable
+private fun SampleTwoTargetSwitchPreferenceWithAsyncSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ val summary = produceState(initialValue = " ") {
+ delay(1000L)
+ value = "Async summary"
+ }
+ TwoTargetSwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "TwoTargetSwitchPreference"
+ override val summary = summary
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ }) {}
+}
+
+@Composable
+private fun SampleNotChangeableTwoTargetSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ TwoTargetSwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "TwoTargetSwitchPreference"
+ override val summary = stateOf("Not changeable")
+ override val changeable = stateOf(false)
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ }) {}
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun TwoTargetSwitchPreferencePagePreview() {
+ SettingsTheme {
+ TwoTargetSwitchPreferencePage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt
new file mode 100644
index 0000000..c0f585f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.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.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavType
+
+class PageArguments(
+ private val navArgsDef: List<NamedNavArgument>,
+ private val rawArgs: Bundle?
+) {
+ fun normalize(): Bundle {
+ if (rawArgs == null) return Bundle.EMPTY
+ val normArgs = Bundle()
+ for (navArg in navArgsDef) {
+ when (navArg.argument.type) {
+ NavType.StringType -> {
+ val value = rawArgs.getString(navArg.name)
+ if (value != null) normArgs.putString(navArg.name, value)
+ }
+ NavType.IntType -> {
+ if (rawArgs.containsKey(navArg.name))
+ normArgs.putInt(navArg.name, rawArgs.getInt(navArg.name))
+ }
+ }
+ }
+ return normArgs
+ }
+
+ fun navLink(): String{
+ if (rawArgs == null) return ""
+ val argsArray = mutableListOf<String>()
+ for (navArg in navArgsDef) {
+ when (navArg.argument.type) {
+ NavType.StringType -> {
+ argsArray.add(rawArgs.getString(navArg.name, ""))
+ }
+ NavType.IntType -> {
+ argsArray.add(rawArgs.getInt(navArg.name).toString())
+ }
+ }
+ }
+ return argsArray.joinToString("") {arg -> "/$arg" }
+ }
+
+ fun getStringArg(key: String): String? {
+ if (rawArgs != null && rawArgs.containsKey(key)) {
+ return rawArgs.getString(key)
+ }
+ return null
+ }
+
+ fun getIntArg(key: String): Int? {
+ if (rawArgs != null && rawArgs.containsKey(key)) {
+ return rawArgs.getInt(key)
+ }
+ return null
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
new file mode 100644
index 0000000..6fcd555
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.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.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+
+/**
+ * Defines data of one Settings entry for Settings search.
+ */
+data class SearchData(val keyword: String = "")
+
+/**
+ * Defines data of one Settings entry for UI rendering.
+ */
+data class UiData(val title: String = "")
+
+/**
+ * Defines data to identify a Settings page.
+ */
+data class SettingsPage(val name: String = "", val args: Bundle = Bundle.EMPTY)
+
+/**
+ * Defines data of a Settings entry.
+ */
+data class SettingsEntry(
+ val name: String,
+ val owner: SettingsPage,
+
+ // Generates the unique id of this entry
+ val uniqueId: String,
+
+ // Defines linking of Settings entries
+ val fromPage: SettingsPage? = null,
+ val toPage: SettingsPage? = null,
+
+ /**
+ * ========================================
+ * Defines entry attributes here.
+ * ========================================
+ */
+ val isAllowSearch: Boolean,
+
+ /**
+ * ========================================
+ * Defines entry APIs to get data here.
+ * ========================================
+ */
+
+ /**
+ * API to get Search related data for this entry.
+ * Returns null if this entry is not available for the search at the moment.
+ */
+ val searchData: () -> SearchData? = { null },
+
+ /**
+ * API to get UI related data for this entry.
+ * Returns null if the entry is not render-able.
+ */
+ val uiData: () -> UiData? = { null },
+
+ /**
+ * API to Render UI of this entry directly. For now, we use it in the internal injection, to
+ * support the case that the injection page owner wants to maintain both data and UI of the
+ * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
+ * use each entries' UI rendering function in the page instead.
+ */
+ val uiLayout: (@Composable () -> Unit) = {},
+)
+
+/**
+ * The helper to build a Settings Page instance.
+ */
+class SettingsPageBuilder(private val name: String) {
+ private val arguments = Bundle()
+
+ fun pushArgs(args: Bundle?): SettingsPageBuilder {
+ if (args != null) arguments.putAll(args)
+ return this
+ }
+
+ fun pushArg(argName: String, argValue: String?): SettingsPageBuilder {
+ if (argValue != null) this.arguments.putString(argName, argValue)
+ return this
+ }
+
+ fun pushArg(argName: String, argValue: Int?): SettingsPageBuilder {
+ if (argValue != null) this.arguments.putInt(argName, argValue)
+ return this
+ }
+
+ fun build(): SettingsPage {
+ return SettingsPage(name, arguments)
+ }
+
+ companion object {
+ fun create(name: String, args: Bundle? = null): SettingsPageBuilder {
+ return SettingsPageBuilder(name).apply {
+ pushArgs(args)
+ }
+ }
+ }
+}
+
+/**
+ * The helper to build a Settings Entry instance.
+ */
+class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
+ private var uniqueId: String? = null
+ private var fromPage: SettingsPage? = null
+ private var toPage: SettingsPage? = null
+ private var isAllowSearch: Boolean? = null
+
+ private var searchDataFn: () -> SearchData? = { null }
+ private var uiLayoutFn: (@Composable () -> Unit) = {}
+
+ fun build(): SettingsEntry {
+ return SettingsEntry(
+ name = name,
+ owner = owner,
+ uniqueId = computeUniqueId(),
+
+ // linking data
+ fromPage = fromPage,
+ toPage = toPage,
+
+ // attributes
+ isAllowSearch = computeSearchable(),
+
+ // functions
+ searchData = searchDataFn,
+ uiLayout = uiLayoutFn,
+ )
+ }
+
+ fun setLink(
+ fromPage: SettingsPage? = null,
+ toPage: SettingsPage? = null
+ ): SettingsEntryBuilder {
+ if (fromPage != null) this.fromPage = fromPage
+ if (toPage != null) this.toPage = toPage
+ return this
+ }
+
+ fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
+ this.isAllowSearch = isAllowSearch
+ return this
+ }
+
+ fun setSearchDataFn(fn: () -> SearchData?): SettingsEntryBuilder {
+ this.searchDataFn = fn
+ return this
+ }
+
+ fun setUiLayoutFn(fn: @Composable () -> Unit): SettingsEntryBuilder {
+ this.uiLayoutFn = fn
+ return this
+ }
+
+ private fun computeUniqueId(): String = uniqueId ?: (name + owner.toString())
+
+ private fun computeSearchable(): Boolean = isAllowSearch ?: false
+
+ companion object {
+ fun create(
+ entryName: String,
+ ownerPageName: String,
+ ownerPageArgs: Bundle? = null
+ ): SettingsEntryBuilder {
+ val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs)
+ return SettingsEntryBuilder(entryName, owner.build())
+ }
+
+ fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
+ return SettingsEntryBuilder(entryName, owner)
+ }
+
+ fun createLinkTo(
+ entryName: String,
+ ownerPageName: String,
+ ownerPageArgs: Bundle? = null
+ ): SettingsEntryBuilder {
+ val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs)
+ return SettingsEntryBuilder(entryName, owner.build()).setLink(toPage = owner.build())
+ }
+
+ fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
+ return SettingsEntryBuilder(entryName, owner).setLink(toPage = owner)
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 2ab0cb9..a57c66e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -36,5 +36,5 @@
@Composable
fun Page(arguments: Bundle?)
- // fun buildEntry( arguments: Bundle?) : List<entry>
+ fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 9654368..c1a3c20 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -32,6 +32,7 @@
bottom = itemPaddingVertical,
)
val itemPaddingAround = 8.dp
+ val itemDividerHeight = 32.dp
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 04ee3c3..11af6ce 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -19,4 +19,5 @@
object SettingsOpacity {
const val Full = 1f
const val Disabled = 0.38f
+ const val Divider = 0.2f
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
new file mode 100644
index 0000000..507421b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity
+
+@Composable
+internal fun TwoTargetPreference(
+ title: String,
+ summary: State<String>,
+ onClick: () -> Unit,
+ icon: @Composable (() -> Unit)? = null,
+ widget: @Composable () -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = SettingsDimension.itemPaddingEnd),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Box(modifier = Modifier.weight(1f)) {
+ Preference(remember {
+ object : PreferenceModel {
+ override val title = title
+ override val summary = summary
+ override val icon = icon
+ override val onClick = onClick
+ }
+ })
+ }
+ PreferenceDivider()
+ widget()
+ }
+}
+
+@Composable
+private fun PreferenceDivider() {
+ Box(
+ Modifier
+ .padding(horizontal = SettingsDimension.itemPaddingEnd)
+ .size(width = 1.dp, height = SettingsDimension.itemDividerHeight)
+ .background(color = MaterialTheme.colorScheme.onSurface.copy(SettingsOpacity.Disabled))
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
new file mode 100644
index 0000000..f1541b7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.ui.SettingsSwitch
+
+@Composable
+fun TwoTargetSwitchPreference(
+ model: SwitchPreferenceModel,
+ icon: @Composable (() -> Unit)? = null,
+ onClick: () -> Unit,
+) {
+ TwoTargetPreference(
+ title = model.title,
+ summary = model.summary,
+ onClick = onClick,
+ icon = icon,
+ ) {
+ SettingsSwitch(
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 8b530b0..1af4ce7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -26,6 +26,7 @@
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -47,6 +48,8 @@
Text(
text = title,
modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
)
},
navigationIcon = { NavigateBack() },
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
new file mode 100644
index 0000000..c49e92d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TwoTargetSwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ val checked = mutableStateOf(false)
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(checked = checked, changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
+ }
+
+ @Test
+ fun toggleable_initialStateIsCorrect() {
+ val checked = mutableStateOf(false)
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(checked = checked, changeable = true)
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun toggleable_changeable_withEffect() {
+ val checked = mutableStateOf(false)
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(checked = checked, changeable = true)
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun toggleable_notChangeable_noEffect() {
+ val checked = mutableStateOf(false)
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(checked = checked, changeable = false)
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun clickable_canBeClick() {
+ val checked = mutableStateOf(false)
+ var clicked = false
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(checked = checked, changeable = false) {
+ clicked = true
+ }
+ }
+
+ composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
+ assertThat(clicked).isTrue()
+ }
+}
+
+@Composable
+private fun TestTwoTargetSwitchPreference(
+ checked: MutableState<Boolean>,
+ changeable: Boolean,
+ onClick: () -> Unit = {},
+) {
+ TwoTargetSwitchPreference(
+ model = remember {
+ object : SwitchPreferenceModel {
+ override val title = "TwoTargetSwitchPreference"
+ override val checked = checked
+ override val changeable = stateOf(changeable)
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ },
+ onClick = onClick,
+ )
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index ba7a9bc..1dfa96f 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -31,7 +31,6 @@
import android.provider.settings.backup.SystemSettings;
import androidx.test.filters.SmallTest;
-import androidx.test.filters.Suppress;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
@@ -709,7 +708,6 @@
Settings.Secure.DOCKED_CLOCK_FACE,
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
- Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P
Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
@@ -837,15 +835,66 @@
}
@Test
- @Suppress //("b/148236308")
public void secureSettingsBackedUpOrDenied() {
+ // List of settings that were not added to either SETTINGS_TO_BACKUP or
+ // BACKUP_DENY_LIST_SECURE_SETTINGS while this test was suppressed in
+ // the last two years. Settings in this list are temporarily allowed to
+ // not be explicitly listed as backed up or denied so we can re-enable
+ // this test.
+ //
+ // DO NOT ADD NEW SETTINGS TO THIS LIST!
+ Set<String> settingsNotBackedUpOrDeniedTemporaryAllowList =
+ newHashSet(
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING,
+ Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT,
+ Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY,
+ Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED,
+ Settings.Secure.AUTO_REVOKE_DISABLED,
+ Settings.Secure.BIOMETRIC_APP_ENABLED,
+ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED,
+ Settings.Secure.BLUETOOTH_ADDR_VALID,
+ Settings.Secure.BLUETOOTH_ADDRESS,
+ Settings.Secure.BLUETOOTH_NAME,
+ Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS,
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+ Settings.Secure.COMMUNAL_MODE_ENABLED,
+ Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS,
+ Settings.Secure.DEFAULT_VOICE_INPUT_METHOD,
+ Settings.Secure.DOCK_SETUP_STATE,
+ Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE,
+ Settings.Secure.GAME_DASHBOARD_ALWAYS_ON,
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
+ Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING,
+ Settings.Secure.LOCATION_COARSE_ACCURACY_M,
+ Settings.Secure.LOCATION_SHOW_SYSTEM_OPS,
+ Settings.Secure.NAS_SETTINGS_UPDATED,
+ Settings.Secure.NAV_BAR_FORCE_VISIBLE,
+ Settings.Secure.NAV_BAR_KIDS_MODE,
+ Settings.Secure.NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT,
+ Settings.Secure.NEARBY_SHARING_SLICE_URI,
+ Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
+ Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT,
+ Settings.Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL,
+ Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
+ Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
+ Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED,
+ Settings.Secure.TIMEOUT_TO_USER_ZERO,
+ Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED,
+ Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF,
+ Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON);
+
HashSet<String> keys = new HashSet<String>();
Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP);
Collections.addAll(keys, DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
- checkSettingsBackedUpOrDenied(
- getCandidateSettings(Settings.Secure.class),
- keys,
- BACKUP_DENY_LIST_SECURE_SETTINGS);
+
+ Set<String> allSettings = getCandidateSettings(Settings.Secure.class);
+ allSettings.removeAll(settingsNotBackedUpOrDeniedTemporaryAllowList);
+
+ checkSettingsBackedUpOrDenied(allSettings, keys, BACKUP_DENY_LIST_SECURE_SETTINGS);
}
private static void checkSettingsBackedUpOrDenied(
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8fa2204..d90156d 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -217,7 +217,7 @@
the force lock button. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_user_request">Device was locked manually</string>
- <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
+ <!-- Face hint message when face was not recognized. [CHAR LIMIT=20] -->
<string name="kg_face_not_recognized">Not recognized</string>
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=53] -->
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index 921f788..73050c2 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -23,4 +23,4 @@
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/sfps_pulse"
- android:contentDescription="@string/accessibility_fingerprint_label"/>
+ android:importantForAccessibility="no"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c7b2ff3..d5a744b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -389,6 +389,10 @@
<string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
<string name="keyguard_face_failed_use_fp">@string/fingerprint_dialog_use_fingerprint_instead</string>
+ <!-- Message shown to inform the user a face cannot be recognized. [CHAR LIMIT=25] -->
+ <string name="keyguard_face_failed">Can\u2019t recognize face</string>
+ <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
+ <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 3517d22..d0baf3d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -89,7 +89,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter;
-import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord;
+import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.settings.GlobalSettings;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 86837366..436b756 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -657,30 +657,50 @@
@Override
public void onAuthenticationSucceeded(@Modality int modality) {
- mBiometricView.onAuthenticationSucceeded(modality);
+ if (mBiometricView != null) {
+ mBiometricView.onAuthenticationSucceeded(modality);
+ } else {
+ Log.e(TAG, "onAuthenticationSucceeded(): mBiometricView is null");
+ }
}
@Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
- mFailedModalities.add(modality);
- mBiometricView.onAuthenticationFailed(modality, failureReason);
+ if (mBiometricView != null) {
+ mFailedModalities.add(modality);
+ mBiometricView.onAuthenticationFailed(modality, failureReason);
+ } else {
+ Log.e(TAG, "onAuthenticationFailed(): mBiometricView is null");
+ }
}
@Override
public void onHelp(@Modality int modality, String help) {
- mBiometricView.onHelp(modality, help);
+ if (mBiometricView != null) {
+ mBiometricView.onHelp(modality, help);
+ } else {
+ Log.e(TAG, "onHelp(): mBiometricView is null");
+ }
}
@Override
public void onError(@Modality int modality, String error) {
- mBiometricView.onError(modality, error);
+ if (mBiometricView != null) {
+ mBiometricView.onError(modality, error);
+ } else {
+ Log.e(TAG, "onError(): mBiometricView is null");
+ }
}
@Override
public void onPointerDown() {
- if (mBiometricView.onPointerDown(mFailedModalities)) {
- Log.d(TAG, "retrying failed modalities (pointer down)");
- mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ if (mBiometricView != null) {
+ if (mBiometricView.onPointerDown(mFailedModalities)) {
+ Log.d(TAG, "retrying failed modalities (pointer down)");
+ mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ }
+ } else {
+ Log.e(TAG, "onPointerDown(): mBiometricView is null");
}
}
@@ -715,7 +735,11 @@
@Override
public void animateToCredentialUI() {
- mBiometricView.startTransitionToCredentialUI();
+ if (mBiometricView != null) {
+ mBiometricView.startTransitionToCredentialUI();
+ } else {
+ Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
+ }
}
void animateAway(@AuthDialogCallback.DismissedReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 918c6be..8f85905 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,7 +27,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
@@ -61,8 +60,6 @@
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private boolean mGridContentVisible = true;
-
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@Override
@@ -204,16 +201,6 @@
});
}
- /** */
- public void setGridContentVisibility(boolean visible) {
- int newVis = visible ? View.VISIBLE : View.INVISIBLE;
- setVisibility(newVis);
- if (mGridContentVisible != visible) {
- mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
- }
- mGridContentVisible = visible;
- }
-
public boolean isLayoutRtl() {
return mView.isLayoutRtl();
}
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 ce6aaae..0ec4eef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -43,6 +43,7 @@
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.data.source.UserRecord;
import javax.inject.Inject;
@@ -95,7 +96,7 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- UserSwitcherController.UserRecord item = getItem(position);
+ UserRecord item = getItem(position);
return createUserDetailItemView(convertView, parent, item);
}
@@ -113,7 +114,7 @@
}
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
- UserSwitcherController.UserRecord item) {
+ UserRecord item) {
UserDetailItemView v = UserDetailItemView.convertOrInflate(
parent.getContext(), convertView, parent);
if (!item.isCurrent || item.isGuest) {
@@ -134,7 +135,7 @@
v.bind(name, drawable, item.info.id);
}
v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(item.isDisabledByAdmin);
+ v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
v.setEnabled(item.isSwitchToEnabled);
v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
@@ -146,7 +147,7 @@
}
private static Drawable getDrawable(Context context,
- UserSwitcherController.UserRecord item) {
+ UserRecord item) {
Drawable icon = getIconDrawable(context, item);
int iconColorRes;
if (item.isCurrent) {
@@ -171,22 +172,24 @@
}
Trace.beginSection("UserDetailView.Adapter#onClick");
- UserSwitcherController.UserRecord tag =
- (UserSwitcherController.UserRecord) view.getTag();
- if (tag.isDisabledByAdmin) {
+ UserRecord userRecord =
+ (UserRecord) view.getTag();
+ if (mController.isDisabledByAdmin(userRecord)) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, tag.enforcedAdmin);
+ mContext, mController.getEnforcedAdmin(userRecord));
mController.startActivity(intent);
- } else if (tag.isSwitchToEnabled) {
+ } else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
- if (!tag.isAddUser && !tag.isRestricted && !tag.isDisabledByAdmin) {
+ if (!userRecord.isAddUser
+ && !userRecord.isRestricted
+ && !mController.isDisabledByAdmin(userRecord)) {
if (mCurrentUserView != null) {
mCurrentUserView.setActivated(false);
}
view.setActivated(true);
}
- onUserListItemClicked(tag, mDialogShower);
+ onUserListItemClicked(userRecord, mDialogShower);
}
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9a8395c..70c0cab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,8 +17,6 @@
package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
@@ -28,12 +26,8 @@
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
-import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
-import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.NotificationPanelView.DEBUG;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -46,8 +40,6 @@
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
-import static java.lang.Float.isNaN;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -55,8 +47,6 @@
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -81,13 +71,11 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
-import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.AccessibilityDelegate;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
@@ -96,7 +84,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -191,7 +178,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -247,25 +233,12 @@
import javax.inject.Provider;
@CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController {
- public static final String TAG = "PanelView";
- public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_SPEED_UP_FACTOR = 0.6f;
- public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
- private static final int NO_FIXED_DURATION = -1;
- private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
- private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
-
- /**
- * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
- * when flinging. A low value will make it that most flings will reach the maximum overshoot.
- */
- private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+public final class NotificationPanelViewController extends PanelViewController {
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
+
/**
* The parallax amount of the quick settings translation when dragging down the panel
*/
@@ -290,16 +263,6 @@
- CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
- private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
- private final Resources mResources;
- private final KeyguardStateController mKeyguardStateController;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final AmbientState mAmbientState;
- private final LockscreenGestureLogger mLockscreenGestureLogger;
- private final SystemClock mSystemClock;
-
- private final ShadeLogger mShadeLog;
-
private final DozeParameters mDozeParameters;
private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
private final Runnable mCollapseExpandAction = new CollapseExpandAction();
@@ -373,28 +336,6 @@
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
private final PanelEventsEmitter mPanelEventsEmitter;
- private final boolean mVibrateOnOpening;
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
- private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
- private final LatencyTracker mLatencyTracker;
- private final DozeLog mDozeLog;
- /** Whether or not the PanelView can be expanded or collapsed with a drag. */
- private final boolean mNotificationsDragEnabled;
- private final Interpolator mBounceInterpolator;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final PanelExpansionStateManager mPanelExpansionStateManager;
- private long mDownTime;
- private boolean mTouchSlopExceededBeforeDown;
- private boolean mIsLaunchAnimationRunning;
- private float mOverExpansion;
- private CentralSurfaces mCentralSurfaces;
- private HeadsUpManagerPhone mHeadsUpManager;
- private float mExpandedHeight = 0;
- private boolean mTracking;
- private boolean mHintAnimationRunning;
- private KeyguardBottomAreaView mKeyguardBottomArea;
- private boolean mExpanding;
private boolean mSplitShadeEnabled;
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
@@ -758,51 +699,6 @@
private final CameraGestureHelper mCameraGestureHelper;
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
- private float mMinExpandHeight;
- private boolean mPanelUpdateWhenAnimatorEnds;
- private int mFixedDuration = NO_FIXED_DURATION;
- /** The overshoot amount when the panel flings open */
- private float mPanelFlingOvershootAmount;
- /** The amount of pixels that we have overexpanded the last time with a gesture */
- private float mLastGesturedOverExpansion = -1;
- /** Is the current animator the spring back animation? */
- private boolean mIsSpringBackAnimation;
- private boolean mInSplitShade;
- private float mHintDistance;
- private float mInitialOffsetOnTouch;
- private boolean mCollapsedAndHeadsUpOnDown;
- private float mExpandedFraction = 0;
- private float mExpansionDragDownAmountPx = 0;
- private boolean mPanelClosedOnDown;
- private boolean mHasLayoutedSinceDown;
- private float mUpdateFlingVelocity;
- private boolean mUpdateFlingOnLayout;
- private boolean mClosing;
- private boolean mTouchSlopExceeded;
- private int mTrackingPointer;
- private int mTouchSlop;
- private float mSlopMultiplier;
- private boolean mTouchAboveFalsingThreshold;
- private boolean mTouchStartedInEmptyArea;
- private boolean mMotionAborted;
- private boolean mUpwardsWhenThresholdReached;
- private boolean mAnimatingOnDown;
- private boolean mHandlingPointerUp;
- private ValueAnimator mHeightAnimator;
- /** Whether instant expand request is currently pending and we are just waiting for layout. */
- private boolean mInstantExpanding;
- private boolean mAnimateAfterExpanding;
- private boolean mIsFlinging;
- private String mViewName;
- private float mInitialExpandY;
- private float mInitialExpandX;
- private boolean mTouchDisabled;
- private boolean mInitialTouchFromKeyguard;
- /** Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */
- private float mNextCollapseSpeedUpFactor = 1.0f;
- private boolean mGestureWaitForTouchSlop;
- private boolean mIgnoreXTouchSlop;
- private boolean mExpandLatencyTracking;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -826,7 +722,7 @@
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
ConfigurationController configurationController,
- Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider,
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
@@ -876,68 +772,25 @@
CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
- keyguardStateController.addCallback(new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- requestPanelHeightUpdate();
- }
- });
- mAmbientState = ambientState;
+ super(view,
+ falsingManager,
+ dozeLog,
+ keyguardStateController,
+ (SysuiStatusBarStateController) statusBarStateController,
+ notificationShadeWindowController,
+ vibratorHelper,
+ statusBarKeyguardViewManager,
+ latencyTracker,
+ flingAnimationUtilsBuilder.get(),
+ statusBarTouchableRegionManager,
+ lockscreenGestureLogger,
+ panelExpansionStateManager,
+ ambientState,
+ interactionJankMonitor,
+ shadeLogger,
+ systemClock);
mView = view;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mLockscreenGestureLogger = lockscreenGestureLogger;
- mPanelExpansionStateManager = panelExpansionStateManager;
- mShadeLog = shadeLogger;
- TouchHandler touchHandler = createTouchHandler();
- mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mViewName = mResources.getResourceName(mView.getId());
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
-
- mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(touchHandler);
- mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
-
- mResources = mView.getResources();
- mKeyguardStateController = keyguardStateController;
- mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
- mNotificationShadeWindowController = notificationShadeWindowController;
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder =
- flingAnimationUtilsBuilderProvider.get();
- mFlingAnimationUtils = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(0.5f)
- .setSpeedUpFactor(0.6f)
- .setX2(0.6f)
- .setY2(0.84f)
- .build();
- mLatencyTracker = latencyTracker;
- mBounceInterpolator = new BounceInterpolator();
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
- mNotificationsDragEnabled = mResources.getBoolean(
- R.bool.config_enableNotificationShadeDrag);
mVibratorHelper = vibratorHelper;
- mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
mPrivacyDotViewController = privacyDotViewController;
mQuickAccessWalletController = quickAccessWalletController;
@@ -945,8 +798,9 @@
mControlsComponent = controlsComponent;
mMetricsLogger = metricsLogger;
mConfigurationController = configurationController;
- mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilderProvider;
+ mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -968,6 +822,7 @@
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
+ mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -983,6 +838,7 @@
mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
+ mInteractionJankMonitor = interactionJankMonitor;
mSysUiState = sysUiState;
mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -1185,14 +1041,9 @@
controller.setup(mNotificationContainerParent));
}
- @VisibleForTesting
- void loadDimens() {
- final ViewConfiguration configuration = ViewConfiguration.get(this.mView.getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
- mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
+ @Override
+ protected void loadDimens() {
+ super.loadDimens();
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1876,6 +1727,7 @@
}
}
+ @Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canPanelBeCollapsed()) {
return;
@@ -1885,20 +1737,7 @@
setQsExpandImmediate(true);
setShowShelfOnly(true);
}
- if (DEBUG) this.logf("collapse: " + this);
- if (canPanelBeCollapsed()) {
- cancelHeightAnimator();
- notifyExpandingStarted();
-
- // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- setIsClosing(true);
- if (delayed) {
- mNextCollapseSpeedUpFactor = speedUpFactor;
- this.mView.postDelayed(mFlingCollapseRunnable, 120);
- } else {
- fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
- }
- }
+ super.collapse(delayed, speedUpFactor);
}
private void setQsExpandImmediate(boolean expandImmediate) {
@@ -1916,15 +1755,10 @@
setQsExpansion(mQsMinExpansionHeight);
}
+ @Override
@VisibleForTesting
- void cancelHeightAnimator() {
- if (mHeightAnimator != null) {
- if (mHeightAnimator.isRunning()) {
- mPanelUpdateWhenAnimatorEnds = false;
- }
- mHeightAnimator.cancel();
- }
- endClosing();
+ protected void cancelHeightAnimator() {
+ super.cancelHeightAnimator();
}
public void cancelAnimation() {
@@ -1992,123 +1826,28 @@
}
}
+ @Override
public void fling(float vel, boolean expand) {
GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
if (gr != null) {
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ super.fling(vel, expand);
}
- @VisibleForTesting
- void flingToHeight(float vel, boolean expand, float target,
+ @Override
+ protected void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
mHeadsUpTouchHelper.notifyFling(!expand);
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
mNotificationStackScrollLayoutController.setPanelFlinging(true);
- if (target == mExpandedHeight && mOverExpansion == 0.0f) {
- // We're at the target and didn't fling and there's no overshoot
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsFlinging = true;
- // we want to perform an overshoot animation when flinging open
- final boolean addOverscroll =
- expand
- && !mInSplitShade // Split shade has its own overscroll logic
- && mStatusBarStateController.getState() != KEYGUARD
- && mOverExpansion == 0.0f
- && vel >= 0;
- final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
- float overshootAmount = 0.0f;
- if (addOverscroll) {
- // Let's overshoot depending on the amount of velocity
- overshootAmount = MathUtils.lerp(
- 0.2f,
- 1.0f,
- MathUtils.saturate(vel
- / (this.mFlingAnimationUtils.getHighVelocityPxPerSecond()
- * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
- overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
- }
- ValueAnimator animator = createHeightAnimator(target, overshootAmount);
- if (expand) {
- if (expandBecauseOfFalsing && vel < 0) {
- vel = 0;
- }
- this.mFlingAnimationUtils.apply(animator, mExpandedHeight,
- target + overshootAmount * mPanelFlingOvershootAmount, vel,
- this.mView.getHeight());
- if (vel == 0) {
- animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
- }
- } else {
- if (shouldUseDismissingAnimation()) {
- if (vel == 0) {
- animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- long duration = (long) (200 + mExpandedHeight / this.mView.getHeight() * 100);
- animator.setDuration(duration);
- } else {
- mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
- this.mView.getHeight());
- }
- } else {
- mFlingAnimationUtilsClosing.apply(
- animator, mExpandedHeight, target, vel, this.mView.getHeight());
- }
-
- // Make it shorter if we run a canned animation
- if (vel == 0) {
- animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
- }
- if (mFixedDuration != NO_FIXED_DURATION) {
- animator.setDuration(mFixedDuration);
- }
- }
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationStart(Animator animation) {
- if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (shouldSpringBack && !mCancelled) {
- // After the shade is flinged open to an overscrolled state, spring back
- // the shade by reducing section padding to 0.
- springBack();
- } else {
- onFlingEnd(mCancelled);
- }
- }
- });
- setAnimator(animator);
- animator.start();
+ super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
- private void onFlingEnd(boolean cancelled) {
- mIsFlinging = false;
- // No overshoot when the animation ends
- setOverExpansionInternal(0, false /* isFromGesture */);
- setAnimator(null);
- mKeyguardStateController.notifyPanelFlingEnd();
- if (!cancelled) {
- endJankMonitoring();
- notifyExpandingFinished();
- } else {
- cancelJankMonitoring();
- }
- updatePanelExpansionAndVisibility();
+ @Override
+ protected void onFlingEnd(boolean cancelled) {
+ super.onFlingEnd(cancelled);
mNotificationStackScrollLayoutController.setPanelFlinging(false);
}
@@ -2203,7 +1942,8 @@
return mQsTracking;
}
- private boolean isInContentBounds(float x, float y) {
+ @Override
+ protected boolean isInContentBounds(float x, float y) {
float stackScrollerX = mNotificationStackScrollLayoutController.getX();
return !mNotificationStackScrollLayoutController
.isBelowLastNotification(x - stackScrollerX, y)
@@ -2336,8 +2076,9 @@
- mQsMinExpansionHeight));
}
- private boolean shouldExpandWhenNotFlinging() {
- if (getExpandedFraction() > 0.5f) {
+ @Override
+ protected boolean shouldExpandWhenNotFlinging() {
+ if (super.shouldExpandWhenNotFlinging()) {
return true;
}
if (mAllowExpandForSmallExpansion) {
@@ -2349,7 +2090,8 @@
return false;
}
- private float getOpeningHeight() {
+ @Override
+ protected float getOpeningHeight() {
return mNotificationStackScrollLayoutController.getOpeningHeight();
}
@@ -2499,20 +2241,9 @@
}
}
- private boolean flingExpands(float vel, float vectorVel, float x, float y) {
- boolean expands = true;
- if (!this.mFalsingManager.isUnlockingDisabled()) {
- @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0
- ? QUICK_SETTINGS : (
- mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
- if (!isFalseTouch(x, y, interactionType)) {
- if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- expands = shouldExpandWhenNotFlinging();
- } else {
- expands = vel > 0;
- }
- }
- }
+ @Override
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ boolean expands = super.flingExpands(vel, vectorVel, x, y);
// If we are already running a QS expansion, make sure that we keep the panel open.
if (mQsExpansionAnimator != null) {
@@ -2521,7 +2252,8 @@
return expands;
}
- private boolean shouldGestureWaitForTouchSlop() {
+ @Override
+ protected boolean shouldGestureWaitForTouchSlop() {
if (mExpectingSynthesizedDown) {
mExpectingSynthesizedDown = false;
return false;
@@ -2599,7 +2331,7 @@
}
}
- private int getFalsingThreshold() {
+ protected int getFalsingThreshold() {
float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
return (int) (mQsFalsingThreshold * factor);
}
@@ -3333,8 +3065,8 @@
}
}
- @VisibleForTesting
- boolean canCollapsePanelOnTouch() {
+ @Override
+ protected boolean canCollapsePanelOnTouch() {
if (!isInSettings() && mBarState == KEYGUARD) {
return true;
}
@@ -3346,6 +3078,7 @@
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
+ @Override
public int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
@@ -3388,7 +3121,8 @@
return mIsExpanding;
}
- private void onHeightUpdated(float expandedHeight) {
+ @Override
+ protected void onHeightUpdated(float expandedHeight) {
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
@@ -3570,7 +3304,9 @@
mLockIconViewController.setAlpha(alpha);
}
- private void onExpandingStarted() {
+ @Override
+ protected void onExpandingStarted() {
+ super.onExpandingStarted();
mNotificationStackScrollLayoutController.onExpansionStarted();
mIsExpanding = true;
mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
@@ -3586,7 +3322,8 @@
mQs.setHeaderListening(true);
}
- private void onExpandingFinished() {
+ @Override
+ protected void onExpandingFinished() {
mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
@@ -3634,54 +3371,18 @@
mQs.setListening(listening);
}
+ @Override
public void expand(boolean animate) {
- if (isFullyCollapsed() || isCollapsing()) {
- mInstantExpanding = true;
- mAnimateAfterExpanding = animate;
- mUpdateFlingOnLayout = false;
- abortAnimations();
- if (mTracking) {
- onTrackingStopped(true /* expands */); // The panel is expanded after this call.
- }
- if (mExpanding) {
- notifyExpandingFinished();
- }
- updatePanelExpansionAndVisibility();// Wait for window manager to pickup the change,
- // so we know the maximum height of the panel then.
- this.mView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (!mInstantExpanding) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(
- this);
- return;
- }
- if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(
- this);
- if (mAnimateAfterExpanding) {
- notifyExpandingStarted();
- beginJankMonitoring();
- fling(0, true /* expand */);
- } else {
- setExpandedFraction(1f);
- }
- mInstantExpanding = false;
- }
- }
- });// Make sure a layout really happens.
- this.mView.requestLayout();
- }
-
+ super.expand(animate);
setListening(true);
}
+ @Override
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
}
- mOverExpansion = overExpansion;
+ super.setOverExpansion(overExpansion);
// Translating the quick settings by half the overexpansion to center it in the background
// frame
updateQsFrameTranslation();
@@ -3693,13 +3394,10 @@
mQsTranslationForFullShadeTransition);
}
- private void onTrackingStarted() {
+ @Override
+ protected void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
- endClosing();
- mTracking = true;
- mCentralSurfaces.onTrackingStarted();
- notifyExpandingStarted();
- updatePanelExpansionAndVisibility();
+ super.onTrackingStarted();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
setQsExpandImmediate(true);
@@ -3709,11 +3407,10 @@
cancelPendingPanelCollapse();
}
- private void onTrackingStopped(boolean expand) {
+ @Override
+ protected void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
- mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
- updatePanelExpansionAndVisibility();
+ super.onTrackingStopped(expand);
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
true /* animate */);
@@ -3730,50 +3427,38 @@
getHeight(), mNavigationBarBottomHeight);
}
- @VisibleForTesting
- void startUnlockHintAnimation() {
+ @Override
+ protected void startUnlockHintAnimation() {
if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) {
onUnlockHintStarted();
onUnlockHintFinished();
return;
}
-
- // We don't need to hint the user if an animation is already running or the user is changing
- // the expansion.
- if (mHeightAnimator != null || mTracking) {
- return;
- }
- notifyExpandingStarted();
- startUnlockHintAnimationPhase1(() -> {
- notifyExpandingFinished();
- onUnlockHintFinished();
- mHintAnimationRunning = false;
- });
- onUnlockHintStarted();
- mHintAnimationRunning = true;
+ super.startUnlockHintAnimation();
}
- @VisibleForTesting
- void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
+ @Override
+ protected void onUnlockHintFinished() {
+ super.onUnlockHintFinished();
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
- @VisibleForTesting
- void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
+ @Override
+ protected void onUnlockHintStarted() {
+ super.onUnlockHintStarted();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
- private boolean shouldUseDismissingAnimation() {
+ @Override
+ protected boolean shouldUseDismissingAnimation() {
return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
|| !isTracking());
}
- @VisibleForTesting
- boolean isTrackingBlocked() {
+ @Override
+ protected boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3795,17 +3480,19 @@
return mIsLaunchTransitionFinished;
}
+ @Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
- mIsLaunchAnimationRunning = running;
+ super.setIsLaunchAnimationRunning(running);
if (wasRunning != mIsLaunchAnimationRunning) {
mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
}
}
- private void setIsClosing(boolean isClosing) {
+ @Override
+ protected void setIsClosing(boolean isClosing) {
boolean wasClosing = isClosing();
- mClosing = isClosing;
+ super.setIsClosing(isClosing);
if (wasClosing != isClosing) {
mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
}
@@ -3819,6 +3506,7 @@
}
}
+ @Override
public boolean isDozing() {
return mDozing;
}
@@ -3835,7 +3523,8 @@
mKeyguardStatusViewController.dozeTimeTick();
}
- private boolean onMiddleClicked() {
+ @Override
+ protected boolean onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3893,13 +3582,15 @@
updateVisibility();
}
- private boolean shouldPanelBeVisible() {
+ @Override
+ protected boolean shouldPanelBeVisible() {
boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
return headsUpVisible || isExpanded() || mBouncerShowing;
}
+ @Override
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- mHeadsUpManager = headsUpManager;
+ super.setHeadsUpManager(headsUpManager);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -3913,7 +3604,8 @@
// otherwise we update the state when the expansion is finished
}
- private void onClosingFinished() {
+ @Override
+ protected void onClosingFinished() {
mCentralSurfaces.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
@@ -3977,7 +3669,8 @@
mCentralSurfaces.clearNotificationEffects();
}
- private boolean isPanelVisibleBecauseOfHeadsUp() {
+ @Override
+ protected boolean isPanelVisibleBecauseOfHeadsUp() {
return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
&& mBarState == StatusBarState.SHADE;
}
@@ -4091,15 +3784,9 @@
mNotificationBoundsAnimationDelay = delay;
}
+ @Override
public void setTouchAndAnimationDisabled(boolean disabled) {
- mTouchDisabled = disabled;
- if (mTouchDisabled) {
- cancelHeightAnimator();
- if (mTracking) {
- onTrackingStopped(true /* expanded */);
- }
- notifyExpandingFinished();
- }
+ super.setTouchAndAnimationDisabled(disabled);
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -4301,14 +3988,9 @@
mBlockingExpansionForCurrentTouch = mTracking;
}
+ @Override
public void dump(PrintWriter pw, String[] args) {
- pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
- + " tracking=%s timeAnim=%s%s "
- + "touchDisabled=%s" + "]",
- this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
- mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
- ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
- mTouchDisabled ? "T" : "f"));
+ super.dump(pw, args);
IndentingPrintWriter ipw = asIndenting(pw);
ipw.increaseIndent();
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
@@ -4451,359 +4133,127 @@
mConfigurationListener.onThemeChanged();
}
- private OnLayoutChangeListener createLayoutChangeListener() {
- return new OnLayoutChangeListener();
+ @Override
+ protected OnLayoutChangeListener createLayoutChangeListener() {
+ return new OnLayoutChangeListenerImpl();
}
- @VisibleForTesting
- TouchHandler createTouchHandler() {
- return new TouchHandler();
- }
+ @Override
+ protected TouchHandler createTouchHandler() {
+ return new TouchHandler() {
- public class TouchHandler implements View.OnTouchListener {
+ private long mLastTouchDownTime = -1L;
- private long mLastTouchDownTime = -1L;
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (SPEW_LOGCAT) {
- Log.v(TAG,
- "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
- + "," + event.getY() + ")");
- }
- if (mBlockTouches || mQs.disallowPanelTouches()) {
- return false;
- }
- initDownStates(event);
- // Do not let touches go to shade or QS if the bouncer is visible,
- // but still let user swipe down to expand the panel, dismissing the bouncer.
- if (mCentralSurfaces.isBouncerShowing()) {
- return true;
- }
- if (mCommandQueue.panelsEnabled()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- return true;
- }
- if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
- return true;
- }
-
- if (!isFullyCollapsed() && onQsIntercept(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
- return true;
- }
- if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
- && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
- return false;
- }
-
- /*
- * If the user drags anywhere inside the panel we intercept it if the movement is
- * upwards. This allows closing the shade from anywhere inside the panel.
- *
- * We only do this if the current content is scrolled to the bottom,
- * i.e. canCollapsePanelOnTouch() is true and therefore there is no conflicting
- * scrolling gesture possible.
- */
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
- boolean canCollapsePanel = canCollapsePanelOnTouch();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mCentralSurfaces.userActivity();
- mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
- mMinExpandHeight = 0.0f;
- mDownTime = mSystemClock.uptimeMillis();
- if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
- cancelHeightAnimator();
- mTouchSlopExceeded = true;
- return true;
- }
- mInitialExpandY = y;
- mInitialExpandX = x;
- mTouchStartedInEmptyArea = !isInContentBounds(x, y);
- mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
- mMotionAborted = false;
- mPanelClosedOnDown = isFullyCollapsed();
- mCollapsedAndHeadsUpOnDown = false;
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mTouchAboveFalsingThreshold = false;
- addMovement(event);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialExpandX = event.getX(newIndex);
- mInitialExpandY = event.getY(newIndex);
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- mVelocityTracker.clear();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialExpandY;
- addMovement(event);
- final boolean openShadeWithoutHun =
- mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
- || openShadeWithoutHun) {
- float hAbs = Math.abs(h);
- float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop
- || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
- && hAbs > Math.abs(x - mInitialExpandX)) {
- cancelHeightAnimator();
- startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mVelocityTracker.clear();
- break;
- }
- return false;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- if (event.getDownTime() == mLastTouchDownTime) {
- // An issue can occur when swiping down after unlock, where multiple down
- // events are received in this handler with identical downTimes. Until the
- // source of the issue can be located, detect this case and ignore.
- // see b/193350347
- Log.w(TAG, "Duplicate down event detected... ignoring");
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (SPEW_LOGCAT) {
+ Log.v(TAG,
+ "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
+ + "," + event.getY() + ")");
+ }
+ if (mBlockTouches || mQs.disallowPanelTouches()) {
+ return false;
+ }
+ initDownStates(event);
+ // Do not let touches go to shade or QS if the bouncer is visible,
+ // but still let user swipe down to expand the panel, dismissing the bouncer.
+ if (mCentralSurfaces.isBouncerShowing()) {
return true;
}
- mLastTouchDownTime = event.getDownTime();
- }
-
-
- if (mBlockTouches || (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches())) {
- return false;
- }
-
- // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
- // otherwise user would be able to pull down QS or expand the shade.
- if (mCentralSurfaces.isBouncerShowingScrimmed()
- || mCentralSurfaces.isBouncerShowingOverDream()) {
- return false;
- }
-
- // Make sure the next touch won't the blocked after the current ends.
- if (event.getAction() == MotionEvent.ACTION_UP
- || event.getAction() == MotionEvent.ACTION_CANCEL) {
- mBlockingExpansionForCurrentTouch = false;
- }
- // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
- // without any ACTION_MOVE event.
- // In such case, simply expand the panel instead of being stuck at the bottom bar.
- if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
- expand(true /* animate */);
- }
- initDownStates(event);
-
- // If pulse is expanding already, let's give it the touch. There are situations
- // where the panel starts expanding even though we're also pulsing
- boolean pulseShouldGetTouch = (!mIsExpanding
- && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
- || mPulseExpansionHandler.isExpanding();
- if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
- // We're expanding all the other ones shouldn't get this anymore
- mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
- return true;
- }
- if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- }
- boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
-
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- return true;
- }
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- handled = true;
- }
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
- && mStatusBarKeyguardViewManager.isShowing()) {
- mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
- }
- handled |= handleTouch(v, event);
- return !mDozing || mPulsing || handled;
- }
-
- public boolean handleTouch(View v, MotionEvent event) {
- if (mInstantExpanding) {
- mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
- return false;
- }
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
- mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
- return false;
- }
- if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
- mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
- return false;
- }
-
- // If dragging should not expand the notifications shade, then return false.
- if (!mNotificationsDragEnabled) {
- if (mTracking) {
- // Turn off tracking if it's on or the shade can get stuck in the down position.
- onTrackingStopped(true /* expand */);
+ if (mCommandQueue.panelsEnabled()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ return true;
}
- mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
- return false;
- }
-
- // On expanding, single mouse click expands the panel instead of dragging.
- if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- expand(true);
+ if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ return true;
}
- return true;
+
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
+ if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+ return true;
+ }
+ return super.onInterceptTouchEvent(event);
}
- /*
- * We capture touch events here and update the expand height here in case according to
- * the users fingers. This also handles multi-touch.
- *
- * Flinging is also enabled in order to open or close the shade.
- */
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getDownTime() == mLastTouchDownTime) {
+ // An issue can occur when swiping down after unlock, where multiple down
+ // events are received in this handler with identical downTimes. Until the
+ // source of the issue can be located, detect this case and ignore.
+ // see b/193350347
+ Log.w(TAG, "Duplicate down event detected... ignoring");
+ return true;
+ }
+ mLastTouchDownTime = event.getDownTime();
+ }
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
+
+ if (mBlockTouches || (mQsFullyExpanded && mQs != null
+ && mQs.disallowPanelTouches())) {
+ return false;
+ }
+
+ // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // otherwise user would be able to pull down QS or expand the shade.
+ if (mCentralSurfaces.isBouncerShowingScrimmed()
+ || mCentralSurfaces.isBouncerShowingOverDream()) {
+ return false;
+ }
+
+ // Make sure the next touch won't the blocked after the current ends.
+ if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ mBlockingExpansionForCurrentTouch = false;
+ }
+ // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+ // without any ACTION_MOVE event.
+ // In such case, simply expand the panel instead of being stuck at the bottom bar.
+ if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true /* animate */);
+ }
+ initDownStates(event);
+
+ // If pulse is expanding already, let's give it the touch. There are situations
+ // where the panel starts expanding even though we're also pulsing
+ boolean pulseShouldGetTouch = (!mIsExpanding
+ && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
+ || mPulseExpansionHandler.isExpanding();
+ if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
+ // We're expanding all the other ones shouldn't get this anymore
+ mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
+ return true;
+ }
+ if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ }
+ boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
+
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ return true;
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ handled = true;
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
+ && mStatusBarKeyguardViewManager.isShowing()) {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
+ }
+
+ handled |= super.onTouch(v, event);
+ return !mDozing || mPulsing || handled;
}
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = true;
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- mMinExpandHeight = 0.0f;
- mPanelClosedOnDown = isFullyCollapsed();
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mMotionAborted = false;
- mDownTime = mSystemClock.uptimeMillis();
- mTouchAboveFalsingThreshold = false;
- mCollapsedAndHeadsUpOnDown =
- isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
- addMovement(event);
- boolean regularHeightAnimationRunning = mHeightAnimator != null
- && !mHintAnimationRunning && !mIsSpringBackAnimation;
- if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
- mTouchSlopExceeded = regularHeightAnimationRunning
- || mTouchSlopExceededBeforeDown;
- cancelHeightAnimator();
- onTrackingStarted();
- }
- if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
- && !mCentralSurfaces.isBouncerShowing()) {
- startOpening(event);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
- mHandlingPointerUp = true;
- startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
- mHandlingPointerUp = false;
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- endMotionEvent(event, x, y, true /* forceCancel */);
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- addMovement(event);
- float h = y - mInitialTouchY;
-
- // If the panel was collapsed when touching, we only need to check for the
- // y-component of the gesture, as we have no conflicting horizontal gesture.
- if (Math.abs(h) > getTouchSlop(event)
- && (Math.abs(h) > Math.abs(x - mInitialTouchX)
- || mIgnoreXTouchSlop)) {
- mTouchSlopExceeded = true;
- if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
- if (mInitialOffsetOnTouch != 0f) {
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- h = 0;
- }
- cancelHeightAnimator();
- onTrackingStarted();
- }
- }
- float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
- newHeight = Math.max(newHeight, mMinExpandHeight);
- if (-h >= getFalsingThreshold()) {
- mTouchAboveFalsingThreshold = true;
- mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
- }
- if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
- // Count h==0 as part of swipe-up,
- // otherwise {@link NotificationStackScrollLayout}
- // wrongly enables stack height updates at the start of lockscreen swipe-up
- mAmbientState.setSwipingUp(h <= 0);
- setExpandedHeightInternal(newHeight);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- addMovement(event);
- endMotionEvent(event, x, y, false /* forceCancel */);
- // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
- if (mHeightAnimator == null) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring();
- } else {
- cancelJankMonitoring();
- }
- }
- break;
- }
- return !mGestureWaitForTouchSlop || mTracking;
- }
+ };
}
private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
@@ -4855,7 +4305,8 @@
}
};
- private OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ @Override
+ protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
@@ -4917,585 +4368,6 @@
.commitUpdate(mDisplayId);
}
- private void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
- }
-
- private void notifyExpandingStarted() {
- if (!mExpanding) {
- mExpanding = true;
- onExpandingStarted();
- }
- }
-
- private void notifyExpandingFinished() {
- endClosing();
- if (mExpanding) {
- mExpanding = false;
- onExpandingFinished();
- }
- }
-
- private float getTouchSlop(MotionEvent event) {
- // Adjust the touch slop if another gesture may be being performed.
- return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
- ? mTouchSlop * mSlopMultiplier
- : mTouchSlop;
- }
-
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
- public void startExpandLatencyTracking() {
- if (mLatencyTracker.isEnabled()) {
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
- mExpandLatencyTracking = true;
- }
- }
-
- private void startOpening(MotionEvent event) {
- updatePanelExpansionAndVisibility();
- maybeVibrateOnOpening();
-
- //TODO: keyguard opens QS a different way; log that too?
-
- // Log the position of the swipe that opened the panel
- float width = mCentralSurfaces.getDisplayWidth();
- float height = mCentralSurfaces.getDisplayHeight();
- int rot = mCentralSurfaces.getRotation();
-
- mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
- (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
- mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
- }
-
- private void maybeVibrateOnOpening() {
- if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
- }
-
- /**
- * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
- * horizontal direction
- */
- private boolean isDirectionUpwards(float x, float y) {
- float xDiff = x - mInitialExpandX;
- float yDiff = y - mInitialExpandY;
- if (yDiff >= 0) {
- return false;
- }
- return Math.abs(yDiff) >= Math.abs(xDiff);
- }
-
- public void startExpandMotion(float newX, float newY, boolean startTracking,
- float expandedHeight) {
- if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- mInitialOffsetOnTouch = expandedHeight;
- mInitialExpandY = newY;
- mInitialExpandX = newX;
- mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- if (startTracking) {
- mTouchSlopExceeded = true;
- setExpandedHeight(mInitialOffsetOnTouch);
- onTrackingStarted();
- }
- }
-
- private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
- mTrackingPointer = -1;
- mAmbientState.setSwipingUp(false);
- if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
- || Math.abs(y - mInitialExpandY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- mVelocityTracker.computeCurrentVelocity(1000);
- float vel = mVelocityTracker.getYVelocity();
- float vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- final boolean onKeyguard =
- mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-
- final boolean expand;
- if (mKeyguardStateController.isKeyguardFadingAway()
- || (mInitialTouchFromKeyguard && !onKeyguard)) {
- // Don't expand for any touches that started from the keyguard and ended after the
- // keyguard is gone.
- expand = false;
- } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- if (onKeyguard) {
- expand = true;
- } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
- expand = false;
- } else {
- // If we get a cancel, put the shade back to the state it was in when the
- // gesture started
- expand = !mPanelClosedOnDown;
- }
- } else {
- expand = flingExpands(vel, vectorVel, x, y);
- }
-
- mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
- mCentralSurfaces.isWakeUpComingFromTouch());
- // Log collapse gesture if on lock screen.
- if (!expand && onKeyguard) {
- float displayDensity = mCentralSurfaces.getDisplayDensity();
- int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
- int velocityDp = (int) Math.abs(vel / displayDensity);
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
- }
- @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
- : y - mInitialExpandY > 0 ? QUICK_SETTINGS
- : (mKeyguardStateController.canDismissLockScreen()
- ? UNLOCK : BOUNCER_UNLOCK);
-
- fling(vel, expand, isFalseTouch(x, y, interactionType));
- onTrackingStopped(expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
- && !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick();
- onTrackingStopped(expands);
- }
- mVelocityTracker.clear();
- }
-
- private float getCurrentExpandVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getYVelocity();
- }
-
- private void endClosing() {
- if (mClosing) {
- setIsClosing(false);
- onClosingFinished();
- }
- }
-
- /**
- * @param x the final x-coordinate when the finger was lifted
- * @param y the final y-coordinate when the finger was lifted
- * @return whether this motion should be regarded as a false touch
- */
- private boolean isFalseTouch(float x, float y,
- @Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(interactionType);
- }
- if (!mTouchAboveFalsingThreshold) {
- return true;
- }
- if (mUpwardsWhenThresholdReached) {
- return false;
- }
- return !isDirectionUpwards(x, y);
- }
-
- private void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
- }
-
- private void fling(float vel, boolean expand, float collapseSpeedUpFactor,
- boolean expandBecauseOfFalsing) {
- float target = expand ? getMaxPanelHeight() : 0;
- if (!expand) {
- setIsClosing(true);
- }
- flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
- }
-
- private void springBack() {
- if (mOverExpansion == 0) {
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsSpringBackAnimation = true;
- ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
- animator.addUpdateListener(
- animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */));
- animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsSpringBackAnimation = false;
- onFlingEnd(mCancelled);
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- public String getName() {
- return mViewName;
- }
-
- public void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
- setExpandedHeightInternal(height);
- }
-
- private void requestPanelHeightUpdate() {
- float currentMaxPanelHeight = getMaxPanelHeight();
-
- if (isFullyCollapsed()) {
- return;
- }
-
- if (currentMaxPanelHeight == mExpandedHeight) {
- return;
- }
-
- if (mTracking && !isTrackingBlocked()) {
- return;
- }
-
- if (mHeightAnimator != null && !mIsSpringBackAnimation) {
- mPanelUpdateWhenAnimatorEnds = true;
- return;
- }
-
- setExpandedHeight(currentMaxPanelHeight);
- }
-
- public void setExpandedHeightInternal(float h) {
- if (isNaN(h)) {
- Log.wtf(TAG, "ExpandedHeight set to NaN");
- }
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
- if (mExpandLatencyTracking && h != 0f) {
- DejankUtils.postAfterTraversal(
- () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
- mExpandLatencyTracking = false;
- }
- float maxPanelHeight = getMaxPanelHeight();
- if (mHeightAnimator == null) {
- // Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
- float overExpansionPixels = Math.max(0, h - maxPanelHeight);
- setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
- }
- mExpandedHeight = Math.min(h, maxPanelHeight);
- } else {
- mExpandedHeight = h;
- }
-
- // If we are closing the panel and we are almost there due to a slow decelerating
- // interpolator, abort the animation.
- if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
- mExpandedHeight = 0f;
- if (mHeightAnimator != null) {
- mHeightAnimator.end();
- }
- }
- mExpansionDragDownAmountPx = h;
- mExpandedFraction = Math.min(1f,
- maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
- mAmbientState.setExpansionFraction(mExpandedFraction);
- onHeightUpdated(mExpandedHeight);
- updatePanelExpansionAndVisibility();
- });
- }
-
- /**
- * Set the current overexpansion
- *
- * @param overExpansion the amount of overexpansion to apply
- * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
- */
- private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
- if (!isFromGesture) {
- mLastGesturedOverExpansion = -1;
- setOverExpansion(overExpansion);
- } else if (mLastGesturedOverExpansion != overExpansion) {
- mLastGesturedOverExpansion = overExpansion;
- final float heightForFullOvershoot = mView.getHeight() / 3.0f;
- float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
- newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
- setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
- }
- }
-
- public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelHeight() * frac);
- }
-
- public float getExpandedHeight() {
- return mExpandedHeight;
- }
-
- public float getExpandedFraction() {
- return mExpandedFraction;
- }
-
- public boolean isFullyExpanded() {
- return mExpandedHeight >= getMaxPanelHeight();
- }
-
- public boolean isFullyCollapsed() {
- return mExpandedFraction <= 0.0f;
- }
-
- public boolean isCollapsing() {
- return mClosing || mIsLaunchAnimationRunning;
- }
-
- public boolean isFlinging() {
- return mIsFlinging;
- }
-
- public boolean isTracking() {
- return mTracking;
- }
-
- public boolean canPanelBeCollapsed() {
- return !isFullyCollapsed() && !mTracking && !mClosing;
- }
-
- private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
- mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
-
- public void instantCollapse() {
- abortAnimations();
- setExpandedFraction(0f);
- if (mExpanding) {
- notifyExpandingFinished();
- }
- if (mInstantExpanding) {
- mInstantExpanding = false;
- updatePanelExpansionAndVisibility();
- }
- }
-
- private void abortAnimations() {
- cancelHeightAnimator();
- mView.removeCallbacks(mFlingCollapseRunnable);
- }
-
- public boolean isUnlockHintRunning() {
- return mHintAnimationRunning;
- }
-
- /**
- * Phase 1: Move everything upwards.
- */
- private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
- float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
- ValueAnimator animator = createHeightAnimator(target);
- animator.setDuration(250);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- setAnimator(null);
- onAnimationFinished.run();
- } else {
- startUnlockHintAnimationPhase2(onAnimationFinished);
- }
- }
- });
- animator.start();
- setAnimator(animator);
-
- final List<ViewPropertyAnimator> indicationAnimators =
- mKeyguardBottomArea.getIndicationAreaAnimators();
- for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
- indicationAreaAnimator
- .translationY(-mHintDistance)
- .setDuration(250)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(() -> indicationAreaAnimator
- .translationY(0)
- .setDuration(450)
- .setInterpolator(mBounceInterpolator)
- .start())
- .start();
- }
- }
-
- private void setAnimator(ValueAnimator animator) {
- mHeightAnimator = animator;
- if (animator == null && mPanelUpdateWhenAnimatorEnds) {
- mPanelUpdateWhenAnimatorEnds = false;
- requestPanelHeightUpdate();
- }
- }
-
- /**
- * Phase 2: Bounce down.
- */
- private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
- ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
- animator.setDuration(450);
- animator.setInterpolator(mBounceInterpolator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setAnimator(null);
- onAnimationFinished.run();
- updatePanelExpansionAndVisibility();
- }
- });
- animator.start();
- setAnimator(animator);
- }
-
- private ValueAnimator createHeightAnimator(float targetHeight) {
- return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
- }
-
- /**
- * Create an animator that can also overshoot
- *
- * @param targetHeight the target height
- * @param overshootAmount the amount of overshoot desired
- */
- private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
- float startExpansion = mOverExpansion;
- ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
- animator.addUpdateListener(
- animation -> {
- if (overshootAmount > 0.0f
- // Also remove the overExpansion when collapsing
- || (targetHeight == 0.0f && startExpansion != 0)) {
- final float expansion = MathUtils.lerp(
- startExpansion,
- mPanelFlingOvershootAmount * overshootAmount,
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
- animator.getAnimatedFraction()));
- setOverExpansionInternal(expansion, false /* isFromGesture */);
- }
- setExpandedHeightInternal((float) animation.getAnimatedValue());
- });
- return animator;
- }
-
- /** Update the visibility of {@link NotificationPanelView} if necessary. */
- public void updateVisibility() {
- mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
- }
-
- /**
- * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
- *
- * TODO(b/200063118): Could public calls to this method be replaced with calls to
- * {@link #updateVisibility()}? That would allow us to make this method private.
- */
- public void updatePanelExpansionAndVisibility() {
- mPanelExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
- updateVisibility();
- }
-
- public boolean isExpanded() {
- return mExpandedFraction > 0f
- || mInstantExpanding
- || isPanelVisibleBecauseOfHeadsUp()
- || mTracking
- || mHeightAnimator != null
- && !mIsSpringBackAnimation;
- }
-
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- private boolean onEmptySpaceClick() {
- if (mHintAnimationRunning) {
- return true;
- }
- return onMiddleClicked();
- }
-
- @VisibleForTesting
- boolean isClosing() {
- return mClosing;
- }
-
- public void collapseWithDuration(int animationDuration) {
- mFixedDuration = animationDuration;
- collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- mFixedDuration = NO_FIXED_DURATION;
- }
-
- public ViewGroup getView() {
- // TODO: remove this method, or at least reduce references to it.
- return mView;
- }
-
- private void beginJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- mView)
- .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
- mInteractionJankMonitor.begin(builder);
- }
-
- private void endJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().end(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- private void cancelJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().cancel(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- private float getExpansionFraction() {
- return mExpandedFraction;
- }
-
- private PanelExpansionStateManager getPanelExpansionStateManager() {
- return mPanelExpansionStateManager;
- }
-
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -5901,19 +4773,13 @@
}
}
- private class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+ private class OnLayoutChangeListenerImpl extends OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
- requestPanelHeightUpdate();
- mHasLayoutedSinceDown = true;
- if (mUpdateFlingOnLayout) {
- abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
- mUpdateFlingOnLayout = false;
- }
+ super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
@@ -6169,12 +5035,4 @@
}
}
}
-
- public class OnConfigurationChangedListener implements
- NotificationPanelView.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadDimens();
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
new file mode 100644
index 0000000..11e36c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -0,0 +1,1479 @@
+/*
+ * Copyright (C) 2019 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.shade;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.GENERIC;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.NotificationPanelView.DEBUG;
+
+import static java.lang.Float.isNaN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.VibrationEffect;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.Classifier;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.time.SystemClock;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+abstract class PanelViewController {
+ public static final String TAG = NotificationPanelView.class.getSimpleName();
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
+ private static final int NO_FIXED_DURATION = -1;
+ private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
+ private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
+
+ /**
+ * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
+ * when flinging. A low value will make it that most flings will reach the maximum overshoot.
+ */
+ private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+
+ protected long mDownTime;
+ protected boolean mTouchSlopExceededBeforeDown;
+ private float mMinExpandHeight;
+ private boolean mPanelUpdateWhenAnimatorEnds;
+ private final boolean mVibrateOnOpening;
+ protected boolean mIsLaunchAnimationRunning;
+ private int mFixedDuration = NO_FIXED_DURATION;
+ protected float mOverExpansion;
+
+ /**
+ * The overshoot amount when the panel flings open
+ */
+ private float mPanelFlingOvershootAmount;
+
+ /**
+ * The amount of pixels that we have overexpanded the last time with a gesture
+ */
+ private float mLastGesturedOverExpansion = -1;
+
+ /**
+ * Is the current animator the spring back animation?
+ */
+ private boolean mIsSpringBackAnimation;
+
+ private boolean mInSplitShade;
+
+ private void logf(String fmt, Object... args) {
+ Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
+
+ protected CentralSurfaces mCentralSurfaces;
+ protected HeadsUpManagerPhone mHeadsUpManager;
+ protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+
+ private float mHintDistance;
+ private float mInitialOffsetOnTouch;
+ private boolean mCollapsedAndHeadsUpOnDown;
+ private float mExpandedFraction = 0;
+ private float mExpansionDragDownAmountPx = 0;
+ protected float mExpandedHeight = 0;
+ private boolean mPanelClosedOnDown;
+ private boolean mHasLayoutedSinceDown;
+ private float mUpdateFlingVelocity;
+ private boolean mUpdateFlingOnLayout;
+ private boolean mClosing;
+ protected boolean mTracking;
+ private boolean mTouchSlopExceeded;
+ private int mTrackingPointer;
+ private int mTouchSlop;
+ private float mSlopMultiplier;
+ protected boolean mHintAnimationRunning;
+ private boolean mTouchAboveFalsingThreshold;
+ private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
+ private boolean mUpwardsWhenThresholdReached;
+ private boolean mAnimatingOnDown;
+ private boolean mHandlingPointerUp;
+
+ private ValueAnimator mHeightAnimator;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final LatencyTracker mLatencyTracker;
+ private final FalsingManager mFalsingManager;
+ private final DozeLog mDozeLog;
+ private final VibratorHelper mVibratorHelper;
+
+ /**
+ * Whether an instant expand request is currently pending and we are just waiting for layout.
+ */
+ private boolean mInstantExpanding;
+ private boolean mAnimateAfterExpanding;
+ private boolean mIsFlinging;
+
+ private String mViewName;
+ private float mInitialExpandY;
+ private float mInitialExpandX;
+ private boolean mTouchDisabled;
+ private boolean mInitialTouchFromKeyguard;
+
+ /**
+ * Whether or not the NotificationPanelView can be expanded or collapsed with a drag.
+ */
+ private final boolean mNotificationsDragEnabled;
+
+ private final Interpolator mBounceInterpolator;
+ protected KeyguardBottomAreaView mKeyguardBottomArea;
+
+ /**
+ * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
+ */
+ private float mNextCollapseSpeedUpFactor = 1.0f;
+
+ protected boolean mExpanding;
+ private boolean mGestureWaitForTouchSlop;
+ private boolean mIgnoreXTouchSlop;
+ private boolean mExpandLatencyTracking;
+ private final NotificationPanelView mView;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ protected final Resources mResources;
+ protected final KeyguardStateController mKeyguardStateController;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
+ protected final AmbientState mAmbientState;
+ protected final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final PanelExpansionStateManager mPanelExpansionStateManager;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+ protected final SystemClock mSystemClock;
+
+ protected final ShadeLogger mShadeLog;
+
+ protected abstract void onExpandingFinished();
+
+ protected void onExpandingStarted() {
+ }
+
+ protected void notifyExpandingStarted() {
+ if (!mExpanding) {
+ mExpanding = true;
+ onExpandingStarted();
+ }
+ }
+
+ protected final void notifyExpandingFinished() {
+ endClosing();
+ if (mExpanding) {
+ mExpanding = false;
+ onExpandingFinished();
+ }
+ }
+
+ protected AmbientState getAmbientState() {
+ return mAmbientState;
+ }
+
+ public PanelViewController(
+ NotificationPanelView view,
+ FalsingManager falsingManager,
+ DozeLog dozeLog,
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ NotificationShadeWindowController notificationShadeWindowController,
+ VibratorHelper vibratorHelper,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ LatencyTracker latencyTracker,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+ StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ LockscreenGestureLogger lockscreenGestureLogger,
+ PanelExpansionStateManager panelExpansionStateManager,
+ AmbientState ambientState,
+ InteractionJankMonitor interactionJankMonitor,
+ ShadeLogger shadeLogger,
+ SystemClock systemClock) {
+ keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ requestPanelHeightUpdate();
+ }
+ });
+ mAmbientState = ambientState;
+ mView = view;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeLog = shadeLogger;
+ TouchHandler touchHandler = createTouchHandler();
+ mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewName = mResources.getResourceName(mView.getId());
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mView.addOnLayoutChangeListener(createLayoutChangeListener());
+ mView.setOnTouchListener(touchHandler);
+ mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+
+ mResources = mView.getResources();
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mFlingAnimationUtils = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(0.5f)
+ .setSpeedUpFactor(0.6f)
+ .setX2(0.6f)
+ .setY2(0.84f)
+ .build();
+ mLatencyTracker = latencyTracker;
+ mBounceInterpolator = new BounceInterpolator();
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mNotificationsDragEnabled = mResources.getBoolean(
+ R.bool.config_enableNotificationShadeDrag);
+ mVibratorHelper = vibratorHelper;
+ mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+ mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mInteractionJankMonitor = interactionJankMonitor;
+ mSystemClock = systemClock;
+ }
+
+ protected void loadDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+ mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
+ mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
+ mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
+ }
+
+ protected float getTouchSlop(MotionEvent event) {
+ // Adjust the touch slop if another gesture may be being performed.
+ return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+ ? mTouchSlop * mSlopMultiplier
+ : mTouchSlop;
+ }
+
+ private void addMovement(MotionEvent event) {
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ public void setTouchAndAnimationDisabled(boolean disabled) {
+ mTouchDisabled = disabled;
+ if (mTouchDisabled) {
+ cancelHeightAnimator();
+ if (mTracking) {
+ onTrackingStopped(true /* expanded */);
+ }
+ notifyExpandingFinished();
+ }
+ }
+
+ public void startExpandLatencyTracking() {
+ if (mLatencyTracker.isEnabled()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
+ mExpandLatencyTracking = true;
+ }
+ }
+
+ private void startOpening(MotionEvent event) {
+ updatePanelExpansionAndVisibility();
+ maybeVibrateOnOpening();
+
+ //TODO: keyguard opens QS a different way; log that too?
+
+ // Log the position of the swipe that opened the panel
+ float width = mCentralSurfaces.getDisplayWidth();
+ float height = mCentralSurfaces.getDisplayHeight();
+ int rot = mCentralSurfaces.getRotation();
+
+ mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+ (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+ mLockscreenGestureLogger
+ .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
+ }
+
+ protected void maybeVibrateOnOpening() {
+ if (mVibrateOnOpening) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ }
+
+ protected abstract float getOpeningHeight();
+
+ /**
+ * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+ * horizontal direction
+ */
+ private boolean isDirectionUpwards(float x, float y) {
+ float xDiff = x - mInitialExpandX;
+ float yDiff = y - mInitialExpandY;
+ if (yDiff >= 0) {
+ return false;
+ }
+ return Math.abs(yDiff) >= Math.abs(xDiff);
+ }
+
+ public void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialExpandY = newY;
+ mInitialExpandX = newX;
+ mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ setExpandedHeight(mInitialOffsetOnTouch);
+ onTrackingStarted();
+ }
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ mAmbientState.setSwipingUp(false);
+ if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+ || Math.abs(y - mInitialExpandY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float vel = mVelocityTracker.getYVelocity();
+ float vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ final boolean onKeyguard =
+ mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+
+ final boolean expand;
+ if (mKeyguardStateController.isKeyguardFadingAway()
+ || (mInitialTouchFromKeyguard && !onKeyguard)) {
+ // Don't expand for any touches that started from the keyguard and ended after the
+ // keyguard is gone.
+ expand = false;
+ } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ if (onKeyguard) {
+ expand = true;
+ } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
+ expand = false;
+ } else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
+ expand = !mPanelClosedOnDown;
+ }
+ } else {
+ expand = flingExpands(vel, vectorVel, x, y);
+ }
+
+ mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+ mCentralSurfaces.isFalsingThresholdNeeded(),
+ mCentralSurfaces.isWakeUpComingFromTouch());
+ // Log collapse gesture if on lock screen.
+ if (!expand && onKeyguard) {
+ float displayDensity = mCentralSurfaces.getDisplayDensity();
+ int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
+ int velocityDp = (int) Math.abs(vel / displayDensity);
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
+ }
+ @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
+ : y - mInitialExpandY > 0 ? QUICK_SETTINGS
+ : (mKeyguardStateController.canDismissLockScreen()
+ ? UNLOCK : BOUNCER_UNLOCK);
+
+ fling(vel, expand, isFalseTouch(x, y, interactionType));
+ onTrackingStopped(expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else if (!mCentralSurfaces.isBouncerShowing()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mKeyguardStateController.isKeyguardGoingAway()) {
+ boolean expands = onEmptySpaceClick();
+ onTrackingStopped(expands);
+ }
+ mVelocityTracker.clear();
+ }
+
+ protected float getCurrentExpandVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ return mVelocityTracker.getYVelocity();
+ }
+
+ protected abstract int getFalsingThreshold();
+
+ protected abstract boolean shouldGestureWaitForTouchSlop();
+
+ protected void onTrackingStopped(boolean expand) {
+ mTracking = false;
+ mCentralSurfaces.onTrackingStopped(expand);
+ updatePanelExpansionAndVisibility();
+ }
+
+ protected void onTrackingStarted() {
+ endClosing();
+ mTracking = true;
+ mCentralSurfaces.onTrackingStarted();
+ notifyExpandingStarted();
+ updatePanelExpansionAndVisibility();
+ }
+
+ /**
+ * @return Whether a pair of coordinates are inside the visible view content bounds.
+ */
+ protected abstract boolean isInContentBounds(float x, float y);
+
+ protected void cancelHeightAnimator() {
+ if (mHeightAnimator != null) {
+ if (mHeightAnimator.isRunning()) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ }
+ mHeightAnimator.cancel();
+ }
+ endClosing();
+ }
+
+ private void endClosing() {
+ if (mClosing) {
+ setIsClosing(false);
+ onClosingFinished();
+ }
+ }
+
+ protected abstract boolean canCollapsePanelOnTouch();
+
+ protected float getContentHeight() {
+ return mExpandedHeight;
+ }
+
+ /**
+ * @param vel the current vertical velocity of the motion
+ * @param vectorVel the length of the vectorial velocity
+ * @return whether a fling should expands the panel; contracts otherwise
+ */
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ if (mFalsingManager.isUnlockingDisabled()) {
+ return true;
+ }
+
+ @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0
+ ? QUICK_SETTINGS : (
+ mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+
+ if (isFalseTouch(x, y, interactionType)) {
+ return true;
+ }
+ if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ return shouldExpandWhenNotFlinging();
+ } else {
+ return vel > 0;
+ }
+ }
+
+ protected boolean shouldExpandWhenNotFlinging() {
+ return getExpandedFraction() > 0.5f;
+ }
+
+ /**
+ * @param x the final x-coordinate when the finger was lifted
+ * @param y the final y-coordinate when the finger was lifted
+ * @return whether this motion should be regarded as a false touch
+ */
+ private boolean isFalseTouch(float x, float y,
+ @Classifier.InteractionType int interactionType) {
+ if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
+ return false;
+ }
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch(interactionType);
+ }
+ if (!mTouchAboveFalsingThreshold) {
+ return true;
+ }
+ if (mUpwardsWhenThresholdReached) {
+ return false;
+ }
+ return !isDirectionUpwards(x, y);
+ }
+
+ protected void fling(float vel, boolean expand) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ }
+
+ protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+ }
+
+ protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+ boolean expandBecauseOfFalsing) {
+ float target = expand ? getMaxPanelHeight() : 0;
+ if (!expand) {
+ setIsClosing(true);
+ }
+ flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+ protected void flingToHeight(float vel, boolean expand, float target,
+ float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+ if (target == mExpandedHeight && mOverExpansion == 0.0f) {
+ // We're at the target and didn't fling and there's no overshoot
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsFlinging = true;
+ // we want to perform an overshoot animation when flinging open
+ final boolean addOverscroll =
+ expand
+ && !mInSplitShade // Split shade has its own overscroll logic
+ && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
+ && mOverExpansion == 0.0f
+ && vel >= 0;
+ final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
+ float overshootAmount = 0.0f;
+ if (addOverscroll) {
+ // Let's overshoot depending on the amount of velocity
+ overshootAmount = MathUtils.lerp(
+ 0.2f,
+ 1.0f,
+ MathUtils.saturate(vel
+ / (mFlingAnimationUtils.getHighVelocityPxPerSecond()
+ * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
+ overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
+ }
+ ValueAnimator animator = createHeightAnimator(target, overshootAmount);
+ if (expand) {
+ if (expandBecauseOfFalsing && vel < 0) {
+ vel = 0;
+ }
+ mFlingAnimationUtils.apply(animator, mExpandedHeight,
+ target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight());
+ if (vel == 0) {
+ animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
+ }
+ } else {
+ if (shouldUseDismissingAnimation()) {
+ if (vel == 0) {
+ animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
+ animator.setDuration(duration);
+ } else {
+ mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+ mView.getHeight());
+ }
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mExpandedHeight, target, vel, mView.getHeight());
+ }
+
+ // Make it shorter if we run a canned animation
+ if (vel == 0) {
+ animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+ }
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (!mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (shouldSpringBack && !mCancelled) {
+ // After the shade is flinged open to an overscrolled state, spring back
+ // the shade by reducing section padding to 0.
+ springBack();
+ } else {
+ onFlingEnd(mCancelled);
+ }
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ private void springBack() {
+ if (mOverExpansion == 0) {
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsSpringBackAnimation = true;
+ ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
+ animator.addUpdateListener(
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+ false /* isFromGesture */));
+ animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsSpringBackAnimation = false;
+ onFlingEnd(mCancelled);
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ protected void onFlingEnd(boolean cancelled) {
+ mIsFlinging = false;
+ // No overshoot when the animation ends
+ setOverExpansionInternal(0, false /* isFromGesture */);
+ setAnimator(null);
+ mKeyguardStateController.notifyPanelFlingEnd();
+ if (!cancelled) {
+ endJankMonitoring();
+ notifyExpandingFinished();
+ } else {
+ cancelJankMonitoring();
+ }
+ updatePanelExpansionAndVisibility();
+ }
+
+ protected abstract boolean shouldUseDismissingAnimation();
+
+ public String getName() {
+ return mViewName;
+ }
+
+ public void setExpandedHeight(float height) {
+ if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ setExpandedHeightInternal(height);
+ }
+
+ protected void requestPanelHeightUpdate() {
+ float currentMaxPanelHeight = getMaxPanelHeight();
+
+ if (isFullyCollapsed()) {
+ return;
+ }
+
+ if (currentMaxPanelHeight == mExpandedHeight) {
+ return;
+ }
+
+ if (mTracking && !isTrackingBlocked()) {
+ return;
+ }
+
+ if (mHeightAnimator != null && !mIsSpringBackAnimation) {
+ mPanelUpdateWhenAnimatorEnds = true;
+ return;
+ }
+
+ setExpandedHeight(currentMaxPanelHeight);
+ }
+
+ public void setExpandedHeightInternal(float h) {
+ if (isNaN(h)) {
+ Log.wtf(TAG, "ExpandedHeight set to NaN");
+ }
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(
+ () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
+ }
+ float maxPanelHeight = getMaxPanelHeight();
+ if (mHeightAnimator == null) {
+ // Split shade has its own overscroll logic
+ if (mTracking && !mInSplitShade) {
+ float overExpansionPixels = Math.max(0, h - maxPanelHeight);
+ setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+ }
+ mExpandedHeight = Math.min(h, maxPanelHeight);
+ } else {
+ mExpandedHeight = h;
+ }
+
+ // If we are closing the panel and we are almost there due to a slow decelerating
+ // interpolator, abort the animation.
+ if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+ mExpandedHeight = 0f;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.end();
+ }
+ }
+ mExpansionDragDownAmountPx = h;
+ mExpandedFraction = Math.min(1f,
+ maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ mAmbientState.setExpansionFraction(mExpandedFraction);
+ onHeightUpdated(mExpandedHeight);
+ updatePanelExpansionAndVisibility();
+ });
+ }
+
+ /**
+ * @return true if the panel tracking should be temporarily blocked; this is used when a
+ * conflicting gesture (opening QS) is happening
+ */
+ protected abstract boolean isTrackingBlocked();
+
+ protected void setOverExpansion(float overExpansion) {
+ mOverExpansion = overExpansion;
+ }
+
+ /**
+ * Set the current overexpansion
+ *
+ * @param overExpansion the amount of overexpansion to apply
+ * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
+ */
+ private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
+ if (!isFromGesture) {
+ mLastGesturedOverExpansion = -1;
+ setOverExpansion(overExpansion);
+ } else if (mLastGesturedOverExpansion != overExpansion) {
+ mLastGesturedOverExpansion = overExpansion;
+ final float heightForFullOvershoot = mView.getHeight() / 3.0f;
+ float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
+ newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
+ setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
+ }
+ }
+
+ protected abstract void onHeightUpdated(float expandedHeight);
+
+ /**
+ * This returns the maximum height of the panel. Children should override this if their
+ * desired height is not the full height.
+ *
+ * @return the default implementation simply returns the maximum height.
+ */
+ protected abstract int getMaxPanelHeight();
+
+ public void setExpandedFraction(float frac) {
+ setExpandedHeight(getMaxPanelHeight() * frac);
+ }
+
+ public float getExpandedHeight() {
+ return mExpandedHeight;
+ }
+
+ public float getExpandedFraction() {
+ return mExpandedFraction;
+ }
+
+ public boolean isFullyExpanded() {
+ return mExpandedHeight >= getMaxPanelHeight();
+ }
+
+ public boolean isFullyCollapsed() {
+ return mExpandedFraction <= 0.0f;
+ }
+
+ public boolean isCollapsing() {
+ return mClosing || mIsLaunchAnimationRunning;
+ }
+
+ public boolean isFlinging() {
+ return mIsFlinging;
+ }
+
+ public boolean isTracking() {
+ return mTracking;
+ }
+
+ public void collapse(boolean delayed, float speedUpFactor) {
+ if (DEBUG) logf("collapse: " + this);
+ if (canPanelBeCollapsed()) {
+ cancelHeightAnimator();
+ notifyExpandingStarted();
+
+ // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+ setIsClosing(true);
+ if (delayed) {
+ mNextCollapseSpeedUpFactor = speedUpFactor;
+ mView.postDelayed(mFlingCollapseRunnable, 120);
+ } else {
+ fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+ }
+ }
+ }
+
+ public boolean canPanelBeCollapsed() {
+ return !isFullyCollapsed() && !mTracking && !mClosing;
+ }
+
+ private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+ mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
+
+ public void expand(final boolean animate) {
+ if (!isFullyCollapsed() && !isCollapsing()) {
+ return;
+ }
+
+ mInstantExpanding = true;
+ mAnimateAfterExpanding = animate;
+ mUpdateFlingOnLayout = false;
+ abortAnimations();
+ if (mTracking) {
+ onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+ }
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ updatePanelExpansionAndVisibility();
+
+ // Wait for window manager to pickup the change, so we know the maximum height of the panel
+ // then.
+ mView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mInstantExpanding) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ return;
+ }
+ if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ if (mAnimateAfterExpanding) {
+ notifyExpandingStarted();
+ beginJankMonitoring();
+ fling(0, true /* expand */);
+ } else {
+ setExpandedFraction(1f);
+ }
+ mInstantExpanding = false;
+ }
+ }
+ });
+
+ // Make sure a layout really happens.
+ mView.requestLayout();
+ }
+
+ public void instantCollapse() {
+ abortAnimations();
+ setExpandedFraction(0f);
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ if (mInstantExpanding) {
+ mInstantExpanding = false;
+ updatePanelExpansionAndVisibility();
+ }
+ }
+
+ private void abortAnimations() {
+ cancelHeightAnimator();
+ mView.removeCallbacks(mFlingCollapseRunnable);
+ }
+
+ protected abstract void onClosingFinished();
+
+ protected void startUnlockHintAnimation() {
+
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHeightAnimator != null || mTracking) {
+ return;
+ }
+ notifyExpandingStarted();
+ startUnlockHintAnimationPhase1(() -> {
+ notifyExpandingFinished();
+ onUnlockHintFinished();
+ mHintAnimationRunning = false;
+ });
+ onUnlockHintStarted();
+ mHintAnimationRunning = true;
+ }
+
+ protected void onUnlockHintFinished() {
+ mCentralSurfaces.onHintFinished();
+ }
+
+ protected void onUnlockHintStarted() {
+ mCentralSurfaces.onUnlockHintStarted();
+ }
+
+ public boolean isUnlockHintRunning() {
+ return mHintAnimationRunning;
+ }
+
+ /**
+ * Phase 1: Move everything upwards.
+ */
+ private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+ float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+ ValueAnimator animator = createHeightAnimator(target);
+ animator.setDuration(250);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ } else {
+ startUnlockHintAnimationPhase2(onAnimationFinished);
+ }
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+
+ final List<ViewPropertyAnimator> indicationAnimators =
+ mKeyguardBottomArea.getIndicationAreaAnimators();
+ for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+ indicationAreaAnimator
+ .translationY(-mHintDistance)
+ .setDuration(250)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> indicationAreaAnimator
+ .translationY(0)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start())
+ .start();
+ }
+ }
+
+ private void setAnimator(ValueAnimator animator) {
+ mHeightAnimator = animator;
+ if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ requestPanelHeightUpdate();
+ }
+ }
+
+ /**
+ * Phase 2: Bounce down.
+ */
+ private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+ ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+ animator.setDuration(450);
+ animator.setInterpolator(mBounceInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ updatePanelExpansionAndVisibility();
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+ }
+
+ private ValueAnimator createHeightAnimator(float targetHeight) {
+ return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
+ }
+
+ /**
+ * Create an animator that can also overshoot
+ *
+ * @param targetHeight the target height
+ * @param overshootAmount the amount of overshoot desired
+ */
+ private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
+ float startExpansion = mOverExpansion;
+ ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+ animator.addUpdateListener(
+ animation -> {
+ if (overshootAmount > 0.0f
+ // Also remove the overExpansion when collapsing
+ || (targetHeight == 0.0f && startExpansion != 0)) {
+ final float expansion = MathUtils.lerp(
+ startExpansion,
+ mPanelFlingOvershootAmount * overshootAmount,
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ animator.getAnimatedFraction()));
+ setOverExpansionInternal(expansion, false /* isFromGesture */);
+ }
+ setExpandedHeightInternal((float) animation.getAnimatedValue());
+ });
+ return animator;
+ }
+
+ /** Update the visibility of {@link NotificationPanelView} if necessary. */
+ public void updateVisibility() {
+ mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+ }
+
+ /** Returns true if {@link NotificationPanelView} should be visible. */
+ abstract protected boolean shouldPanelBeVisible();
+
+ /**
+ * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary.
+ *
+ * TODO(b/200063118): Could public calls to this method be replaced with calls to
+ * {@link #updateVisibility()}? That would allow us to make this method private.
+ */
+ public void updatePanelExpansionAndVisibility() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ updateVisibility();
+ }
+
+ public boolean isExpanded() {
+ return mExpandedFraction > 0f
+ || mInstantExpanding
+ || isPanelVisibleBecauseOfHeadsUp()
+ || mTracking
+ || mHeightAnimator != null
+ && !mIsSpringBackAnimation;
+ }
+
+ protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
+
+ /**
+ * Gets called when the user performs a click anywhere in the empty area of the panel.
+ *
+ * @return whether the panel will be expanded after the action performed by this method
+ */
+ protected boolean onEmptySpaceClick() {
+ if (mHintAnimationRunning) {
+ return true;
+ }
+ return onMiddleClicked();
+ }
+
+ protected abstract boolean onMiddleClicked();
+
+ protected abstract boolean isDozing();
+
+ public void dump(PrintWriter pw, String[] args) {
+ pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+ + " tracking=%s timeAnim=%s%s "
+ + "touchDisabled=%s" + "]",
+ this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
+ mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
+ ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
+ mTouchDisabled ? "T" : "f"));
+ }
+
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ public void setIsLaunchAnimationRunning(boolean running) {
+ mIsLaunchAnimationRunning = running;
+ }
+
+ protected void setIsClosing(boolean isClosing) {
+ mClosing = isClosing;
+ }
+
+ protected boolean isClosing() {
+ return mClosing;
+ }
+
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
+
+ public ViewGroup getView() {
+ // TODO: remove this method, or at least reduce references to it.
+ return mView;
+ }
+
+ protected abstract OnLayoutChangeListener createLayoutChangeListener();
+
+ protected abstract TouchHandler createTouchHandler();
+
+ protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ return new OnConfigurationChangedListener();
+ }
+
+ public class TouchHandler implements View.OnTouchListener {
+
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ return false;
+ }
+
+ /*
+ * If the user drags anywhere inside the panel we intercept it if the movement is
+ * upwards. This allows closing the shade from anywhere inside the panel.
+ *
+ * We only do this if the current content is scrolled to the bottom,
+ * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
+ * gesture
+ * possible.
+ */
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ boolean canCollapsePanel = canCollapsePanelOnTouch();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mCentralSurfaces.userActivity();
+ mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
+ mMinExpandHeight = 0.0f;
+ mDownTime = mSystemClock.uptimeMillis();
+ if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
+ cancelHeightAnimator();
+ mTouchSlopExceeded = true;
+ return true;
+ }
+ mInitialExpandY = y;
+ mInitialExpandX = x;
+ mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+ mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+ mMotionAborted = false;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mCollapsedAndHeadsUpOnDown = false;
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mTouchAboveFalsingThreshold = false;
+ addMovement(event);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialExpandX = event.getX(newIndex);
+ mInitialExpandY = event.getY(newIndex);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ mVelocityTracker.clear();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialExpandY;
+ addMovement(event);
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
+ float hAbs = Math.abs(h);
+ float touchSlop = getTouchSlop(event);
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
+ && hAbs > Math.abs(x - mInitialExpandX)) {
+ cancelHeightAnimator();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mVelocityTracker.clear();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mInstantExpanding) {
+ mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+ return false;
+ }
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+ return false;
+ }
+ if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
+ return false;
+ }
+
+ // If dragging should not expand the notifications shade, then return false.
+ if (!mNotificationsDragEnabled) {
+ if (mTracking) {
+ // Turn off tracking if it's on or the shade can get stuck in the down position.
+ onTrackingStopped(true /* expand */);
+ }
+ mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
+ return false;
+ }
+
+ // On expanding, single mouse click expands the panel instead of dragging.
+ if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true);
+ }
+ return true;
+ }
+
+ /*
+ * We capture touch events here and update the expand height here in case according to
+ * the users fingers. This also handles multi-touch.
+ *
+ * Flinging is also enabled in order to open or close the shade.
+ */
+
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+ mIgnoreXTouchSlop = true;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ mMinExpandHeight = 0.0f;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
+ mDownTime = mSystemClock.uptimeMillis();
+ mTouchAboveFalsingThreshold = false;
+ mCollapsedAndHeadsUpOnDown =
+ isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
+ addMovement(event);
+ boolean regularHeightAnimationRunning = mHeightAnimator != null
+ && !mHintAnimationRunning && !mIsSpringBackAnimation;
+ if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
+ mTouchSlopExceeded = regularHeightAnimationRunning
+ || mTouchSlopExceededBeforeDown;
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+ && !mCentralSurfaces.isBouncerShowing()) {
+ startOpening(event);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ mHandlingPointerUp = true;
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ mHandlingPointerUp = false;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ addMovement(event);
+ float h = y - mInitialExpandY;
+
+ // If the panel was collapsed when touching, we only need to check for the
+ // y-component of the gesture, as we have no conflicting horizontal gesture.
+ if (Math.abs(h) > getTouchSlop(event)
+ && (Math.abs(h) > Math.abs(x - mInitialExpandX)
+ || mIgnoreXTouchSlop)) {
+ mTouchSlopExceeded = true;
+ if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+ if (mInitialOffsetOnTouch != 0f) {
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ h = 0;
+ }
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ }
+ float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+ newHeight = Math.max(newHeight, mMinExpandHeight);
+ if (-h >= getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
+ }
+ if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+ // Count h==0 as part of swipe-up,
+ // otherwise {@link NotificationStackScrollLayout}
+ // wrongly enables stack height updates at the start of lockscreen swipe-up
+ mAmbientState.setSwipingUp(h <= 0);
+ setExpandedHeightInternal(newHeight);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ addMovement(event);
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
+ if (mHeightAnimator == null) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ endJankMonitoring();
+ } else {
+ cancelJankMonitoring();
+ }
+ }
+ break;
+ }
+ return !mGestureWaitForTouchSlop || mTracking;
+ }
+ }
+
+ protected abstract class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ requestPanelHeightUpdate();
+ mHasLayoutedSinceDown = true;
+ if (mUpdateFlingOnLayout) {
+ abortAnimations();
+ fling(mUpdateFlingVelocity, true /* expands */);
+ mUpdateFlingOnLayout = false;
+ }
+ }
+ }
+
+ public class OnConfigurationChangedListener implements
+ NotificationPanelView.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ loadDimens();
+ }
+ }
+
+ private void beginJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mView)
+ .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
+ mInteractionJankMonitor.begin(builder);
+ }
+
+ private void endJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ private void cancelJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ protected float getExpansionFraction() {
+ return mExpandedFraction;
+ }
+
+ protected PanelExpansionStateManager getPanelExpansionStateManager() {
+ return mPanelExpansionStateManager;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 87f8a03..47dc5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -19,9 +19,12 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -428,9 +431,9 @@
if (info == null) {
// Use the current user owner information if enabled.
final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
- KeyguardUpdateMonitor.getCurrentUser());
+ getCurrentUser());
if (ownerInfoEnabled) {
- info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+ info = mLockPatternUtils.getOwnerInfo(getCurrentUser());
}
}
@@ -595,7 +598,7 @@
private void updateLockScreenLogoutView() {
final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
- && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ && getCurrentUser() != UserHandle.USER_SYSTEM;
if (shouldShowLogout) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_LOGOUT,
@@ -610,7 +613,7 @@
if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
return;
}
- int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ int currentUserId = getCurrentUser();
mDevicePolicyManager.logoutUser();
})
.build(),
@@ -767,7 +770,7 @@
mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
hideBiometricMessageDelayed(
mBiometricMessageFollowUp != null
- ? DEFAULT_HIDE_DELAY_MS * 2
+ ? IMPORTANT_MSG_MIN_DURATION * 2
: DEFAULT_HIDE_DELAY_MS
);
@@ -847,7 +850,7 @@
mTopIndicationView.setVisibility(GONE);
mTopIndicationView.setText(null);
mLockScreenIndicationView.setVisibility(View.VISIBLE);
- updateLockScreenIndications(animate, KeyguardUpdateMonitor.getCurrentUser());
+ updateLockScreenIndications(animate, getCurrentUser());
}
protected String computePowerIndication() {
@@ -915,7 +918,7 @@
public void showActionToUnlock() {
if (mDozing
&& !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ getCurrentUser())) {
return;
}
@@ -928,7 +931,7 @@
}
} else {
final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser());
+ getCurrentUser());
if (canSkipBouncer) {
final boolean faceAuthenticated = mKeyguardUpdateMonitor.getIsFaceAuthenticated();
final boolean udfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
@@ -1045,12 +1048,15 @@
return;
}
- boolean showActionToUnlock =
- msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
- if (biometricSourceType == BiometricSourceType.FACE
- && !showActionToUnlock
- && mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())
+ final boolean faceAuthSoftError = biometricSourceType == FACE
+ && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+ final boolean faceAuthFailed = biometricSourceType == FACE
+ && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
+ final boolean isUnlockWithFingerprintPossible =
+ mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+ getCurrentUser());
+ if (faceAuthSoftError
+ && isUnlockWithFingerprintPossible
&& !mCoExFaceHelpMsgIdsToShow.contains(msgId)) {
if (DEBUG) {
Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString
@@ -1061,8 +1067,16 @@
mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
mInitialTextColorState);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
- showBiometricMessage(helpString);
- } else if (showActionToUnlock) {
+ if (faceAuthFailed && isUnlockWithFingerprintPossible) {
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_failed),
+ mContext.getString(R.string.keyguard_suggest_fingerprint)
+ );
+ } else {
+ showBiometricMessage(helpString);
+ }
+ } else if (faceAuthFailed) {
+ // show action to unlock
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
} else {
@@ -1080,17 +1094,17 @@
return;
}
- if (biometricSourceType == BiometricSourceType.FACE
+ if (biometricSourceType == FACE
&& msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
// suppress all face UNABLE_TO_PROCESS errors
if (DEBUG) {
Log.d(TAG, "skip showing FACE_ERROR_UNABLE_TO_PROCESS errString="
+ errString);
}
- } else if (biometricSourceType == BiometricSourceType.FACE
+ } else if (biometricSourceType == FACE
&& msgId == FaceManager.FACE_ERROR_TIMEOUT) {
if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ getCurrentUser())) {
// no message if fingerprint is also enrolled
if (DEBUG) {
Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
@@ -1122,8 +1136,9 @@
BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT)
return shouldSuppressFingerprintError(msgId, updateMonitor);
- if (biometricSourceType == BiometricSourceType.FACE)
+ if (biometricSourceType == FACE) {
return shouldSuppressFaceError(msgId, updateMonitor);
+ }
return false;
}
@@ -1152,7 +1167,7 @@
@Override
public void onTrustChanged(int userId) {
- if (KeyguardUpdateMonitor.getCurrentUser() != userId) {
+ if (getCurrentUser() != userId) {
return;
}
updateDeviceEntryIndication(false);
@@ -1172,7 +1187,7 @@
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
- if (running && biometricSourceType == BiometricSourceType.FACE) {
+ if (running && biometricSourceType == FACE) {
// Let's hide any previous messages when authentication starts, otherwise
// multiple auth attempts would overlap.
hideBiometricMessage();
@@ -1186,7 +1201,7 @@
super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
hideBiometricMessage();
- if (biometricSourceType == BiometricSourceType.FACE
+ if (biometricSourceType == FACE
&& !mKeyguardBypassController.canBypass()) {
showActionToUnlock();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 169347a..16306081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -49,6 +49,7 @@
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -79,7 +80,7 @@
@VisibleForTesting
UserAvatarView mUserAvatarView;
private View mUserAvatarViewWithBackground;
- UserSwitcherController.UserRecord mCurrentUser;
+ UserRecord mCurrentUser;
private boolean mIsKeyguardShowing;
// State info for the user switch and keyguard
@@ -269,10 +270,10 @@
* @return true if the current user has changed
*/
private boolean updateCurrentUser() {
- UserSwitcherController.UserRecord previousUser = mCurrentUser;
+ UserRecord previousUser = mCurrentUser;
mCurrentUser = null;
for (int i = 0; i < mAdapter.getCount(); i++) {
- UserSwitcherController.UserRecord r = mAdapter.getItem(i);
+ UserRecord r = mAdapter.getItem(i);
if (r.isCurrent) {
mCurrentUser = r;
return !mCurrentUser.equals(previousUser);
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 03ab888..e2f5734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
@@ -287,8 +288,8 @@
}
KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
mAdapter.getView(i, oldView, mListView);
- UserSwitcherController.UserRecord userTag =
- (UserSwitcherController.UserRecord) newView.getTag();
+ UserRecord userTag =
+ (UserRecord) newView.getTag();
if (userTag.isCurrent) {
if (i != 0) {
Log.w(TAG, "Current user is not the first view in the list");
@@ -443,7 +444,7 @@
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private View mCurrentUserView;
// List of users where the first entry is always the current user
- private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+ private ArrayList<UserRecord> mUsersOrdered = new ArrayList<>();
KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
UserSwitcherController controller,
@@ -464,10 +465,10 @@
}
void refreshUserOrder() {
- ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+ ArrayList<UserRecord> users = super.getUsers();
mUsersOrdered = new ArrayList<>(users.size());
for (int i = 0; i < users.size(); i++) {
- UserSwitcherController.UserRecord record = users.get(i);
+ UserRecord record = users.get(i);
if (record.isCurrent) {
mUsersOrdered.add(0, record);
} else {
@@ -477,19 +478,19 @@
}
@Override
- protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+ protected ArrayList<UserRecord> getUsers() {
return mUsersOrdered;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- UserSwitcherController.UserRecord item = getItem(position);
+ UserRecord item = getItem(position);
return createUserDetailItemView(convertView, parent, item);
}
KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
if (!(convertView instanceof KeyguardUserDetailItemView)
- || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+ || !(convertView.getTag() instanceof UserRecord)) {
convertView = mLayoutInflater.inflate(
R.layout.keyguard_user_switcher_item, parent, false);
}
@@ -497,7 +498,7 @@
}
KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
- UserSwitcherController.UserRecord item) {
+ UserRecord item) {
KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
v.setOnClickListener(this);
@@ -513,7 +514,7 @@
v.bind(name, drawable, item.info.id);
}
v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(item.isDisabledByAdmin);
+ v.setDisabledByAdmin(mController.isDisabledByAdmin(item));
v.setEnabled(item.isSwitchToEnabled);
v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
@@ -524,7 +525,7 @@
return v;
}
- private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+ private Drawable getDrawable(UserRecord item) {
Drawable drawable;
if (item.isCurrent && item.isGuest) {
drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
@@ -547,7 +548,7 @@
@Override
public void onClick(View v) {
- UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+ UserRecord user = (UserRecord) v.getTag();
if (mKeyguardUserSwitcherController.isListAnimating()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 836d571..e2d1601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -45,6 +45,7 @@
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;
@@ -54,6 +55,7 @@
import android.widget.Toast;
import androidx.annotation.Nullable;
+import androidx.collection.SimpleArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
@@ -83,6 +85,7 @@
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.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -138,6 +141,9 @@
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
@@ -975,6 +981,21 @@
return mKeyguardStateController;
}
+ /**
+ * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one.
+ */
+ @Nullable
+ public EnforcedAdmin getEnforcedAdmin(UserRecord record) {
+ return mEnforcedAdminByUserRecord.get(record);
+ }
+
+ /**
+ * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise.
+ */
+ public boolean isDisabledByAdmin(UserRecord record) {
+ return mDisabledByAdmin.contains(record);
+ }
+
public static abstract class BaseUserAdapter extends BaseAdapter {
final UserSwitcherController mController;
@@ -1106,11 +1127,11 @@
UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) {
- record.isDisabledByAdmin = true;
- record.enforcedAdmin = admin;
+ mDisabledByAdmin.add(record);
+ mEnforcedAdminByUserRecord.put(record, admin);
} else {
- record.isDisabledByAdmin = false;
- record.enforcedAdmin = null;
+ mDisabledByAdmin.remove(record);
+ mEnforcedAdminByUserRecord.put(record, null);
}
}
@@ -1152,74 +1173,6 @@
}
}
- public static final class UserRecord {
- public final UserInfo info;
- public final Bitmap picture;
- public final boolean isGuest;
- public final boolean isCurrent;
- public final boolean isAddUser;
- public final boolean isAddSupervisedUser;
- /** If true, the record is only visible to the owner and only when unlocked. */
- public final boolean isRestricted;
- public boolean isDisabledByAdmin;
- public EnforcedAdmin enforcedAdmin;
- public boolean isSwitchToEnabled;
-
- public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
- boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled,
- boolean isAddSupervisedUser) {
- this.info = info;
- this.picture = picture;
- this.isGuest = isGuest;
- this.isCurrent = isCurrent;
- this.isAddUser = isAddUser;
- this.isRestricted = isRestricted;
- this.isSwitchToEnabled = isSwitchToEnabled;
- this.isAddSupervisedUser = isAddSupervisedUser;
- }
-
- public UserRecord copyWithIsCurrent(boolean _isCurrent) {
- return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted,
- isSwitchToEnabled, isAddSupervisedUser);
- }
-
- public int resolveId() {
- if (isGuest || info == null) {
- return UserHandle.USER_NULL;
- }
- return info.id;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("UserRecord(");
- if (info != null) {
- sb.append("name=\"").append(info.name).append("\" id=").append(info.id);
- } else {
- if (isGuest) {
- sb.append("<add guest placeholder>");
- } else if (isAddUser) {
- sb.append("<add user placeholder>");
- }
- }
- if (isGuest) sb.append(" <isGuest>");
- if (isAddUser) sb.append(" <isAddUser>");
- if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>");
- if (isCurrent) sb.append(" <isCurrent>");
- if (picture != null) sb.append(" <hasPicture>");
- if (isRestricted) sb.append(" <isRestricted>");
- if (isDisabledByAdmin) {
- sb.append(" <isDisabledByAdmin>");
- sb.append(" enforcedAdmin=").append(enforcedAdmin);
- }
- if (isSwitchToEnabled) {
- sb.append(" <isSwitchToEnabled>");
- }
- sb.append(')');
- return sb.toString();
- }
- }
-
private final KeyguardStateController.Callback mCallback =
new KeyguardStateController.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 80c55c0..ff0f0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -51,7 +51,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
-import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
+import com.android.systemui.user.data.source.UserRecord
import javax.inject.Inject
import kotlin.math.ceil
@@ -81,16 +81,17 @@
}
}
// When the add users options become available, insert another option to manage users
- private val manageUserRecord = UserRecord(
- null /* info */,
- null /* picture */,
- false /* isGuest */,
- false /* isCurrent */,
- false /* isAddUser */,
- false /* isRestricted */,
- false /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
+ private val manageUserRecord =
+ UserRecord(
+ null /* info */,
+ null /* picture */,
+ false /* isGuest */,
+ false /* isCurrent */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ false /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */
+ )
private val adapter = object : BaseUserAdapter(userSwitcherController) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
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
new file mode 100644
index 0000000..6ab6d7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.source
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserHandle
+
+/**
+ * Encapsulates raw data for a user or an option item related to managing users on the device.
+ */
+data class UserRecord(
+ /** Relevant user information. If `null`, this record is not a user but an option item. */
+ @JvmField
+ val info: UserInfo?,
+ /** An image representing the user. */
+ @JvmField
+ val picture: Bitmap?,
+ /** Whether this record represents an option to switch to a guest user. */
+ @JvmField
+ val isGuest: Boolean,
+ /** Whether this record represents the currently-selected user. */
+ @JvmField
+ val isCurrent: Boolean,
+ /** Whether this record represents an option to add another user to the device. */
+ @JvmField
+ val isAddUser: Boolean,
+ /** If true, the record is only visible to the owner and only when unlocked. */
+ @JvmField
+ val isRestricted: Boolean,
+ /** Whether it is possible to switch to this user. */
+ @JvmField
+ val isSwitchToEnabled: Boolean,
+ /** Whether this record represents an option to add another supervised user to the device. */
+ @JvmField
+ val isAddSupervisedUser: Boolean,
+) {
+ /**
+ * Returns a new instance of [UserRecord] with its [isCurrent] set to the given value.
+ */
+ fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
+ return copy(isCurrent = isCurrent)
+ }
+
+ /**
+ * Returns the user ID for the user represented by this instance or [UserHandle.USER_NULL] if
+ * this instance if a guest or does not represent a user (represents an option item).
+ */
+ fun resolveId(): Int {
+ return if (isGuest || info == null) {
+ UserHandle.USER_NULL
+ } else {
+ info.id
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index f2ac0c7..28e99da 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -57,7 +57,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord;
+import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.settings.GlobalSettings;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 9b0142d..5db3b9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -39,8 +40,8 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -140,13 +141,14 @@
}
private fun createUserRecord(current: Boolean, guest: Boolean) =
- UserSwitcherController.UserRecord(
- mUserInfo,
- mPicture,
- guest,
- current,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ UserRecord(
+ mUserInfo,
+ mPicture,
+ guest,
+ current,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 351dd0c..05692b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -570,6 +570,33 @@
}
@Test
+ public void onBiometricHelp_coEx_faceFailure() {
+ createController();
+
+ // GIVEN unlocking with fingerprint is possible
+ when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
+ .thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a face not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FACE);
+
+ // THEN show sequential messages such as: 'face not recognized' and
+ // 'try fingerprint instead'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_failed));
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
createController();
String message = mContext.getString(R.string.keyguard_unlock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index 0dd6cbb7..c3805ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.tiles.UserDetailItemView
+import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
@@ -38,8 +39,8 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -186,13 +187,14 @@
}
private fun createUserRecord(isCurrentUser: Boolean, isGuestUser: Boolean) =
- UserSwitcherController.UserRecord(
- userInfo,
- picture,
- isGuestUser,
- isCurrentUser,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ UserRecord(
+ userInfo,
+ picture,
+ isGuestUser,
+ isCurrentUser,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 359a780..8dcd4bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -56,6 +56,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -235,15 +236,16 @@
@Test
fun testSwitchUser_parentDialogDismissed() {
- val otherUserRecord = UserSwitcherController.UserRecord(
- secondaryUser,
- picture,
- false /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ val otherUserRecord = UserRecord(
+ secondaryUser,
+ picture,
+ false /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -255,7 +257,8 @@
@Test
fun testAddGuest_okButtonPressed() {
- val emptyGuestUserRecord = UserSwitcherController.UserRecord(
+ val emptyGuestUserRecord =
+ UserRecord(
null,
null,
true /* guest */,
@@ -263,7 +266,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -282,7 +286,8 @@
@Test
fun testAddGuest_parentDialogDismissed() {
- val emptyGuestUserRecord = UserSwitcherController.UserRecord(
+ val emptyGuestUserRecord =
+ UserRecord(
null,
null,
true /* guest */,
@@ -290,7 +295,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -305,7 +311,8 @@
@Test
fun testRemoveGuest_removeButtonPressed_isLogged() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -313,7 +320,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -331,7 +339,8 @@
@Test
fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -339,7 +348,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -353,7 +363,8 @@
@Test
fun testRemoveGuest_dialogShowerUsed() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -361,7 +372,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -376,7 +388,8 @@
@Test
fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -384,7 +397,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -398,7 +412,8 @@
@Test
fun testWipeGuest_startOverButtonPressed_isLogged() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -406,7 +421,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -433,7 +449,8 @@
@Test
fun testWipeGuest_continueButtonPressed_isLogged() {
- val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ val currentGuestUserRecord =
+ UserRecord(
guestInfo,
picture,
true /* guest */,
@@ -441,7 +458,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -470,11 +488,13 @@
@Test
fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
fun addUser(id: Int, name: String, isCurrent: Boolean) {
- userSwitcherController.users.add(UserSwitcherController.UserRecord(
+ userSwitcherController.users.add(
+ UserRecord(
UserInfo(id, name, 0),
null, false, isCurrent, false,
false, false, false
- ))
+ )
+ )
}
val bgUserName = "background_user"
val fgUserName = "foreground_user"
@@ -593,7 +613,7 @@
@Test
fun onUserItemClicked_guest_runsOnBgThread() {
val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
- val guestUserRecord = UserSwitcherController.UserRecord(
+ val guestUserRecord = UserRecord(
null,
picture,
true /* guest */,
@@ -601,7 +621,8 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */)
+ false /* isAddSupervisedUser */
+ )
userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
assertTrue(bgExecutor.numPending() > 0)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd776c1..2cf24fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8748,12 +8748,40 @@
if (process.info.isInstantApp()) {
sb.append("Instant-App: true\n");
}
+
if (isSdkSandboxUid(process.uid)) {
+ final int appUid = Process.getAppUidForSdkSandboxUid(process.uid);
+ try {
+ String[] clientPackages = pm.getPackagesForUid(appUid);
+ // In shared UID case, don't add the package information
+ if (clientPackages.length == 1) {
+ appendSdkSandboxClientPackageHeader(sb, clientPackages[0], callingUserId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error getting packages for client app uid: " + appUid, e);
+ }
sb.append("SdkSandbox: true\n");
}
}
}
+ private void appendSdkSandboxClientPackageHeader(StringBuilder sb, String pkg, int userId) {
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ sb.append("SdkSandbox-Client-Package: ").append(pkg);
+ try {
+ final PackageInfo pi = pm.getPackageInfo(pkg, 0, userId);
+ if (pi != null) {
+ sb.append(" v").append(pi.getLongVersionCode());
+ if (pi.versionName != null) {
+ sb.append(" (").append(pi.versionName).append(")");
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error getting package info for SDK sandbox client: " + pkg, e);
+ }
+ sb.append("\n");
+ }
+
private static String processClass(ProcessRecord process) {
if (process == null || process.getPid() == MY_PID) {
return "system_server";
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ccbca76..48eb0de 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -133,7 +133,6 @@
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ProcessChangeItem;
import com.android.server.compat.PlatformCompat;
-import com.android.server.pm.dex.DexManager;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -1790,14 +1789,6 @@
if (app.info.isEmbeddedDexUsed()) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
- } else if (app.info.isPrivilegedApp()) {
- final PackageList pkgList = app.getPkgList();
- synchronized (pkgList) {
- if (DexManager.isPackageSelectedToRunOob(
- pkgList.getPackageListLocked().keySet())) {
- runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
- }
- }
}
if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index b7e817e..c143675 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1115,6 +1115,9 @@
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
+ if (enabled && !mHeadTrackerAvailable) {
+ postInitSensors();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
new file mode 100644
index 0000000..e9640cf
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -0,0 +1,55 @@
+/*
+ * 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.display;
+
+import android.os.IBinder;
+
+import java.util.Objects;
+
+/**
+ * Calls into SurfaceFlinger for Display creation and deletion.
+ */
+public class DisplayControl {
+ private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native void nativeDestroyDisplay(IBinder displayToken);
+
+ /**
+ * Create a display in SurfaceFlinger.
+ *
+ * @param name The name of the display
+ * @param secure Whether this display is secure.
+ * @return The token reference for the display in SurfaceFlinger.
+ */
+ public static IBinder createDisplay(String name, boolean secure) {
+ Objects.requireNonNull(name, "name must not be null");
+ return nativeCreateDisplay(name, secure);
+ }
+
+ /**
+ * Destroy a display in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display to be destroyed.
+ */
+ public static void destroyDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ nativeDestroyDisplay(displayToken);
+ }
+
+}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 330379c..b0de844 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -305,7 +305,7 @@
mSurface.release();
mSurface = null;
}
- SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+ DisplayControl.destroyDisplay(getDisplayTokenLocked());
}
@Override
@@ -460,7 +460,7 @@
public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
long presentationDeadlineNanos, int state) {
synchronized (getSyncRoot()) {
- IBinder displayToken = SurfaceControl.createDisplay(mName, mFlags.mSecure);
+ IBinder displayToken = DisplayControl.createDisplay(mName, mFlags.mSecure);
mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
mFlags, state, surfaceTexture, mNumber) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 479629e..38728ce 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -82,8 +82,7 @@
// Called with SyncRoot lock held.
public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
- this(syncRoot, context, handler, listener,
- (String name, boolean secure) -> SurfaceControl.createDisplay(name, secure));
+ this(syncRoot, context, handler, listener, DisplayControl::createDisplay);
}
@VisibleForTesting
@@ -296,7 +295,7 @@
mSurface.release();
mSurface = null;
}
- SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+ DisplayControl.destroyDisplay(getDisplayTokenLocked());
if (mProjection != null && mMediaProjectionCallback != null) {
try {
mProjection.unregisterCallback(mMediaProjectionCallback);
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index d632ee3..146b003 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -388,7 +388,7 @@
String name = display.getFriendlyDisplayName();
String address = display.getDeviceAddress();
- IBinder displayToken = SurfaceControl.createDisplay(name, secure);
+ IBinder displayToken = DisplayControl.createDisplay(name, secure);
mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
refreshRate, deviceFlags, address, surface);
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
@@ -621,7 +621,7 @@
mSurface.release();
mSurface = null;
}
- SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+ DisplayControl.destroyDisplay(getDisplayTokenLocked());
}
public void setNameLocked(String name) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index de83f5a..d770f71 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -123,6 +123,7 @@
import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_NOTIFICATION_BOOL;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -3158,7 +3159,8 @@
* active merge set [A,B], we'd return a new template that primarily matches
* A, but also matches B.
*/
- private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
+ @VisibleForTesting(visibility = PRIVATE)
+ static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
@NonNull List<String[]> mergedList) {
// Now there are several types of network which uses Subscriber Id to store network
// information. For instance:
@@ -3168,6 +3170,12 @@
if (template.getSubscriberIds().isEmpty()) return template;
for (final String[] merged : mergedList) {
+ // In some rare cases (e.g. b/243015487), merged subscriberId list might contain
+ // duplicated items. Deduplication for better error handling.
+ final ArraySet mergedSet = new ArraySet(merged);
+ if (mergedSet.size() != merged.length) {
+ Log.wtf(TAG, "Duplicated merged list detected: " + Arrays.toString(merged));
+ }
// TODO: Handle incompatible subscriberIds if that happens in practice.
for (final String subscriberId : template.getSubscriberIds()) {
if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) {
@@ -3175,7 +3183,7 @@
// a template that matches all merged subscribers.
return new NetworkTemplate.Builder(template.getMatchRule())
.setWifiNetworkKeys(template.getWifiNetworkKeys())
- .setSubscriberIds(Set.of(merged))
+ .setSubscriberIds(mergedSet)
.setMeteredness(template.getMeteredness())
.build();
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 5a40b30..0fac808 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1786,8 +1786,8 @@
* from receiving events from the profile.
*/
public boolean isPermittedForProfile(int userId) {
- if (!mUserProfiles.isManagedProfile(userId)) {
- return true;
+ if (!mUserProfiles.canProfileUseBoundServices(userId)) {
+ return false;
}
DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE);
@@ -1862,10 +1862,16 @@
}
}
- public boolean isManagedProfile(int userId) {
+ public boolean canProfileUseBoundServices(int userId) {
synchronized (mCurrentProfiles) {
UserInfo user = mCurrentProfiles.get(userId);
- return user != null && user.isManagedProfile();
+ if (user == null) {
+ return false;
+ }
+ if (user.isManagedProfile() || user.isCloneProfile()) {
+ return false;
+ }
+ return true;
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aa2a25b..9f525d5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -304,6 +304,7 @@
import com.android.server.notification.toast.TextToastRecord;
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -533,6 +534,7 @@
private UriGrantsManagerInternal mUgmInternal;
private volatile RoleObserver mRoleObserver;
private UserManager mUm;
+ private UserManagerInternal mUmInternal;
private IPlatformCompat mPlatformCompat;
private ShortcutHelper mShortcutHelper;
private PermissionHelper mPermissionHelper;
@@ -819,7 +821,8 @@
final List<UserInfo> activeUsers = mUm.getUsers();
for (UserInfo userInfo : activeUsers) {
int userId = userInfo.getUserHandle().getIdentifier();
- if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) {
+ if (isNASMigrationDone(userId)
+ || userInfo.isManagedProfile() || userInfo.isCloneProfile()) {
continue;
}
List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
@@ -950,7 +953,9 @@
}
XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);
boolean migratedManagedServices = false;
- boolean ineligibleForManagedServices = forRestore && mUm.isManagedProfile(userId);
+ UserInfo userInfo = mUmInternal.getUserInfo(userId);
+ boolean ineligibleForManagedServices = forRestore &&
+ (userInfo.isManagedProfile() || userInfo.isCloneProfile());
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
@@ -1824,7 +1829,7 @@
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
- if (!mUserProfiles.isManagedProfile(userId)) {
+ if (mUserProfiles.canProfileUseBoundServices(userId)) {
// reload per-user settings
mSettingsObserver.update(null);
// Refresh managed services
@@ -1838,7 +1843,7 @@
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
if (userId != USER_NULL) {
mUserProfiles.updateCache(context);
- if (!mUserProfiles.isManagedProfile(userId)) {
+ if (mUserProfiles.canProfileUseBoundServices(userId)) {
allowDefaultApprovedServices(userId);
}
}
@@ -1856,7 +1861,7 @@
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
mAssistants.onUserUnlocked(userId);
- if (!mUserProfiles.isManagedProfile(userId)) {
+ if (mUserProfiles.canProfileUseBoundServices(userId)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
mZenModeHelper.onUserUnlocked(userId);
@@ -2218,6 +2223,7 @@
mPackageManagerClient = packageManagerClient;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
+ mUmInternal = LocalServices.getService(UserManagerInternal.class);
mUsageStatsManagerInternal = usageStatsManagerInternal;
mAppOps = appOps;
mAppOpsService = iAppOps;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6cfe093..4b6543b 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -74,7 +74,6 @@
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.ArtStatsLogUtils;
import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
-import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptUtils;
import com.android.server.pm.dex.PackageDexUsage;
@@ -787,10 +786,7 @@
*/
private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
boolean isUsedByOtherApps) {
- // When an app or priv app is configured to run out of box, only verify it.
- if (info.isEmbeddedDexUsed()
- || (info.isPrivilegedApp()
- && DexManager.isPackageSelectedToRunOob(info.packageName))) {
+ if (info.isEmbeddedDexUsed()) {
return "verify";
}
@@ -827,10 +823,7 @@
* handling the case where the package code is used by other apps.
*/
private String getRealCompilerFilter(AndroidPackage pkg, String targetCompilerFilter) {
- // When an app or priv app is configured to run out of box, only verify it.
- if (pkg.isUseEmbeddedDex()
- || (pkg.isPrivileged()
- && DexManager.isPackageSelectedToRunOob(pkg.getPackageName()))) {
+ if (pkg.isUseEmbeddedDex()) {
return "verify";
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 17109e9..8c2da45 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -36,7 +36,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.Log;
@@ -58,8 +57,6 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -81,10 +78,6 @@
private static final String TAG = "DexManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
- private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
- "pm.dexopt.priv-apps-oob-list";
-
// System server cannot load executable code outside system partitions.
// However it can load verification data - thus we pick the "verify" compiler filter.
private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify";
@@ -910,45 +903,6 @@
}
/**
- * Returns whether the given package is in the list of privilaged apps that should run out of
- * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
- * empty, all priv apps will run in OOB mode.
- */
- public static boolean isPackageSelectedToRunOob(String packageName) {
- return isPackageSelectedToRunOob(Arrays.asList(packageName));
- }
-
- /**
- * Returns whether any of the given packages are in the list of privilaged apps that should run
- * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
- * is empty, all priv apps will run in OOB mode.
- */
- public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
- return isPackageSelectedToRunOobInternal(
- SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
- SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
- packageNamesInSameProcess);
- }
-
- @VisibleForTesting
- /* package */ static boolean isPackageSelectedToRunOobInternal(boolean isEnabled,
- String whitelist, Collection<String> packageNamesInSameProcess) {
- if (!isEnabled) {
- return false;
- }
-
- if ("ALL".equals(whitelist)) {
- return true;
- }
- for (String oobPkgName : whitelist.split(",")) {
- if (packageNamesInSameProcess.contains(oobPkgName)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Generates log if the archive located at {@code fileName} has uncompressed dex file that can
* be direclty mapped.
*/
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 79fe905..1bb476f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -917,7 +917,8 @@
}
} else {
// handled by another power key policy.
- if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
+ if (mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
+ Slog.d(TAG, "Skip power key gesture for other policy has handled it.");
mSingleKeyGestureDetector.reset();
}
}
@@ -931,11 +932,6 @@
// Abort possibly stuck animations only when power key up without long press case.
mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
}
- } else {
- // handled by single key or another power key policy.
- if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
- mSingleKeyGestureDetector.reset();
- }
}
finishPowerKeyPress();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2a1748c..9ee0df9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2953,7 +2953,8 @@
mHandler.removeMessages(MSG_ATTENTIVE_TIMEOUT);
- if (isBeingKeptFromInattentiveSleepLocked()) {
+ if (getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP
+ || isBeingKeptFromInattentiveSleepLocked()) {
return;
}
@@ -2985,7 +2986,7 @@
return false;
}
- if (getGlobalWakefulnessLocked() != WAKEFULNESS_AWAKE) {
+ if (getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
mInattentiveSleepWarningOverlayController.dismiss(false);
return true;
} else if (attentiveTimeout < 0 || isBeingKeptFromInattentiveSleepLocked()
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index df902c2..49ac559 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -663,6 +663,11 @@
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
reason, 0);
+ if (measuredEnergyDeltas != null && !measuredEnergyDeltas.isEmpty()) {
+ mStats.recordMeasuredEnergyDetailsLocked(elapsedRealtime, uptime,
+ mMeasuredEnergySnapshot.getMeasuredEnergyDetails(measuredEnergyDeltas));
+ }
+
if ((updateFlags & UPDATE_CPU) != 0) {
if (useLatestStates) {
onBattery = mStats.isOnBatteryLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 968f916..5fdd006 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -7404,6 +7404,16 @@
return names;
}
+ /**
+ * Adds measured energy delta to battery history.
+ */
+ @GuardedBy("this")
+ public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs,
+ long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) {
+ mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
+ measuredEnergyDetails);
+ }
+
@GuardedBy("this")
@Override public long getStartClockTime() {
final long currentTimeMs = mClock.currentTimeMillis();
diff --git a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java
index b55c799..c8b4e36 100644
--- a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java
@@ -23,19 +23,17 @@
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryStats.MeasuredEnergyDetails;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.io.PrintWriter;
/**
* Keeps snapshots of data from previously pulled EnergyConsumerResults.
*/
-@VisibleForTesting
public class MeasuredEnergySnapshot {
private static final String TAG = "MeasuredEnergySnapshot";
@@ -87,6 +85,8 @@
*/
private final SparseArray<SparseLongArray> mAttributionSnapshots;
+ private MeasuredEnergyDetails mMeasuredEnergyDetails;
+
/**
* Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
* exist and what their details are.
@@ -128,6 +128,28 @@
/** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->chargeUC} maps. */
public @Nullable SparseLongArray[] otherUidChargesUC = null;
+
+ boolean isEmpty() {
+ return bluetoothChargeUC <= 0
+ && isEmpty(cpuClusterChargeUC)
+ && isEmpty(displayChargeUC)
+ && gnssChargeUC <= 0
+ && mobileRadioChargeUC <= 0
+ && wifiChargeUC <= 0
+ && isEmpty(otherTotalChargeUC);
+ }
+
+ private boolean isEmpty(long[] values) {
+ if (values == null) {
+ return true;
+ }
+ for (long value: values) {
+ if (value > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
}
/**
@@ -394,4 +416,119 @@
// since the last snapshot. Round off to the nearest whole long.
return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
+
+ /**
+ * Converts the MeasuredEnergyDeltaData object to MeasuredEnergyDetails, which can
+ * be saved in battery history.
+ */
+ MeasuredEnergyDetails getMeasuredEnergyDetails(
+ MeasuredEnergySnapshot.MeasuredEnergyDeltaData delta) {
+ if (mMeasuredEnergyDetails == null) {
+ mMeasuredEnergyDetails = createMeasuredEnergyDetails();
+ }
+
+ final long[] chargeUC = mMeasuredEnergyDetails.chargeUC;
+ for (int i = 0; i < mMeasuredEnergyDetails.consumers.length; i++) {
+ MeasuredEnergyDetails.EnergyConsumer energyConsumer =
+ mMeasuredEnergyDetails.consumers[i];
+ switch (energyConsumer.type) {
+ case EnergyConsumerType.BLUETOOTH:
+ chargeUC[i] = delta.bluetoothChargeUC;
+ break;
+ case EnergyConsumerType.CPU_CLUSTER:
+ if (delta.cpuClusterChargeUC != null) {
+ chargeUC[i] = delta.cpuClusterChargeUC[energyConsumer.ordinal];
+ } else {
+ chargeUC[i] = UNAVAILABLE;
+ }
+ break;
+ case EnergyConsumerType.DISPLAY:
+ if (delta.displayChargeUC != null) {
+ chargeUC[i] = delta.displayChargeUC[energyConsumer.ordinal];
+ } else {
+ chargeUC[i] = UNAVAILABLE;
+ }
+ break;
+ case EnergyConsumerType.GNSS:
+ chargeUC[i] = delta.gnssChargeUC;
+ break;
+ case EnergyConsumerType.MOBILE_RADIO:
+ chargeUC[i] = delta.mobileRadioChargeUC;
+ break;
+ case EnergyConsumerType.WIFI:
+ chargeUC[i] = delta.wifiChargeUC;
+ break;
+ case EnergyConsumerType.OTHER:
+ if (delta.otherTotalChargeUC != null) {
+ chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
+ } else {
+ chargeUC[i] = UNAVAILABLE;
+ }
+ break;
+ default:
+ chargeUC[i] = UNAVAILABLE;
+ break;
+ }
+ }
+ return mMeasuredEnergyDetails;
+ }
+
+ private MeasuredEnergyDetails createMeasuredEnergyDetails() {
+ MeasuredEnergyDetails details = new MeasuredEnergyDetails();
+ details.consumers =
+ new MeasuredEnergyDetails.EnergyConsumer[mEnergyConsumers.size()];
+ for (int i = 0; i < mEnergyConsumers.size(); i++) {
+ EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i);
+ MeasuredEnergyDetails.EnergyConsumer consumer =
+ new MeasuredEnergyDetails.EnergyConsumer();
+ consumer.type = energyConsumer.type;
+ consumer.ordinal = energyConsumer.ordinal;
+ switch (consumer.type) {
+ case EnergyConsumerType.BLUETOOTH:
+ consumer.name = "BLUETOOTH";
+ break;
+ case EnergyConsumerType.CPU_CLUSTER:
+ consumer.name = "CPU";
+ break;
+ case EnergyConsumerType.DISPLAY:
+ consumer.name = "DISPLAY";
+ break;
+ case EnergyConsumerType.GNSS:
+ consumer.name = "GNSS";
+ break;
+ case EnergyConsumerType.MOBILE_RADIO:
+ consumer.name = "MOBILE_RADIO";
+ break;
+ case EnergyConsumerType.WIFI:
+ consumer.name = "WIFI";
+ break;
+ case EnergyConsumerType.OTHER:
+ consumer.name = sanitizeCustomBucketName(energyConsumer.name);
+ break;
+ default:
+ consumer.name = "UNKNOWN";
+ break;
+ }
+ if (consumer.type != EnergyConsumerType.OTHER) {
+ boolean hasOrdinal = consumer.ordinal != 0;
+ if (!hasOrdinal) {
+ // See if any other EnergyConsumer of the same type has an ordinal
+ for (int j = 0; j < mEnergyConsumers.size(); j++) {
+ EnergyConsumer aConsumer = mEnergyConsumers.valueAt(j);
+ if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) {
+ hasOrdinal = true;
+ break;
+ }
+ }
+ }
+ if (hasOrdinal) {
+ consumer.name = consumer.name + "/" + energyConsumer.ordinal;
+ }
+ }
+ details.consumers[i] = consumer;
+ }
+
+ details.chargeUC = new long[details.consumers.length];
+ return details;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9ad62af..f224cc7 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2459,6 +2459,7 @@
mInTask = null;
mInTaskFragment = null;
+ mAddingToTaskFragment = null;
mAddingToTask = false;
mSourceRootTask = null;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6ee0186..ac9bcfb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,6 +61,7 @@
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -94,6 +95,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
@@ -182,11 +184,13 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.DisplayMetrics;
@@ -336,7 +340,7 @@
private WindowState mTmpWindow;
private boolean mUpdateImeTarget;
private boolean mTmpInitial;
- private int mMaxUiWidth;
+ private int mMaxUiWidth = 0;
final AppTransition mAppTransition;
final AppTransitionController mAppTransitionController;
@@ -361,9 +365,10 @@
// Initial display metrics.
int mInitialDisplayWidth = 0;
int mInitialDisplayHeight = 0;
- int mInitialDisplayDensity = 0;
float mInitialPhysicalXDpi = 0.0f;
float mInitialPhysicalYDpi = 0.0f;
+ // The physical density of the display
+ int mInitialDisplayDensity = 0;
private Point mPhysicalDisplaySize;
@@ -701,10 +706,33 @@
*/
private boolean mInEnsureActivitiesVisible = false;
- // Used to indicate that the movement of child tasks to top will not move the display to top as
- // well and thus won't change the top resumed / focused record
+ /**
+ * Used to indicate that the movement of child tasks to top will not move the display to top as
+ * well and thus won't change the top resumed / focused record
+ */
boolean mDontMoveToTop;
+ /** Used for windows that want to keep the screen awake. */
+ private PowerManager.WakeLock mHoldScreenWakeLock;
+
+ /** The current window causing mHoldScreenWakeLock to be held. */
+ private WindowState mHoldScreenWindow;
+
+ /**
+ * Used during updates to temporarily store what will become the next value for
+ * mHoldScreenWindow.
+ */
+ private WindowState mTmpHoldScreenWindow;
+
+ /** Last window that obscures all windows below. */
+ private WindowState mObscuringWindow;
+
+ /** Last window which obscured a window holding the screen locked. */
+ private WindowState mLastWakeLockObscuringWindow;
+
+ /** Last window to hold the screen locked. */
+ private WindowState mLastWakeLockHoldingWindow;
+
/**
* The helper of policy controller.
*
@@ -917,7 +945,7 @@
if (isDisplayed && w.isObscuringDisplay()) {
// This window completely covers everything behind it, so we want to leave all
// of them as undimmed (for performance reasons).
- root.mObscuringWindow = w;
+ mObscuringWindow = w;
mTmpApplySurfaceChangesTransactionState.obscured = true;
}
@@ -931,6 +959,15 @@
}
if (w.mHasSurface && isDisplayed) {
+ if ((w.mAttrs.flags & FLAG_KEEP_SCREEN_ON) != 0) {
+ mTmpHoldScreenWindow = w;
+ } else if (w == mLastWakeLockHoldingWindow) {
+ ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
+ "handleNotObscuredLocked: %s was holding screen wakelock but no longer "
+ + "has FLAG_KEEP_SCREEN_ON!!! called by%s",
+ w, Debug.getCallers(10));
+ }
+
final int type = w.mAttrs.type;
if (type == TYPE_SYSTEM_DIALOG
|| type == TYPE_SYSTEM_ERROR
@@ -1038,6 +1075,7 @@
mCurrentUniqueDisplayId = display.getUniqueId();
mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer;
mWallpaperController = new WallpaperController(mWmService, this);
+ mWallpaperController.resetLargestDisplay(display);
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
@@ -1050,6 +1088,11 @@
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
+ mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
+ TAG_WM + "/displayId:" + mDisplayId, mDisplayId);
+ mHoldScreenWakeLock.setReferenceCounted(false);
+
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
@@ -1110,6 +1153,37 @@
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
+ private void beginHoldScreenUpdate() {
+ mTmpHoldScreenWindow = null;
+ mObscuringWindow = null;
+ }
+
+ private void finishHoldScreenUpdate() {
+ final boolean hold = mTmpHoldScreenWindow != null;
+ if (hold && mTmpHoldScreenWindow != mHoldScreenWindow) {
+ mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid));
+ }
+ mHoldScreenWindow = mTmpHoldScreenWindow;
+ mTmpHoldScreenWindow = null;
+
+ final boolean state = mHoldScreenWakeLock.isHeld();
+ if (hold != state) {
+ if (hold) {
+ ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Acquiring screen wakelock due to %s",
+ mHoldScreenWindow);
+ mLastWakeLockHoldingWindow = mHoldScreenWindow;
+ mLastWakeLockObscuringWindow = null;
+ mHoldScreenWakeLock.acquire();
+ } else {
+ ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by %s",
+ mObscuringWindow);
+ mLastWakeLockHoldingWindow = null;
+ mLastWakeLockObscuringWindow = mObscuringWindow;
+ mHoldScreenWakeLock.release();
+ }
+ }
+ }
+
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
@@ -2555,6 +2629,17 @@
return mCurrentOverrideConfigurationChanges;
}
+ /**
+ * @return The initial display density. This is constrained by config_maxUIWidth.
+ */
+ int getInitialDisplayDensity() {
+ int density = mInitialDisplayDensity;
+ if (mMaxUiWidth > 0 && mInitialDisplayWidth > mMaxUiWidth) {
+ density = (int) ((density * mMaxUiWidth) / (float) mInitialDisplayWidth);
+ }
+ return density;
+ }
+
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int lastOrientation = getConfiguration().orientation;
@@ -2883,7 +2968,7 @@
* so only need to configure display.
*/
void setForcedDensity(int density, int userId) {
- mIsDensityForced = density != mInitialDisplayDensity;
+ mIsDensityForced = density != getInitialDisplayDensity();
final boolean updateCurrent = userId == UserHandle.USER_CURRENT;
if (mWmService.mCurrentUserId == userId || updateCurrent) {
mBaseDisplayDensity = density;
@@ -2894,7 +2979,7 @@
return;
}
- if (density == mInitialDisplayDensity) {
+ if (density == getInitialDisplayDensity()) {
density = 0;
}
mWmService.mDisplayWindowSettings.setForcedDensity(this, density, userId);
@@ -3199,6 +3284,7 @@
mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
mRootWindowContainer.mTaskSupervisor
.getKeyguardController().onDisplayRemoved(mDisplayId);
+ mWallpaperController.resetLargestDisplay(mDisplay);
} finally {
mDisplayReady = false;
}
@@ -3456,6 +3542,16 @@
}
pw.println();
+ pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
+ pw.println();
+ pw.print(prefix + "mObscuringWindow="); pw.print(mObscuringWindow);
+ pw.println();
+ pw.print(prefix + "mLastWakeLockHoldingWindow="); pw.print(mLastWakeLockHoldingWindow);
+ pw.println();
+ pw.print(prefix + "mLastWakeLockObscuringWindow=");
+ pw.println(mLastWakeLockObscuringWindow);
+
+ pw.println();
mWallpaperController.dump(pw, " ");
if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() > 0) {
@@ -4675,6 +4771,8 @@
void applySurfaceChangesTransaction() {
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
+ beginHoldScreenUpdate();
+
mTmpUpdateAllDrawn.clear();
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
@@ -4750,6 +4848,8 @@
// can now be shown.
activity.updateAllDrawn();
}
+
+ finishHoldScreenUpdate();
}
private void getBounds(Rect out, @Rotation int rotation) {
@@ -5769,6 +5869,8 @@
updateRecording();
}
}
+ // Notify wallpaper controller of any size changes.
+ mWallpaperController.resetLargestDisplay(mDisplay);
// Dispatch pending Configuration to WindowContext if the associated display changes to
// un-suspended state from suspended.
if (isSuspendedState(lastDisplayState)
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 508e6dc..d4eac8d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -362,6 +362,7 @@
private WindowState mTopFullscreenOpaqueWindowState;
private boolean mTopIsFullscreen;
private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
+ private boolean mForceConsumeSystemBars;
private boolean mForceShowSystemBars;
private boolean mShowingDream;
@@ -1561,6 +1562,13 @@
}
/**
+ * @return true if the system bars are forced to be consumed
+ */
+ public boolean areSystemBarsForcedConsumedLw() {
+ return mForceConsumeSystemBars;
+ }
+
+ /**
* @return true if the system bars are forced to stay visible
*/
public boolean areSystemBarsForcedShownLw() {
@@ -2462,6 +2470,10 @@
// We need to force showing system bars when the multi-window or freeform root task is
// visible.
mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible;
+ // We need to force the consumption of the system bars if they are force shown or if they
+ // are controlled by a remote insets controller.
+ mForceConsumeSystemBars = mForceShowSystemBars
+ || mDisplayContent.getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win);
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
final boolean topAppHidesStatusBar = topAppHidesStatusBar();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 11bcd8c..9462d4f7 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -276,7 +276,7 @@
final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth;
final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight;
final int density = hasDensityOverride ? settings.mForcedDensity
- : dc.mInitialDisplayDensity;
+ : dc.getInitialDisplayDensity();
dc.updateBaseDisplayMetrics(width, height, density, dc.mBaseDisplayPhysicalXDpi,
dc.mBaseDisplayPhysicalYDpi);
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index 3f6fb622..e3a2065 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -22,6 +22,8 @@
import android.util.SparseArray;
import android.view.DisplayInfo;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/**
@@ -52,19 +54,21 @@
/**
- * Returns, for the given displayId, a set of display infos. Set contains each supported device
- * state.
+ * Returns, for the given displayId, a list of unique display infos. List contains each
+ * supported device state.
+ * <p>List contents are guaranteed to be unique, but returned as a list rather than a set to
+ * minimize copies needed to make an iteraable data structure.
*/
- public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) {
+ public List<DisplayInfo> getPossibleDisplayInfos(int displayId) {
// Update display infos before returning, since any cached values would have been removed
// in response to any display event. This model avoids re-computing the cache for every
// display change event (which occurs extremely frequently in the normal usage of the
// device).
updatePossibleDisplayInfos(displayId);
if (!mDisplayInfos.contains(displayId)) {
- return new ArraySet<>();
+ return new ArrayList<>();
}
- return Set.copyOf(mDisplayInfos.get(displayId));
+ return List.copyOf(mDisplayInfos.get(displayId));
}
/**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 077f8b5..b3f3548 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -28,7 +28,6 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -176,15 +175,9 @@
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
private Object mLastWindowFreezeSource = null;
- private Session mHoldScreen = null;
private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private long mUserActivityTimeout = -1;
private boolean mUpdateRotation = false;
- // Following variables are for debugging screen wakelock only.
- // Last window that requires screen wakelock
- WindowState mHoldScreenWindow = null;
- // Last window that obscures all windows below
- WindowState mObscuringWindow = null;
// Only set while traversing the default display based on its content.
// Affects the behavior of mirroring on secondary displays.
private boolean mObscureApplicationContentOnSecondaryDisplays = false;
@@ -801,7 +794,6 @@
UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
}
- mHoldScreen = null;
mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mUserActivityTimeout = -1;
mObscureApplicationContentOnSecondaryDisplays = false;
@@ -914,7 +906,6 @@
}
}
- mWmService.setHoldScreenLocked(mHoldScreen);
if (!mWmService.mDisplayFrozen) {
final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN
|| mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX
@@ -994,9 +985,6 @@
}
private void applySurfaceChangesTransaction() {
- mHoldScreenWindow = null;
- mObscuringWindow = null;
-
// TODO(multi-display): Support these features on secondary screens.
final DisplayContent defaultDc = mDefaultDisplay;
final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
@@ -1071,15 +1059,6 @@
}
}
if (w.mHasSurface && canBeSeen) {
- if ((attrFlags & FLAG_KEEP_SCREEN_ON) != 0) {
- mHoldScreen = w.mSession;
- mHoldScreenWindow = w;
- } else if (w == mWmService.mLastWakeLockHoldingWindow) {
- ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON,
- "handleNotObscuredLocked: %s was holding screen wakelock but no longer "
- + "has FLAG_KEEP_SCREEN_ON!!! called by%s",
- w, Debug.getCallers(10));
- }
if (!syswin && w.mAttrs.screenBrightness >= 0
&& Float.isNaN(mScreenBrightnessOverride)) {
mScreenBrightnessOverride = w.mAttrs.screenBrightness;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d8a054c..8c037a7 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -45,6 +45,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
+import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
@@ -484,16 +485,31 @@
}
@Override
- public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer,
- @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) {
+ public void onTransactionHandled(@NonNull IBinder transactionToken,
+ @NonNull WindowContainerTransaction wct,
+ @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
+ // Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- // Keep the calling identity to avoid unsecure change.
- mWindowOrganizerController.applyTransaction(wct);
- final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ applyTransaction(wct, transitionType, shouldApplyIndependently);
+ final TaskFragmentOrganizerState state = validateAndGetState(
+ wct.getTaskFragmentOrganizer());
state.onTransactionFinished(transactionToken);
}
}
+ @Override
+ public void applyTransaction(@NonNull WindowContainerTransaction wct,
+ @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
+ // Keep the calling identity to avoid unsecure change.
+ synchronized (mGlobalLock) {
+ if (wct.isEmpty()) {
+ return;
+ }
+ mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
+ shouldApplyIndependently);
+ }
+ }
+
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
* {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6245005..d652b8e 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -19,6 +19,7 @@
import static android.app.WallpaperManager.COMMAND_FREEZE;
import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -35,7 +36,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
@@ -45,6 +48,8 @@
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -56,6 +61,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -113,8 +119,12 @@
*/
private WindowState mTmpTopWallpaper;
+ @Nullable private Point mLargestDisplaySize = null;
+
private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
+ private boolean mShouldOffsetWallpaperCenter;
+
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
if ((w.mAttrs.type == TYPE_WALLPAPER)) {
if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
@@ -241,6 +251,38 @@
mDisplayContent = displayContent;
mMaxWallpaperScale = service.mContext.getResources()
.getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale);
+ mShouldOffsetWallpaperCenter = service.mContext.getResources()
+ .getBoolean(
+ com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ }
+
+ void resetLargestDisplay(Display display) {
+ if (display != null && display.getType() == Display.TYPE_INTERNAL) {
+ mLargestDisplaySize = null;
+ }
+ }
+
+ @VisibleForTesting void setShouldOffsetWallpaperCenter(boolean shouldOffset) {
+ mShouldOffsetWallpaperCenter = shouldOffset;
+ }
+
+ @Nullable private Point findLargestDisplaySize() {
+ if (!mShouldOffsetWallpaperCenter) {
+ return null;
+ }
+ Point largestDisplaySize = new Point();
+ List<DisplayInfo> possibleDisplayInfo =
+ mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY);
+ for (int i = 0; i < possibleDisplayInfo.size(); i++) {
+ DisplayInfo displayInfo = possibleDisplayInfo.get(i);
+ if (displayInfo.type == Display.TYPE_INTERNAL
+ && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight)
+ > Math.max(largestDisplaySize.x, largestDisplaySize.y)) {
+ largestDisplaySize.set(displayInfo.logicalWidth,
+ displayInfo.logicalHeight);
+ }
+ }
+ return largestDisplaySize;
}
WindowState getWallpaperTarget() {
@@ -327,24 +369,44 @@
}
boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
- final Rect bounds = wallpaperWin.getLastReportedBounds();
- final int dw = bounds.width();
- final int dh = bounds.height();
+ // Size of the display the wallpaper is rendered on.
+ final Rect lastWallpaperBounds = wallpaperWin.getLastReportedBounds();
+ // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
+ // swiping through Launcher pages).
+ final Rect wallpaperFrame = wallpaperWin.getFrame();
- int xOffset = 0;
- int yOffset = 0;
+ int newXOffset = 0;
+ int newYOffset = 0;
boolean rawChanged = false;
// Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
// match the behavior of most Launchers
float defaultWallpaperX = wallpaperWin.isRtl() ? 1f : 0f;
+ // "Wallpaper X" is the previous x-offset of the wallpaper (in a 0 to 1 scale).
+ // The 0 to 1 scale is because the "length" varies depending on how many home screens you
+ // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for
+ // LTR, and the opposite for RTL).
float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
+ // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen
+ // when scrolling.
float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
- int availw = wallpaperWin.getFrame().right - wallpaperWin.getFrame().left - dw;
+ // Difference between width of wallpaper image, and the last size of the wallpaper.
+ // This is the horizontal surplus from the prior configuration.
+ int availw = wallpaperFrame.width() - lastWallpaperBounds.width();
+
+ int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
+ wallpaperWin.isRtl());
+ availw -= displayOffset;
int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+ // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn
+ // always starting from the left of the screen).
offset += mLastWallpaperDisplayOffsetX;
+ } else if (!wallpaperWin.isRtl()) {
+ // In RTL the offset is calculated so that the wallpaper ends up right aligned (see
+ // offset above).
+ offset -= displayOffset;
}
- xOffset = offset;
+ newXOffset = offset;
if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
wallpaperWin.mWallpaperX = wpx;
@@ -354,12 +416,13 @@
float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
- int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top - dh;
+ int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top
+ - lastWallpaperBounds.height();
offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
offset += mLastWallpaperDisplayOffsetY;
}
- yOffset = offset;
+ newYOffset = offset;
if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
wallpaperWin.mWallpaperY = wpy;
@@ -372,7 +435,7 @@
rawChanged = true;
}
- boolean changed = wallpaperWin.setWallpaperOffset(xOffset, yOffset,
+ boolean changed = wallpaperWin.setWallpaperOffset(newXOffset, newYOffset,
wallpaperWin.mShouldScaleWallpaper
? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1);
@@ -419,6 +482,52 @@
return changed;
}
+ /**
+ * Get an extra offset if needed ({@link #mShouldOffsetWallpaperCenter} = true, typically on
+ * multiple display devices) so that the wallpaper in a smaller display ends up centered at the
+ * same position as in the largest display of the device.
+ *
+ * Note that the wallpaper has already been cropped when set by the user, so these calculations
+ * apply to the image size for the display the wallpaper was set for.
+ *
+ * @param availWidth width available for the wallpaper offset in the current display
+ * @param displayFrame size of the "display" (parent frame)
+ * @param isRtl whether we're in an RTL configuration
+ * @return an offset to apply to the width, or 0 if the current configuration doesn't require
+ * any adjustment (either @link #mShouldOffsetWallpaperCenter} is false or we're on the largest
+ * display).
+ */
+ private int getDisplayWidthOffset(int availWidth, Rect displayFrame, boolean isRtl) {
+ if (!mShouldOffsetWallpaperCenter) {
+ return 0;
+ }
+ if (mLargestDisplaySize == null) {
+ mLargestDisplaySize = findLargestDisplaySize();
+ }
+ if (mLargestDisplaySize == null) {
+ return 0;
+ }
+ // Page width is the width of a Launcher "page", for pagination when swiping right.
+ int pageWidth = displayFrame.width();
+ // Only need offset if the current size is different from the largest display, and we're
+ // in a portrait configuration
+ if (mLargestDisplaySize.x != pageWidth && displayFrame.width() < displayFrame.height()) {
+ // The wallpaper will be scaled to fit the height of the wallpaper, so if the height
+ // of the displays are different, we need to account for that scaling when calculating
+ // the offset to the center
+ float sizeRatio = (float) displayFrame.height() / mLargestDisplaySize.y;
+ // Scale the width of the largest display to match the scale of the wallpaper size in
+ // the current display
+ int adjustedLargestWidth = Math.round(mLargestDisplaySize.x * sizeRatio);
+ // Finally, find the difference between the centers, taking into account that the
+ // size of the wallpaper frame could be smaller than the screen
+ return isRtl
+ ? adjustedLargestWidth - (adjustedLargestWidth + pageWidth) / 2
+ : Math.min(adjustedLargestWidth - pageWidth, availWidth) / 2;
+ }
+ return 0;
+ }
+
void setWindowWallpaperPosition(
WindowState window, float x, float y, float xStep, float yStep) {
if (window.mWallpaperX != x || window.mWallpaperY != y) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8025cb2..236d0dd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -103,7 +103,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
@@ -218,7 +217,6 @@
import android.os.SystemService;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.WorkSource;
import android.provider.DeviceConfigInterface;
import android.provider.Settings;
import android.service.vr.IVrManager;
@@ -620,10 +618,6 @@
boolean mBootAnimationStopped = false;
long mBootWaitForWindowsStartTime = -1;
- // Following variables are for debugging screen wakelock only.
- WindowState mLastWakeLockHoldingWindow = null;
- WindowState mLastWakeLockObscuringWindow = null;
-
/** Dump of the windows and app tokens at the time of the last ANR. Cleared after
* LAST_ANR_LIFETIME_DURATION_MSECS */
String mLastANRState;
@@ -991,10 +985,6 @@
private boolean mHasWideColorGamutSupport;
private boolean mHasHdrSupport;
- /** Who is holding the screen on. */
- private Session mHoldingScreenOn;
- private PowerManager.WakeLock mHoldingScreenWakeLock;
-
/** Whether or not a layout can cause a wake up when theater mode is enabled. */
boolean mAllowTheaterModeWakeFromLayout;
@@ -1326,10 +1316,6 @@
mSettingsObserver = new SettingsObserver();
- mHoldingScreenWakeLock = mPowerManager.newWakeLock(
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
- mHoldingScreenWakeLock.setReferenceCounted(false);
-
mSurfaceAnimationRunner = new SurfaceAnimationRunner(mTransactionFactory,
mPowerManagerInternal);
@@ -1808,7 +1794,7 @@
prepareNoneTransitionForRelaunching(activity);
}
- if (displayPolicy.areSystemBarsForcedShownLw()) {
+ if (displayPolicy.areSystemBarsForcedConsumedLw()) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
}
@@ -2527,7 +2513,7 @@
if (win.mActivityRecord != null) {
win.mActivityRecord.updateReportedVisibilityLocked();
}
- if (displayPolicy.areSystemBarsForcedShownLw()) {
+ if (displayPolicy.areSystemBarsForcedConsumedLw()) {
result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
}
if (!win.isGoneForLayout()) {
@@ -3591,8 +3577,8 @@
// Otherwise, we'll update it when it's prepared.
if (mDisplayReady) {
final int forcedDensity = getForcedDisplayDensityForUserLocked(newUserId);
- final int targetDensity = forcedDensity != 0 ? forcedDensity
- : displayContent.mInitialDisplayDensity;
+ final int targetDensity = forcedDensity != 0
+ ? forcedDensity : displayContent.getInitialDisplayDensity();
displayContent.setForcedDensity(targetDensity, UserHandle.USER_CURRENT);
}
}
@@ -5803,7 +5789,7 @@
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
- return displayContent.mInitialDisplayDensity;
+ return displayContent.getInitialDisplayDensity();
}
}
return -1;
@@ -5858,7 +5844,7 @@
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
- displayContent.setForcedDensity(displayContent.mInitialDisplayDensity,
+ displayContent.setForcedDensity(displayContent.getInitialDisplayDensity(),
callingUserId);
}
}
@@ -6013,34 +5999,6 @@
});
}
- void setHoldScreenLocked(final Session newHoldScreen) {
- final boolean hold = newHoldScreen != null;
-
- if (hold && mHoldingScreenOn != newHoldScreen) {
- mHoldingScreenWakeLock.setWorkSource(new WorkSource(newHoldScreen.mUid));
- }
- mHoldingScreenOn = newHoldScreen;
-
- final boolean state = mHoldingScreenWakeLock.isHeld();
- if (hold != state) {
- if (hold) {
- ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Acquiring screen wakelock due to %s",
- mRoot.mHoldScreenWindow);
- mLastWakeLockHoldingWindow = mRoot.mHoldScreenWindow;
- mLastWakeLockObscuringWindow = null;
- mHoldingScreenWakeLock.acquire();
- mPolicy.keepScreenOnStartedLw();
- } else {
- ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by %s",
- mRoot.mObscuringWindow);
- mLastWakeLockHoldingWindow = null;
- mLastWakeLockObscuringWindow = mRoot.mObscuringWindow;
- mPolicy.keepScreenOnStoppedLw();
- mHoldingScreenWakeLock.release();
- }
- }
- }
-
void requestTraversal() {
mWindowPlacerLocked.requestTraversal();
}
@@ -6674,9 +6632,6 @@
pw.print(mLastFinishedFreezeSource);
}
pw.println();
- pw.print(" mLastWakeLockHoldingWindow=");pw.print(mLastWakeLockHoldingWindow);
- pw.print(" mLastWakeLockObscuringWindow="); pw.print(mLastWakeLockObscuringWindow);
- pw.println();
mInputManagerCallback.dump(pw, " ");
mTaskSnapshotController.dump(pw, " ");
@@ -8896,7 +8851,7 @@
: overrideScale;
outInsetsState.scale(1f / compatScale);
}
- return dc.getDisplayPolicy().areSystemBarsForcedShownLw();
+ return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -8916,13 +8871,19 @@
}
// Retrieve the DisplayInfo across all possible display layouts.
- return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId));
+ return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ List<DisplayInfo> getPossibleDisplayInfoLocked(int displayId) {
+ // Retrieve the DisplayInfo for all possible rotations across all possible display
+ // layouts.
+ return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
+ }
+
/**
* Returns {@code true} when the calling package is the recents component.
*/
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 34a4bf1..d34ad7d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -29,6 +29,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
@@ -74,6 +75,7 @@
import android.util.Slog;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
@@ -176,7 +178,7 @@
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applyTransaction");
}
- enforceTaskPermission("applyTransaction()", t);
+ enforceTaskPermission("applyTransaction()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -194,7 +196,7 @@
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
}
- enforceTaskPermission("applySyncTransaction()", t);
+ enforceTaskPermission("applySyncTransaction()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
@@ -373,6 +375,87 @@
}
}
+ /**
+ * Applies the {@link WindowContainerTransaction} as a request from
+ * {@link android.window.TaskFragmentOrganizer}.
+ *
+ * @param wct {@link WindowContainerTransaction} to apply.
+ * @param type {@link WindowManager.TransitionType} if it needs to start a new transition.
+ * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new
+ * transition, which will be queued until the sync engine is
+ * free if there is any other active sync. If {@code false},
+ * the {@code wct} will be directly applied to the active sync.
+ */
+ void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
+ @WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
+ if (!isValidTransaction(wct)) {
+ return;
+ }
+ enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
+ Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
+ Objects.requireNonNull(wct));
+ final CallerInfo caller = new CallerInfo();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mTransitionController.getTransitionPlayer() == null) {
+ // No need to worry about transition when Shell transition is not enabled.
+ applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
+ return;
+ }
+
+ if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+ // Sync is for either transition or applySyncTransaction(). We don't support
+ // multiple sync at the same time because it may cause conflict.
+ // Create a new transition when there is no active sync to collect the changes.
+ final Transition transition = mTransitionController.createTransition(type);
+ applyTransaction(wct, -1 /* syncId */, transition, caller);
+ mTransitionController.requestStartTransition(transition, null /* startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ return;
+ }
+
+ if (!shouldApplyIndependently) {
+ // Although there is an active sync, we want to apply the transaction now.
+ if (!mTransitionController.isCollecting()) {
+ // This should rarely happen, and we should try to avoid using
+ // {@link #applySyncTransaction} with Shell transition.
+ // We still want to apply and merge the transaction to the active sync
+ // because {@code shouldApplyIndependently} is {@code false}.
+ ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "TaskFragmentTransaction changes are not collected in transition"
+ + " because there is an ongoing sync for"
+ + " applySyncTransaction().");
+ }
+ // TODO(b/207070762) make sure changes are all collected.
+ applyTransaction(wct, -1 /* syncId */, null /* transition */, caller);
+ return;
+ }
+
+ // It is ok to queue the WCT until the sync engine is free.
+ final Transition nextTransition = new Transition(type, 0 /* flags */,
+ mTransitionController, mService.mWindowManager.mSyncEngine);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Pending Transition for TaskFragment: %s", nextTransition);
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ // Make sure to collect immediately to prevent another transition
+ // from sneaking in before it. Note: moveToCollecting internally
+ // calls startSyncSet.
+ () -> mTransitionController.moveToCollecting(nextTransition),
+ () -> {
+ if (isValidTransaction(wct)) {
+ applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
+ mTransitionController.requestStartTransition(nextTransition,
+ null /* startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ } else {
+ nextTransition.abort();
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller) {
applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
@@ -387,12 +470,6 @@
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
- if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
- .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
- Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
- + " is no longer registered");
- return;
- }
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
@@ -703,6 +780,12 @@
@Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {
final int type = hop.getType();
switch (type) {
+ case HIERARCHY_OP_TYPE_REMOVE_TASK: {
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ final Task task = wc != null ? wc.asTask() : null;
+ task.remove(true, "Applying remove task Hierarchy Op");
+ break;
+ }
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
final Task task = wc != null ? wc.asTask() : null;
@@ -1488,25 +1571,24 @@
mService.enforceTaskPermission(func);
}
- private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) {
- if (t == null || t.getTaskFragmentOrganizer() == null) {
- enforceTaskPermission(func);
- return;
+ private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+ if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
+ .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
+ // Transaction from an unregistered organizer should not be applied. This can happen
+ // when the organizer process died before the transaction is applied.
+ Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ + " is no longer registered");
+ return false;
}
-
- // Apps may not have the permission to manage Tasks, but we are allowing apps to manage
- // TaskFragments belonging to their own Task.
- enforceOperationsAllowedForTaskFragmentOrganizer(func, t);
+ return true;
}
/**
* Makes sure that the transaction only contains operations that are allowed for the
* {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
*/
- private void enforceOperationsAllowedForTaskFragmentOrganizer(
- String func, WindowContainerTransaction t) {
- final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
-
+ private void enforceTaskFragmentOrganizerPermission(@NonNull String func,
+ @NonNull ITaskFragmentOrganizer organizer, @NonNull WindowContainerTransaction t) {
// Configuration changes
final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3469303..2e7e78d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3917,7 +3917,7 @@
final boolean forceRelayout = syncWithBuffers || isDragResizeChanged;
final DisplayContent displayContent = getDisplayContent();
final boolean alwaysConsumeSystemBars =
- displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
+ displayContent.getDisplayPolicy().areSystemBarsForcedConsumedLw();
final int displayId = displayContent.getDisplayId();
if (isDragResizeChanged) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 2ee5fb0..aa58902 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -228,7 +228,5 @@
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "mTraversalScheduled=" + mTraversalScheduled);
- pw.println(prefix + "mHoldScreenWindow=" + mService.mRoot.mHoldScreenWindow);
- pw.println(prefix + "mObscuringWindow=" + mService.mRoot.mObscuringWindow);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 24101dd..5d6ffd8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,6 +37,7 @@
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
+ "com_android_server_display_DisplayControl.cpp",
"com_android_server_connectivity_Vpn.cpp",
"com_android_server_gpu_GpuService.cpp",
"com_android_server_HardwarePropertiesManagerService.cpp",
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
new file mode 100644
index 0000000..61f2b14
--- /dev/null
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include <android_util_Binder.h>
+#include <gui/SurfaceComposerClient.h>
+#include <jni.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+namespace android {
+
+static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) {
+ ScopedUtfChars name(env, nameObj);
+ sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure)));
+ return javaObjectForIBinder(env, token);
+}
+
+static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+ SurfaceComposerClient::destroyDisplay(token);
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod sDisplayMethods[] = {
+ // clang-format off
+ {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
+ (void*)nativeCreateDisplay },
+ {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
+ (void*)nativeDestroyDisplay },
+ // clang-format on
+};
+
+int register_com_android_server_display_DisplayControl(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/display/DisplayControl",
+ sDisplayMethods, NELEM(sDisplayMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0cd9494..00bef09 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -64,6 +64,7 @@
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
+int register_com_android_server_display_DisplayControl(JNIEnv* env);
};
using namespace android;
@@ -120,5 +121,6 @@
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
+ register_com_android_server_display_DisplayControl(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fbaf1ce..7b941d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5760,36 +5760,6 @@
return new ParcelableGranteeMap(result);
}
- /**
- * Enforce one the following conditions are met:
- * (1) The device has a Device Owner, and one of the following holds:
- * (1.1) The caller is the Device Owner
- * (1.2) The caller is another app in the same user as the device owner, AND
- * The caller is the delegated certificate installer.
- * (1.3) The caller is a Profile Owner and the calling user is affiliated.
- * (2) The user has a profile owner, AND:
- * (2.1) The profile owner has been granted access to Device IDs and one of the following
- * holds:
- * (2.1.1) The caller is the profile owner.
- * (2.1.2) The caller is from another app in the same user as the profile owner, AND
- * (2.1.2.1) The caller is the delegated cert installer.
- *
- * For the device owner case, simply check that the caller is the device owner or the
- * delegated certificate installer.
- *
- * For the profile owner case, first check that the caller is the profile owner or can
- * manage the DELEGATION_CERT_INSTALL scope.
- * If that check succeeds, ensure the profile owner was granted access to device
- * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed
- * access to device identifiers in this case as part of the delegation.
- */
- @VisibleForTesting
- public void enforceCallerCanRequestDeviceIdAttestation(CallerIdentity caller)
- throws SecurityException {
- Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked(caller.getPackageName(),
- caller.getUid()));
- }
-
@VisibleForTesting
public static int[] translateIdAttestationFlags(
int idAttestationFlags) {
@@ -5844,8 +5814,8 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
- // TODO: replace enforce methods
- enforceCallerCanRequestDeviceIdAttestation(caller);
+ Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked(
+ caller.getPackageName(), caller.getUid()));
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
@@ -9376,21 +9346,36 @@
}
/**
- * Check if caller is device owner, delegate cert installer or profile owner of
- * affiliated user. Or if caller is profile owner for a specified user or delegate cert
- * installer on an organization-owned device.
+ * Check if one the following conditions hold:
+ * (1) The device has a Device Owner, and one of the following holds:
+ * (1.1) The caller is the Device Owner
+ * (1.2) The caller is another app in the same user as the device owner, AND
+ * The caller is the delegated certificate installer.
+ * (1.3) The caller is a Profile Owner and the calling user is affiliated.
+ * (2) The user has a profile owner, AND:
+ * (2.1) The profile owner has been granted access to Device IDs and one of the following
+ * holds:
+ * (2.1.1) The caller is the profile owner.
+ * (2.1.2) The caller is from another app in the same user as the profile owner, AND
+ * the caller is the delegated cert installer.
+ *
+ * For the device owner case, simply check that the caller is the device owner or the
+ * delegated certificate installer.
+ *
+ * For the profile owner case, first check that the caller is the profile owner or can
+ * manage the DELEGATION_CERT_INSTALL scope.
+ * If that check succeeds, ensure the profile owner was granted access to device
+ * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed
+ * access to device identifiers in this case as part of the delegation.
*/
- private boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
- // Is the caller a device owner, delegate cert installer or profile owner of an
- // affiliated user.
+ @VisibleForTesting
+ boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
ComponentName deviceOwner = getDeviceOwnerComponent(true);
if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
|| isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
return true;
}
final int userId = UserHandle.getUserId(uid);
- // Is the caller the profile owner for the specified user, or delegate cert installer on an
- // organization-owned device.
ComponentName profileOwner = getProfileOwnerAsUser(userId);
final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
&& (profileOwner.getPackageName().equals(packageName)
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index d322290..3c68662 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -205,6 +205,7 @@
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setPeriodic(BG_PROCESS_PERIOD)
+ .setPriority(JobInfo.PRIORITY_MIN)
.build());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 9d26971..fb9cbb0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -61,7 +61,6 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -1029,44 +1028,4 @@
return mPackageInfo.applicationInfo.splitSourceDirs[length - 1];
}
}
-
- private boolean shouldPackageRunOob(boolean isDefaultEnabled, String whitelist,
- Collection<String> packageNamesInSameProcess) {
- return DexManager.isPackageSelectedToRunOobInternal(
- isDefaultEnabled, whitelist, packageNamesInSameProcess);
- }
-
- @Test
- public void testOobPackageSelectionDefault() {
- // Feature is off by default, not overriden
- assertFalse(shouldPackageRunOob(false, "ALL", null));
- }
-
- @Test
- public void testOobPackageSelectionWhitelist() {
- // Various allowlist of apps to run in OOB mode.
- final String kWhitelistApp0 = "com.priv.app0";
- final String kWhitelistApp1 = "com.priv.app1";
- final String kWhitelistApp2 = "com.priv.app2";
- final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2";
-
- // Packages that shares the targeting process.
- final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2");
-
- // Feature is off, allowlist does not matter
- assertFalse(shouldPackageRunOob(false, kWhitelistApp0, runningPackages));
- assertFalse(shouldPackageRunOob(false, kWhitelistApp1, runningPackages));
- assertFalse(shouldPackageRunOob(false, "", runningPackages));
- assertFalse(shouldPackageRunOob(false, "ALL", runningPackages));
-
- // Feature is on, app not in allowlist
- assertFalse(shouldPackageRunOob(true, kWhitelistApp0, runningPackages));
- assertFalse(shouldPackageRunOob(true, "", runningPackages));
-
- // Feature is on, app in allowlist
- assertTrue(shouldPackageRunOob(true, kWhitelistApp1, runningPackages));
- assertTrue(shouldPackageRunOob(true, kWhitelistApp2, runningPackages));
- assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, runningPackages));
- assertTrue(shouldPackageRunOob(true, "ALL", runningPackages));
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a7ca083..eda9133 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -6724,55 +6724,51 @@
}
@Test
- public void testEnforceCallerCanRequestDeviceIdAttestation_deviceOwnerCaller()
+ public void testHasDeviceIdAccessUnchecked_deviceOwnerCaller()
throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
configureContextForAccess(mContext, false);
// Device owner should be allowed to request Device ID attestation.
- dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_SYSTEM_USER_UID)).isTrue();
- mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
// Another package must not be allowed to request Device ID attestation.
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(null, admin2.getPackageName())));
-
- // Another component that is not the admin must not be allowed to request Device ID
- // attestation.
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(admin2)));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DpmMockContext.CALLER_SYSTEM_USER_UID)).isFalse();
}
@Test
- public void testEnforceCallerCanRequestDeviceIdAttestation_profileOwnerCaller()
+ public void testHasDeviceIdAccessUnchecked_profileOwnerCaller()
throws Exception {
configureContextForAccess(mContext, false);
// Make sure a security exception is thrown if the device has no profile owner.
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(admin1)));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_UID)).isFalse();
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
// The profile owner is allowed to request Device ID attestation.
- mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
- dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_UID)).isTrue();
// But not another package.
- mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(null, admin2.getPackageName())));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DpmMockContext.CALLER_UID)).isFalse();
// Or another component which is not the admin.
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(admin2, admin2.getPackageName())));
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.ANOTHER_UID)).isFalse();
}
public void runAsDelegatedCertInstaller(DpmRunnable action) throws Exception {
@@ -6788,7 +6784,7 @@
}
@Test
- public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCaller() throws Exception {
+ public void testHasDeviceIdAccessUnchecked_delegateCaller() throws Exception {
setupProfileOwner();
markDelegatedCertInstallerAsInstalled();
@@ -6800,15 +6796,19 @@
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
// Make sure that the profile owner can still request Device ID attestation.
- mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
- dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_UID)).isTrue();
- runAsDelegatedCertInstaller(dpm -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(null, DpmMockContext.DELEGATE_PACKAGE_NAME)));
+ runAsDelegatedCertInstaller(dpm -> assertThat(
+ dpms.hasDeviceIdAccessUnchecked(
+ DpmMockContext.DELEGATE_PACKAGE_NAME,
+ UserHandle.getUid(CALLER_USER_HANDLE,
+ DpmMockContext.DELEGATE_CERT_INSTALLER_UID))).isTrue());
}
@Test
- public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCallerWithoutPermissions()
+ public void testHasDeviceIdAccessUnchecked_delegateCallerWithoutPermissions()
throws Exception {
setupProfileOwner();
markDelegatedCertInstallerAsInstalled();
@@ -6818,14 +6818,14 @@
dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME,
Arrays.asList(DELEGATION_CERT_INSTALL)));
- assertExpectException(SecurityException.class, null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(admin1)));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ admin1.getPackageName(),
+ DpmMockContext.CALLER_UID)).isFalse();
runAsDelegatedCertInstaller(dpm -> {
- assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpms.enforceCallerCanRequestDeviceIdAttestation(
- dpms.getCallerIdentity(null, DpmMockContext.DELEGATE_PACKAGE_NAME)));
+ assertThat(dpms.hasDeviceIdAccessUnchecked(
+ DpmMockContext.DELEGATE_PACKAGE_NAME,
+ DpmMockContext.CALLER_UID)).isFalse();
});
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 0f2fe44..821ce5e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -75,6 +75,7 @@
import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
+import static com.android.server.net.NetworkPolicyManagerService.normalizeTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -2030,6 +2031,18 @@
METERED_NO, actualPolicy.template.getMeteredness());
}
+ @Test
+ public void testNormalizeTemplate_duplicatedMergedImsiList() {
+ final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(TEST_IMSI)).build();
+ final String[] mergedImsiGroup = new String[] {TEST_IMSI, TEST_IMSI};
+ final ArrayList<String[]> mergedList = new ArrayList<>();
+ mergedList.add(mergedImsiGroup);
+ // Verify the duplicated items in the merged IMSI list won't crash the system.
+ final NetworkTemplate result = normalizeTemplate(template, mergedList);
+ assertEquals(template, result);
+ }
+
private String formatBlockedStateError(int uid, int rule, boolean metered,
boolean backgroundRestricted) {
return String.format(
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 f1f423d..f6852d8 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -992,6 +992,40 @@
}
@Test
+ public void testInattentiveSleep_warningStaysWhenDreaming() {
+ setMinimumScreenOffTimeoutConfig(5);
+ setAttentiveWarningDuration(70);
+ setAttentiveTimeout(100);
+ createService();
+ startSystem();
+ advanceTime(50);
+ verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show();
+ when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true);
+
+ forceDream();
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+
+ advanceTime(10);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean());
+ }
+
+ @Test
+ public void testInattentiveSleep_warningNotShownWhenSleeping() {
+ setMinimumScreenOffTimeoutConfig(5);
+ setAttentiveWarningDuration(70);
+ setAttentiveTimeout(100);
+ createService();
+ startSystem();
+
+ advanceTime(10);
+ forceSleep();
+
+ advanceTime(50);
+ verify(mInattentiveSleepWarningControllerMock, never()).show();
+ }
+
+ @Test
public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() {
setAttentiveTimeout(-1);
createService();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 5c934852..047fcd6 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -16,11 +16,17 @@
package com.android.server.power.stats;
+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.assertTrue;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.MeasuredEnergyDetails;
import android.os.Parcel;
import android.util.Log;
@@ -28,15 +34,19 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.Clock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -51,6 +61,9 @@
private File mSystemDir;
private File mHistoryDir;
private final Clock mClock = new MockClock();
+ private BatteryStatsHistory mHistory;
+ @Mock
+ private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator;
@Before
public void setUp() {
@@ -65,55 +78,56 @@
}
}
mHistoryDir.delete();
+ mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ mStepDetailsCalculator, mClock);
+
+ when(mStepDetailsCalculator.getHistoryStepDetails())
+ .thenReturn(new BatteryStats.HistoryStepDetails());
}
@Test
public void testConstruct() {
- BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- null, mClock);
- createActiveFile(history);
- verifyFileNumbers(history, Arrays.asList(0));
- verifyActiveFile(history, "0.bin");
+ createActiveFile(mHistory);
+ verifyFileNumbers(mHistory, Arrays.asList(0));
+ verifyActiveFile(mHistory, "0.bin");
}
@Test
public void testStartNextFile() {
- BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- null, mClock);
List<Integer> fileList = new ArrayList<>();
fileList.add(0);
- createActiveFile(history);
+ createActiveFile(mHistory);
// create file 1 to 31.
for (int i = 1; i < 32; i++) {
fileList.add(i);
- history.startNextFile();
- createActiveFile(history);
- verifyFileNumbers(history, fileList);
- verifyActiveFile(history, i + ".bin");
+ mHistory.startNextFile();
+ createActiveFile(mHistory);
+ verifyFileNumbers(mHistory, fileList);
+ verifyActiveFile(mHistory, i + ".bin");
}
// create file 32
- history.startNextFile();
- createActiveFile(history);
+ mHistory.startNextFile();
+ createActiveFile(mHistory);
fileList.add(32);
fileList.remove(0);
// verify file 0 is deleted.
verifyFileDeleted("0.bin");
- verifyFileNumbers(history, fileList);
- verifyActiveFile(history, "32.bin");
+ verifyFileNumbers(mHistory, fileList);
+ verifyActiveFile(mHistory, "32.bin");
// create file 33
- history.startNextFile();
- createActiveFile(history);
+ mHistory.startNextFile();
+ createActiveFile(mHistory);
// verify file 1 is deleted
fileList.add(33);
fileList.remove(0);
verifyFileDeleted("1.bin");
- verifyFileNumbers(history, fileList);
- verifyActiveFile(history, "33.bin");
+ verifyFileNumbers(mHistory, fileList);
+ verifyActiveFile(mHistory, "33.bin");
- assertEquals(0, history.getHistoryUsedSize());
+ assertEquals(0, mHistory.getHistoryUsedSize());
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
@@ -168,4 +182,74 @@
Log.e(TAG, "Error creating history file " + file.getPath(), e);
}
}
+
+ @Test
+ public void testRecordMeasuredEnergyDetails() {
+ mHistory.forceRecordAllHistory();
+ mHistory.startRecordingHistory(0, 0, /* reset */ true);
+ mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
+ 1234);
+
+ MeasuredEnergyDetails details = new MeasuredEnergyDetails();
+ MeasuredEnergyDetails.EnergyConsumer consumer1 =
+ new MeasuredEnergyDetails.EnergyConsumer();
+ consumer1.type = 42;
+ consumer1.ordinal = 0;
+ consumer1.name = "A";
+
+ MeasuredEnergyDetails.EnergyConsumer consumer2 =
+ new MeasuredEnergyDetails.EnergyConsumer();
+ consumer2.type = 777;
+ consumer2.ordinal = 0;
+ consumer2.name = "B/0";
+
+ MeasuredEnergyDetails.EnergyConsumer consumer3 =
+ new MeasuredEnergyDetails.EnergyConsumer();
+ consumer3.type = 777;
+ consumer3.ordinal = 1;
+ consumer3.name = "B/1";
+
+ MeasuredEnergyDetails.EnergyConsumer consumer4 =
+ new MeasuredEnergyDetails.EnergyConsumer();
+ consumer4.type = 314;
+ consumer4.ordinal = 1;
+ consumer4.name = "C";
+
+ details.consumers =
+ new MeasuredEnergyDetails.EnergyConsumer[]{consumer1, consumer2, consumer3,
+ consumer4};
+ details.chargeUC = new long[details.consumers.length];
+ for (int i = 0; i < details.chargeUC.length; i++) {
+ details.chargeUC[i] = 100L * i;
+ }
+ details.chargeUC[3] = BatteryStats.POWER_DATA_UNAVAILABLE;
+
+ mHistory.recordMeasuredEnergyDetails(200, 200, details);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+
+ assertThat(iterator.next(item)).isTrue();
+
+ String dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+200ms");
+ assertThat(dump).contains("ext=E");
+ assertThat(dump).contains("Energy: A=0 B/0=100 B/1=200");
+ assertThat(dump).doesNotContain("C=");
+
+ String checkin = toString(item, /* checkin */ true);
+ assertThat(checkin).contains("XE");
+ assertThat(checkin).contains("A=0,B/0=100,B/1=200");
+ assertThat(checkin).doesNotContain("C=");
+ }
+
+ private String toString(BatteryStats.HistoryItem item, boolean checkin) {
+ BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter();
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ printer.printNextItem(pw, item, 0, checkin, /* verbose */ true);
+ pw.flush();
+ return writer.toString();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java
index 8a0f924..122f7eb 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java
@@ -26,6 +26,7 @@
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryStats;
import android.util.SparseArray;
import android.util.SparseLongArray;
@@ -236,6 +237,17 @@
assertEquals(0, snapshot.getOtherOrdinalNames().length);
}
+ @Test
+ public void getMeasuredEnergyDetails() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+ snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
+ MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1);
+ BatteryStats.MeasuredEnergyDetails details = snapshot.getMeasuredEnergyDetails(delta);
+ assertThat(details.consumers).hasLength(4);
+ assertThat(details.chargeUC).isEqualTo(new long[]{2667, 3200000, 0, 0});
+ assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
+ }
+
private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
final EnergyConsumer ec = new EnergyConsumer();
ec.id = id;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 8ba9af0..49879efe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -15,6 +15,11 @@
*/
package com.android.server.notification;
+import static android.content.Context.DEVICE_POLICY_SERVICE;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
@@ -34,6 +39,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -94,6 +101,7 @@
private UserManager mUm;
@Mock
private ManagedServices.UserProfiles mUserProfiles;
+ @Mock private DevicePolicyManager mDpm;
Object mLock = new Object();
UserInfo mZero = new UserInfo(0, "zero", 0);
@@ -122,6 +130,7 @@
getContext().setMockPackageManager(mPm);
getContext().addMockSystemService(Context.USER_SERVICE, mUm);
+ getContext().addMockSystemService(DEVICE_POLICY_SERVICE, mDpm);
List<UserInfo> users = new ArrayList<>();
users.add(mZero);
@@ -1694,6 +1703,58 @@
assertEquals(new ArrayMap(), mService.mIsUserChanged);
}
+ @Test
+ public void testInfoIsPermittedForProfile_notAllowed() {
+ when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false);
+
+ IInterface service = mock(IInterface.class);
+ when(service.asBinder()).thenReturn(mock(IBinder.class));
+ ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, APPROVAL_BY_PACKAGE);
+ services.registerSystemService(service, null, 10, 1000);
+ ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service);
+
+ assertFalse(info.isPermittedForProfile(0));
+ }
+
+ @Test
+ public void testInfoIsPermittedForProfile_allows() {
+ when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true);
+ when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true);
+
+ IInterface service = mock(IInterface.class);
+ when(service.asBinder()).thenReturn(mock(IBinder.class));
+ ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, APPROVAL_BY_PACKAGE);
+ services.registerSystemService(service, null, 10, 1000);
+ ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service);
+ info.component = new ComponentName("a","b");
+
+ assertTrue(info.isPermittedForProfile(0));
+ }
+
+ @Test
+ public void testUserProfiles_canProfileUseBoundServices_managedProfile() {
+ List<UserInfo> users = new ArrayList<>();
+ UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0);
+ profile.userType = USER_TYPE_FULL_SECONDARY;
+ users.add(profile);
+ UserInfo managed = new UserInfo(12, "12", 0);
+ managed.userType = USER_TYPE_PROFILE_MANAGED;
+ users.add(managed);
+ UserInfo clone = new UserInfo(13, "13", 0);
+ clone.userType = USER_TYPE_PROFILE_CLONE;
+ users.add(clone);
+ when(mUm.getProfiles(ActivityManager.getCurrentUser())).thenReturn(users);
+
+ ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles();
+ profiles.updateCache(mContext);
+
+ assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser()));
+ assertFalse(profiles.canProfileUseBoundServices(12));
+ assertFalse(profiles.canProfileUseBoundServices(13));
+ }
+
private void resetComponentsAndPackages() {
ArrayMap<Integer, ArrayMap<Integer, String>> empty = new ArrayMap(1);
ArrayMap<Integer, String> emptyPkgs = new ArrayMap(0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ec48e23..894a901 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -58,6 +58,9 @@
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -212,6 +215,7 @@
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -353,6 +357,8 @@
@Mock
UserManager mUm;
@Mock
+ UserManagerInternal mUmInternal;
+ @Mock
NotificationHistoryManager mHistoryManager;
@Mock
StatsManager mStatsManager;
@@ -394,6 +400,8 @@
DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mUmInternal);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
@@ -4464,6 +4472,33 @@
}
@Test
+ public void testReadPolicyXml_doesNotRestoreManagedServicesForCloneUser() throws Exception {
+ final String policyXml = "<notification-policy version=\"1\">"
+ + "<ranking></ranking>"
+ + "<enabled_listeners>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</enabled_listeners>"
+ + "<enabled_assistants>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</enabled_assistants>"
+ + "<dnd_apps>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</dnd_apps>"
+ + "</notification-policy>";
+ UserInfo ui = new UserInfo();
+ ui.id = 10;
+ ui.userType = USER_TYPE_PROFILE_CLONE;
+ when(mUmInternal.getUserInfo(10)).thenReturn(ui);
+ mService.readPolicyXml(
+ new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
+ true,
+ 10);
+ verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
+ verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
+ verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
+ }
+
+ @Test
public void testReadPolicyXml_doesNotRestoreManagedServicesForManagedUser() throws Exception {
final String policyXml = "<notification-policy version=\"1\">"
+ "<ranking></ranking>"
@@ -4477,7 +4512,10 @@
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- when(mUm.isManagedProfile(10)).thenReturn(true);
+ UserInfo ui = new UserInfo();
+ ui.id = 10;
+ ui.userType = USER_TYPE_PROFILE_MANAGED;
+ when(mUmInternal.getUserInfo(10)).thenReturn(ui);
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
@@ -4501,7 +4539,10 @@
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- when(mUm.isManagedProfile(10)).thenReturn(false);
+ UserInfo ui = new UserInfo();
+ ui.id = 10;
+ ui.userType = USER_TYPE_FULL_SECONDARY;
+ when(mUmInternal.getUserInfo(10)).thenReturn(ui);
mService.readPolicyXml(
new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
true,
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 7c6fd05..49af2c1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -21,6 +21,7 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
+import android.provider.Settings;
import android.view.Display;
import org.junit.Test;
@@ -81,4 +82,17 @@
sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 0);
mPhoneWindowManager.assertNoPowerSleep();
}
+
+ /**
+ * When a phone call is active, and INCALL_POWER_BUTTON_BEHAVIOR_HANGUP is enabled, then the
+ * power button should only stop phone call. The screen should not be turned off (power sleep
+ * should not be activated).
+ */
+ @Test
+ public void testIgnoreSinglePressWhenEndCall() {
+ mPhoneWindowManager.overrideIncallPowerBehavior(
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
+ sendKey(KEYCODE_POWER);
+ mPhoneWindowManager.assertNoPowerSleep();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 9d57f48..ee11ac8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -298,6 +298,18 @@
Mockito.reset(mPowerManager);
}
+ void overrideIncallPowerBehavior(int behavior) {
+ mPhoneWindowManager.mIncallPowerBehavior = behavior;
+ setPhoneCallIsInProgress();
+ }
+
+ void setPhoneCallIsInProgress() {
+ // Let device has an ongoing phone call.
+ doReturn(false).when(mTelecomManager).isRinging();
+ doReturn(true).when(mTelecomManager).isInCall();
+ doReturn(true).when(mTelecomManager).endCall();
+ }
+
/**
* Below functions will check the policy behavior could be invoked.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
index 8b0a540..58b0e16 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
@@ -36,6 +36,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
@@ -88,7 +90,8 @@
mPossibleDisplayInfo.add(mDefaultDisplayInfo);
mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
- Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(
+ DEFAULT_DISPLAY);
// An entry for rotation 0, for a display that can be in a single state.
assertThat(displayInfos.size()).isEqualTo(1);
assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
@@ -105,9 +108,10 @@
mPossibleDisplayInfo.add(mSecondDisplayInfo);
mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
- Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
- Set<DisplayInfo> defaultDisplayInfos = new ArraySet<>();
- Set<DisplayInfo> secondDisplayInfos = new ArraySet<>();
+ List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(
+ DEFAULT_DISPLAY);
+ List<DisplayInfo> defaultDisplayInfos = new ArrayList<>();
+ List<DisplayInfo> secondDisplayInfos = new ArrayList<>();
for (DisplayInfo di : displayInfos) {
if ((di.flags & FLAG_PRESENTATION) != 0) {
secondDisplayInfos.add(di);
@@ -137,12 +141,13 @@
mPossibleDisplayInfo.add(mSecondDisplayInfo);
mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId);
- Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(
+ DEFAULT_DISPLAY);
// An entry for rotation 0, for the default display.
assertThat(displayInfos).hasSize(1);
assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
- Set<DisplayInfo> secondStateEntries =
+ List<DisplayInfo> secondStateEntries =
mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId);
// An entry for rotation 0, for the second display.
assertThat(secondStateEntries).hasSize(1);
@@ -157,7 +162,7 @@
outDisplayInfo.logicalHeight = logicalBounds.height();
}
- private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos,
+ private static void assertPossibleDisplayInfoEntries(List<DisplayInfo> displayInfos,
DisplayInfo expectedDisplayInfo) {
for (DisplayInfo displayInfo : displayInfos) {
assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index ab7e8ea..9e6c4c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -235,6 +235,7 @@
doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));
mStubbedWakeLock = createStubbedWakeLock(false /* needVerification */);
doReturn(mStubbedWakeLock).when(pm).newWakeLock(anyInt(), anyString());
+ doReturn(mStubbedWakeLock).when(pm).newWakeLock(anyInt(), anyString(), anyInt());
// DisplayManagerInternal
final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class);
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 24cdc0f..9bdf750 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
+import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -49,8 +50,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
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.Mockito.clearInvocations;
@@ -78,7 +79,6 @@
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
import androidx.test.filters.SmallTest;
@@ -136,6 +136,7 @@
mTaskFragment =
new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
mTransaction = new WindowContainerTransaction();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
mDefinition = new RemoteAnimationDefinition();
mErrorToken = new Binder();
@@ -155,11 +156,16 @@
doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
// To prevent it from calling the real server.
- doNothing().when(mOrganizer).onTransactionHandled(any(), any());
+ doNothing().when(mOrganizer).applyTransaction(any(), anyInt(), anyBoolean());
+ doNothing().when(mOrganizer).onTransactionHandled(any(), any(), anyInt(), anyBoolean());
+
+ mController.registerOrganizer(mIOrganizer);
}
@Test
public void testCallTaskFragmentCallbackWithoutRegister_throwsException() {
+ mController.unregisterOrganizer(mIOrganizer);
+
doReturn(mTask).when(mTaskFragment).getTask();
assertThrows(IllegalArgumentException.class, () -> mController
@@ -175,8 +181,6 @@
@Test
public void testOnTaskFragmentAppeared() {
- mController.registerOrganizer(mIOrganizer);
-
// No-op when the TaskFragment is not attached.
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -194,7 +198,6 @@
@Test
public void testOnTaskFragmentInfoChanged() {
- mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
// No-op if onTaskFragmentAppeared is not called yet.
@@ -233,8 +236,6 @@
@Test
public void testOnTaskFragmentVanished() {
- mController.registerOrganizer(mIOrganizer);
-
mTaskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
@@ -244,7 +245,6 @@
@Test
public void testOnTaskFragmentVanished_clearUpRemaining() {
- mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
// Not trigger onTaskFragmentAppeared.
@@ -270,7 +270,6 @@
@Test
public void testOnTaskFragmentParentInfoChanged() {
- mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
mTask.getConfiguration().smallestScreenWidthDp = 10;
@@ -317,7 +316,6 @@
public void testOnTaskFragmentError() {
final Throwable exception = new IllegalArgumentException("Test exception");
- mController.registerOrganizer(mIOrganizer);
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
mErrorToken, null /* taskFragment */, HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
exception);
@@ -332,7 +330,6 @@
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- mController.registerOrganizer(mIOrganizer);
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final Task task = activity.getTask();
activity.info.applicationInfo.uid = uid;
@@ -375,8 +372,6 @@
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- mController.registerOrganizer(mIOrganizer);
- mOrganizer.applyTransaction(mTransaction);
final Task task = createTask(mDisplayContent);
task.addChild(mTaskFragment, POSITION_TOP);
final ActivityRecord activity = createActivityRecord(task);
@@ -404,7 +399,7 @@
assertEquals(activity.intent, change.getActivityIntent());
assertNotEquals(activity.token, change.getActivityToken());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertEquals(mTaskFragment, activity.getTaskFragment());
// The temporary token can only be used once.
@@ -414,7 +409,6 @@
@Test
public void testRegisterRemoteAnimations() {
- mController.registerOrganizer(mIOrganizer);
mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
@@ -425,23 +419,7 @@
}
@Test
- public void testWindowContainerTransaction_setTaskFragmentOrganizer() {
- mOrganizer.applyTransaction(mTransaction);
-
- assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
-
- mTransaction = new WindowContainerTransaction();
- mOrganizer.applySyncTransaction(
- mTransaction, mock(WindowContainerTransactionCallback.class));
-
- assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
- }
-
- @Test
- public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment()
- throws RemoteException {
- mOrganizer.applyTransaction(mTransaction);
-
+ public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() {
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
@@ -457,10 +435,7 @@
@Test
- public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment()
- throws RemoteException {
- mController.registerOrganizer(mIOrganizer);
- mOrganizer.applyTransaction(mTransaction);
+ public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() {
doReturn(true).when(mTaskFragment).isAttached();
// Throw exception if the transaction is trying to change a window that is not organized by
@@ -486,13 +461,10 @@
}
@Test
- public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
- throws RemoteException {
- mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
+ public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots() {
final TaskFragment taskFragment2 =
new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
- mOrganizer.applyTransaction(mTransaction);
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
@@ -513,9 +485,7 @@
}
@Test
- public void testApplyTransaction_enforceHierarchyChange_createTaskFragment()
- throws RemoteException {
- mController.registerOrganizer(mIOrganizer);
+ public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
@@ -526,11 +496,10 @@
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class),
null /* options */);
- mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment.
- final TaskFragment taskFragment = mAtm.mWindowOrganizerController
- .getTaskFragment(fragmentToken);
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
assertNotNull(taskFragment);
assertEquals(ownerActivity.getTask(), taskFragment.getTask());
}
@@ -539,7 +508,6 @@
public void testApplyTransaction_enforceTaskFragmentOrganized_startActivityInTaskFragment() {
final Task task = createTask(mDisplayContent);
final ActivityRecord ownerActivity = createActivityRecord(task);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -562,7 +530,6 @@
public void testApplyTransaction_enforceTaskFragmentOrganized_reparentActivityInTaskFragment() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -583,7 +550,6 @@
@Test
public void testApplyTransaction_enforceTaskFragmentOrganized_setAdjacentTaskFragments() {
final Task task = createTask(mDisplayContent);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -623,7 +589,6 @@
@Test
public void testApplyTransaction_enforceTaskFragmentOrganized_requestFocusOnTaskFragment() {
final Task task = createTask(mDisplayContent);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -642,44 +607,38 @@
}
@Test
- public void testApplyTransaction_createTaskFragment_failForDifferentUid()
- throws RemoteException {
- mController.registerOrganizer(mIOrganizer);
+ public void testApplyTransaction_createTaskFragment_failForDifferentUid() {
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final int uid = Binder.getCallingUid();
final IBinder fragmentToken = new Binder();
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken, activity.token).build();
- mOrganizer.applyTransaction(mTransaction);
mTransaction.createTaskFragment(params);
// Fail to create TaskFragment when the task uid is different from caller.
activity.info.applicationInfo.uid = uid;
activity.getTask().effectiveUid = uid + 1;
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
// Fail to create TaskFragment when the task uid is different from owner activity.
activity.info.applicationInfo.uid = uid + 1;
activity.getTask().effectiveUid = uid;
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
// Successfully created a TaskFragment for same uid.
activity.info.applicationInfo.uid = uid;
activity.getTask().effectiveUid = uid;
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@Test
- public void testApplyTransaction_enforceHierarchyChange_reparentChildren()
- throws RemoteException {
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
+ public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached();
// Throw exception if the transaction is trying to change a window that is not organized by
@@ -699,14 +658,12 @@
}
@Test
- public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate()
- throws RemoteException {
+ public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -717,15 +674,13 @@
doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
clearInvocations(mAtm.mRootWindowContainer);
- mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
@Test
public void testApplyTransaction_requestFocusOnTaskFragment() {
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
final Task task = createTask(mDisplayContent);
final IBinder token0 = new Binder();
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
@@ -750,7 +705,7 @@
final ActivityRecord activityInOtherTask = createActivityRecord(mDefaultDisplay);
mDisplayContent.setFocusedApp(activityInOtherTask);
mTransaction.requestFocusOnTaskFragment(token0);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertEquals(activityInOtherTask, mDisplayContent.mFocusedApp);
@@ -758,7 +713,7 @@
activity0.setState(ActivityRecord.State.PAUSED, "test");
activity1.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.setFocusedApp(activity1);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertEquals(activity1, mDisplayContent.mFocusedApp);
@@ -766,28 +721,29 @@
// has a resumed activity.
activity0.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.setFocusedApp(activity1);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertEquals(activity0, mDisplayContent.mFocusedApp);
}
@Test
public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
- mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
// Nothing should happen as the organizer is not registered.
- assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
mController.registerOrganizer(mIOrganizer);
- mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
// Successfully created when the organizer is registered.
- assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@Test
@@ -799,13 +755,13 @@
// Not allow to start activity in a TaskFragment that is in a PIP Task.
mTransaction.startActivityInTaskFragment(
- mFragmentToken, activity.token, new Intent(), null /* activityOptions */)
+ mFragmentToken, activity.token, new Intent(), null /* activityOptions */)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(),
any(), any(), anyInt(), anyInt(), any());
- verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+ verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
eq(HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT),
any(IllegalArgumentException.class));
@@ -820,7 +776,7 @@
// Not allow to reparent activity to a TaskFragment that is in a PIP Task.
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
@@ -836,9 +792,9 @@
// Not allow to set adjacent on a TaskFragment that is in a PIP Task.
mTransaction.setAdjacentTaskFragments(mFragmentToken, null /* fragmentToken2 */,
- null /* options */)
+ null /* options */)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment),
@@ -849,7 +805,6 @@
@Test
public void testTaskFragmentInPip_createTaskFragment() {
- mController.registerOrganizer(mIOrganizer);
final Task pipTask = createTask(mDisplayContent, WINDOWING_MODE_PINNED,
ACTIVITY_TYPE_STANDARD);
final ActivityRecord activity = createActivityRecord(pipTask);
@@ -859,7 +814,7 @@
// Not allow to create TaskFragment in a PIP Task.
createTaskFragmentFromOrganizer(mTransaction, activity, fragmentToken);
mTransaction.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(null), eq(HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT),
@@ -875,7 +830,7 @@
// Not allow to delete a TaskFragment that is in a PIP Task.
mTransaction.deleteTaskFragment(mFragmentWindowToken)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT),
@@ -885,7 +840,7 @@
// Allow organizer to delete empty TaskFragment for cleanup.
final Task task = mTaskFragment.getTask();
mTaskFragment.removeChild(mTaskFragment.getTopMostActivity());
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertNull(mWindowOrganizerController.getTaskFragment(mFragmentToken));
assertNull(task.getTopChild());
@@ -916,7 +871,6 @@
doReturn(false).when(task).shouldBeVisible(any());
// Sending events
- mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -942,7 +896,6 @@
taskFragment.setResumedActivity(null, "test");
// Sending events
- mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -977,7 +930,6 @@
assertTrue(parentTask.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
- mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -1013,7 +965,6 @@
assertTrue(task.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
- mController.registerOrganizer(mIOrganizer);
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
@@ -1039,13 +990,11 @@
* {@link WindowOrganizerController}.
*/
@Test
- public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment()
- throws RemoteException {
- mController.registerOrganizer(mIOrganizer);
+ public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
final IBinder fragmentToken = new Binder();
createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
- mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
assertNotNull(taskFragment);
@@ -1060,9 +1009,7 @@
* its parent bounds.
*/
@Test
- public void testUntrustedEmbedding_configChange() throws RemoteException {
- mController.registerOrganizer(mIOrganizer);
- mOrganizer.applyTransaction(mTransaction);
+ public void testUntrustedEmbedding_configChange() {
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
"Test:TaskFragmentOrganizer" /* processName */);
doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode();
@@ -1123,8 +1070,6 @@
// Make minWidth/minHeight exceeds the TaskFragment bounds.
activity.info.windowLayout = new ActivityInfo.WindowLayout(
0, 0, 0, 0, 0, mTaskFragBounds.width() + 10, mTaskFragBounds.height() + 10);
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
@@ -1137,7 +1082,7 @@
// minimum dimensions.
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
@@ -1148,8 +1093,6 @@
@Test
public void testMinDimensionViolation_ReparentChildren() {
final Task task = createTask(mDisplayContent);
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
final IBinder oldFragToken = new Binder();
final TaskFragment oldTaskFrag = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
@@ -1175,7 +1118,7 @@
mTransaction.reparentChildren(oldTaskFrag.mRemoteToken.toWindowContainerToken(),
mTaskFragment.mRemoteToken.toWindowContainerToken())
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
@@ -1186,8 +1129,6 @@
@Test
public void testMinDimensionViolation_SetBounds() {
final Task task = createTask(mDisplayContent);
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.createActivityCount(1)
@@ -1206,7 +1147,7 @@
// minimum dimensions.
mTransaction.setBounds(mTaskFragment.mRemoteToken.toWindowContainerToken(), mTaskFragBounds)
.setErrorCallbackToken(mErrorToken);
- mWindowOrganizerController.applyTransaction(mTransaction);
+ assertApplyTransactionAllowed(mTransaction);
assertWithMessage("setBounds must not be performed.")
.that(mTaskFragment.getBounds()).isEqualTo(task.getBounds());
@@ -1214,18 +1155,17 @@
@Test
public void testOnTransactionReady_invokeOnTransactionHandled() {
- mController.registerOrganizer(mIOrganizer);
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
mOrganizer.onTransactionReady(transaction);
// Organizer should always trigger #onTransactionHandled when receives #onTransactionReady
- verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any());
- verify(mOrganizer, never()).applyTransaction(any());
+ verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
+ anyInt(), anyBoolean());
+ verify(mOrganizer, never()).applyTransaction(any(), anyInt(), anyBoolean());
}
@Test
public void testDispatchTransaction_deferTransitionReady() {
- mController.registerOrganizer(mIOrganizer);
setupMockParent(mTaskFragment, mTask);
final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<WindowContainerTransaction> wctCaptor =
@@ -1238,12 +1178,15 @@
// Defer transition when send TaskFragment transaction during transition collection.
verify(mTransitionController).deferTransitionReady();
- verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture());
+ verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture(),
+ anyInt(), anyBoolean());
- mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue());
+ final IBinder transactionToken = tokenCaptor.getValue();
+ final WindowContainerTransaction wct = wctCaptor.getValue();
+ wct.setTaskFragmentOrganizer(mIOrganizer);
+ mController.onTransactionHandled(transactionToken, wct, getTransitionType(wct),
+ false /* shouldApplyIndependently */);
- // Apply the organizer change and continue transition.
- verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue());
verify(mTransitionController).continueTransitionReady();
}
@@ -1258,7 +1201,7 @@
ownerActivity.getTask().effectiveUid = uid;
final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
mOrganizerToken, fragmentToken, ownerActivity.token).build();
- mOrganizer.applyTransaction(wct);
+ wct.setTaskFragmentOrganizer(mIOrganizer);
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
wct.createTaskFragment(params);
@@ -1266,22 +1209,14 @@
/** Asserts that applying the given transaction will throw a {@link SecurityException}. */
private void assertApplyTransactionDisallowed(WindowContainerTransaction t) {
- assertThrows(SecurityException.class, () -> {
- try {
- mAtm.getWindowOrganizerController().applyTransaction(t);
- } catch (RemoteException e) {
- fail();
- }
- });
+ assertThrows(SecurityException.class, () ->
+ mController.applyTransaction(t, getTransitionType(t),
+ false /* shouldApplyIndependently */));
}
/** Asserts that applying the given transaction will not throw any exception. */
private void assertApplyTransactionAllowed(WindowContainerTransaction t) {
- try {
- mAtm.getWindowOrganizerController().applyTransaction(t);
- } catch (RemoteException e) {
- fail();
- }
+ mController.applyTransaction(t, getTransitionType(t), false /* shouldApplyIndependently */);
}
/** Asserts that there will be a transaction for TaskFragment appeared. */
@@ -1367,8 +1302,6 @@
/** Setups an embedded TaskFragment in a PIP Task. */
private void setupTaskFragmentInPip() {
- mOrganizer.applyTransaction(mTransaction);
- mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()
.setFragmentToken(mFragmentToken)
@@ -1376,8 +1309,7 @@
.createActivityCount(1)
.build();
mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
- mAtm.mWindowOrganizerController.mLaunchTaskFragments
- .put(mFragmentToken, mTaskFragment);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index fba4ff1..8288713 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -33,17 +33,21 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+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.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
@@ -61,9 +65,12 @@
import com.android.server.wm.utils.WmDisplayCutout;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
/**
* Tests for the {@link WallpaperController} class.
*
@@ -74,6 +81,18 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class WallpaperControllerTests extends WindowTestsBase {
+ private static final int INITIAL_WIDTH = 600;
+ private static final int INITIAL_HEIGHT = 900;
+ private static final int SECOND_WIDTH = 300;
+
+ @Before
+ public void setup() {
+ Resources resources = mWm.mContext.getResources();
+ spyOn(resources);
+ doReturn(false).when(resources).getBoolean(
+ com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ }
+
@Test
public void testWallpaperScreenshot() {
WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
@@ -365,6 +384,108 @@
assertTrue(token.isVisible());
}
+ private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) {
+ spyOn(dc.mWmService);
+ DisplayInfo firstDisplay = dc.getDisplayInfo();
+ DisplayInfo secondDisplay = new DisplayInfo(firstDisplay);
+ // Second display is narrower than first display.
+ secondDisplay.logicalWidth = width;
+ secondDisplay.logicalHeight = height;
+ doReturn(List.of(firstDisplay, secondDisplay)).when(
+ dc.mWmService).getPossibleDisplayInfoLocked(anyInt());
+ }
+
+ private static void resizeDisplayAndWallpaper(DisplayContent dc, WindowState wallpaperWindow,
+ int width, int height) {
+ dc.setBounds(0, 0, width, height);
+ dc.updateOrientation();
+ dc.sendNewConfiguration();
+ spyOn(wallpaperWindow);
+ doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getLastReportedBounds();
+ }
+
+ @Test
+ public void testUpdateWallpaperOffset_initial_shouldCenterDisabled() {
+ final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
+ INITIAL_HEIGHT).build();
+ dc.mWallpaperController.setShouldOffsetWallpaperCenter(false);
+ prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT);
+ final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH,
+ INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Wallpaper centering is disabled, so no offset.
+ assertThat(wallpaperWindow.mXOffset).isEqualTo(0);
+ assertThat(wallpaperWindow.mYOffset).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateWallpaperOffset_initial_shouldCenterEnabled() {
+ final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
+ INITIAL_HEIGHT).build();
+ dc.mWallpaperController.setShouldOffsetWallpaperCenter(true);
+ prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT);
+ final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH,
+ INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Wallpaper matches first display, so has no offset.
+ assertThat(wallpaperWindow.mXOffset).isEqualTo(0);
+ assertThat(wallpaperWindow.mYOffset).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateWallpaperOffset_resize_shouldCenterEnabled() {
+ final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
+ INITIAL_HEIGHT).build();
+ dc.mWallpaperController.setShouldOffsetWallpaperCenter(true);
+ prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT);
+ final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH,
+ INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Resize display to match second display bounds.
+ resizeDisplayAndWallpaper(dc, wallpaperWindow, SECOND_WIDTH, INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Wallpaper is 300 wider than second display.
+ assertThat(wallpaperWindow.mXOffset).isEqualTo(-Math.abs(INITIAL_WIDTH - SECOND_WIDTH) / 2);
+ assertThat(wallpaperWindow.mYOffset).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateWallpaperOffset_resize_shouldCenterDisabled() {
+ final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
+ INITIAL_HEIGHT).build();
+ dc.mWallpaperController.setShouldOffsetWallpaperCenter(false);
+ prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT);
+ final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH,
+ INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Resize display to match second display bounds.
+ resizeDisplayAndWallpaper(dc, wallpaperWindow, SECOND_WIDTH, INITIAL_HEIGHT);
+
+ dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false);
+
+ // Wallpaper is 300 wider than second display, but offset disabled.
+ assertThat(wallpaperWindow.mXOffset).isEqualTo(0);
+ assertThat(wallpaperWindow.mYOffset).isEqualTo(0);
+ }
+
+ private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
+ // Wallpaper is cropped to match first display.
+ wallpaperWindow.getWindowFrames().mParentFrame.set(new Rect(0, 0, width, height));
+ wallpaperWindow.getWindowFrames().mFrame.set(0, 0, width, height);
+ return wallpaperWindow;
+ }
+
private WindowState createWallpaperWindow(DisplayContent dc) {
final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
true /* explicit */, dc, true /* ownerCanManageAppTokens */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
new file mode 100644
index 0000000..d255271
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atLeast;
+
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link WindowContainerTransaction}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:WindowContainerTransactionTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class WindowContainerTransactionTests extends WindowTestsBase {
+
+ @Test
+ public void testRemoveTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ WindowContainerToken token = task.getTaskInfo().token;
+ wct.removeTask(token);
+ applyTransaction(wct);
+
+ // There is still an activity to be destroyed, so the task is not removed immediately.
+ assertNotNull(task.getParent());
+ assertTrue(rootTask.hasChild());
+ assertTrue(task.hasChild());
+ assertTrue(activity.finishing);
+
+ activity.destroyed("testRemoveContainer");
+ // Assert that the container was removed after the activity is destroyed.
+ assertNull(task.getParent());
+ assertEquals(0, task.getChildCount());
+ assertNull(activity.getParent());
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task);
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
+ }
+
+ private Task createTask(int taskId) {
+ return new Task.Builder(mAtm)
+ .setTaskId(taskId)
+ .setIntent(new Intent())
+ .setRealActivity(ActivityBuilder.getDefaultComponent())
+ .setEffectiveUid(10050)
+ .buildInner();
+ }
+
+ private void applyTransaction(@NonNull WindowContainerTransaction t) {
+ if (!t.isEmpty()) {
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..b003f59 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -88,8 +88,8 @@
public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
/**
- * Reason code (returned via {@link #getReason()}), which indicates that the video telephony
- * call was disconnected because IMS access is blocked.
+ * Reason code (returned via {@link #getReason()}), which indicates that the call was
+ * disconnected because IMS access is blocked.
*/
public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";