Merge "Add Sv2 to aapt2 codenames" into sc-v2-dev
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8e1f263..76f8731 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -215,6 +215,14 @@
"android.activity.launchRootTaskToken";
/**
+ * The {@link com.android.server.wm.TaskFragment} token the activity should be launched into.
+ * @see #setLaunchTaskFragmentToken(IBinder)
+ * @hide
+ */
+ public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN =
+ "android.activity.launchTaskFragmentToken";
+
+ /**
* The windowing mode the activity should be launched into.
* @hide
*/
@@ -396,6 +404,7 @@
private int mCallerDisplayId = INVALID_DISPLAY;
private WindowContainerToken mLaunchTaskDisplayArea;
private WindowContainerToken mLaunchRootTask;
+ private IBinder mLaunchTaskFragmentToken;
@WindowConfiguration.WindowingMode
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@WindowConfiguration.ActivityType
@@ -1138,6 +1147,7 @@
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
+ mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
@@ -1473,6 +1483,17 @@
}
/** @hide */
+ public IBinder getLaunchTaskFragmentToken() {
+ return mLaunchTaskFragmentToken;
+ }
+
+ /** @hide */
+ public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
+ mLaunchTaskFragmentToken = taskFragmentToken;
+ return this;
+ }
+
+ /** @hide */
public int getLaunchWindowingMode() {
return mLaunchWindowingMode;
}
@@ -1882,6 +1903,9 @@
if (mLaunchRootTask != null) {
b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
}
+ if (mLaunchTaskFragmentToken != null) {
+ b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
+ }
if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7d70441..f212b6d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5666,8 +5666,8 @@
*/
private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
boolean preserveWindow) {
- if (r.activity.mFinished || r.token instanceof Binder) {
- // Do not schedule relaunch if the activity is finishing or not a local object (e.g.
+ if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
+ // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
// created by ActivtiyGroup that server side doesn't recognize it).
return;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bb7cdfa..d932a29 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7205,6 +7205,34 @@
* @hide
*/
public interface OnOpStartedListener {
+
+ /**
+ * Represents a start operation that was unsuccessful
+ * @hide
+ */
+ public int START_TYPE_FAILED = 0;
+
+ /**
+ * Represents a successful start operation
+ * @hide
+ */
+ public int START_TYPE_STARTED = 1;
+
+ /**
+ * Represents an operation where a restricted operation became unrestricted, and resumed.
+ * @hide
+ */
+ public int START_TYPE_RESUMED = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ START_TYPE_FAILED,
+ START_TYPE_STARTED,
+ START_TYPE_RESUMED
+ })
+ public @interface StartedType {}
+
/**
* Called when an op was started.
*
@@ -7213,11 +7241,35 @@
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
* @param attributionTag The attribution tag performing the operation.
- * @param flags The flags of this op
+ * @param flags The flags of this op.
* @param result The result of the start.
*/
void onOpStarted(int op, int uid, String packageName, String attributionTag,
@OpFlags int flags, @Mode int result);
+
+ /**
+ * Called when an op was started.
+ *
+ * Note: This is only for op starts. It is not called when an op is noted or stopped.
+ * By default, unless this method is overridden, no code will be executed for resume
+ * events.
+ * @param op The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param attributionTag The attribution tag performing the operation.
+ * @param flags The flags of this op.
+ * @param result The result of the start.
+ * @param startType The start type of this start event. Either failed, resumed, or started.
+ * @param attributionFlags The location of this started op in an attribution chain.
+ * @param attributionChainId The ID of the attribution chain of this op, if it is in one.
+ */
+ default void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @OpFlags int flags, @Mode int result, @StartedType int startType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ if (startType != START_TYPE_RESUMED) {
+ onOpStarted(op, uid, packageName, attributionTag, flags, result);
+ }
+ }
}
AppOpsManager(Context context, IAppOpsService service) {
@@ -7858,8 +7910,10 @@
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
- int flags, int mode) {
- callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode);
+ int flags, int mode, int startType, int attributionFlags,
+ int attributionChainId) {
+ callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
+ startType, attributionFlags, attributionChainId);
}
};
mStartedWatchers.put(callback, cb);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b2184fe..45db0f6 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -743,6 +743,17 @@
}
/**
+ * This overload is used for notifying the {@link android.window.TaskFragmentOrganizer}
+ * implementation internally about started activities.
+ *
+ * @see #onStartActivity(Intent)
+ * @hide
+ */
+ public ActivityResult onStartActivity(Context who, Intent intent, Bundle options) {
+ return onStartActivity(intent);
+ }
+
+ /**
* Used for intercepting any started activity.
*
* <p> A non-null return value here will be considered a hit for this monitor.
@@ -1722,7 +1733,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1790,7 +1804,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intents[0]);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intents[0], options);
}
if (result != null) {
am.mHits++;
@@ -1861,7 +1878,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1928,7 +1948,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1974,7 +1997,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -2021,7 +2047,10 @@
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 9acf9bf..afefcbe 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -75,7 +75,7 @@
ImageWriter writer = null;
Image img = null;
SurfaceInfo surfaceInfo = new SurfaceInfo();
- int nativeFormat = SurfaceUtils.getSurfaceFormat(s);
+ int nativeFormat = SurfaceUtils.detectSurfaceFormat(s);
int dataspace = SurfaceUtils.getSurfaceDataspace(s);
Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
surfaceInfo.mFormat = nativeFormat;
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index 57d8ded..fd1a331 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -160,6 +160,23 @@
}
/**
+ * Detect and retrieve the Surface format without any
+ * additional overrides.
+ *
+ * @param surface The surface to be queried for format.
+ * @return format of the surface.
+ *
+ * @throws IllegalArgumentException if the surface is already abandoned.
+ */
+ public static int detectSurfaceFormat(Surface surface) {
+ checkNotNull(surface);
+ int surfaceType = nativeDetectSurfaceType(surface);
+ if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+
+ return surfaceType;
+ }
+
+ /**
* Get the Surface dataspace.
*
* @param surface The surface to be queried for dataspace.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 03f94c5..19f204b 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -31,7 +31,9 @@
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
@@ -63,7 +65,8 @@
*
* @hide
*/
-public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
+public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
+ AppOpsManager.OnOpStartedListener {
/** Whether to show the mic and camera icons. */
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -160,9 +163,10 @@
mUserContexts = new ArrayMap<>();
mUserContexts.put(Process.myUserHandle(), mContext);
// TODO ntmyren: make this listen for flag enable/disable changes
- String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
- mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
- context.getMainExecutor(), this);
+ String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
+ mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
+ int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
+ mAppOpsManager.startWatchingStarted(ops, this);
}
private Context getUserContext(UserHandle user) {
@@ -182,25 +186,65 @@
public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
- if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
- || attributionFlags == ATTRIBUTION_FLAGS_NONE
- || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
- // If this is not a chain, or it is untrusted, return
+ if (active) {
+ // Started callback handles these
return;
}
- if (!active) {
- // if any link in the chain is finished, remove the chain.
- // TODO ntmyren: be smarter about this
- mAttributionChains.remove(attributionChainId);
+ // if any link in the chain is finished, remove the chain. Then, find any other chains that
+ // contain this op/package/uid/tag combination, and remove them, as well.
+ // TODO ntmyren: be smarter about this
+ mAttributionChains.remove(attributionChainId);
+ int numChains = mAttributionChains.size();
+ ArrayList<Integer> toRemove = new ArrayList<>();
+ for (int i = 0; i < numChains; i++) {
+ int chainId = mAttributionChains.keyAt(i);
+ ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
+ int chainSize = chain.size();
+ for (int j = 0; j < chainSize; j++) {
+ AccessChainLink link = chain.get(j);
+ if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
+ toRemove.add(chainId);
+ break;
+ }
+ }
+ }
+ mAttributionChains.removeAll(toRemove);
+ }
+
+ @Override
+ public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+ // not part of an attribution chain. Do nothing
+ }
+
+ @Override
+ public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result,
+ @StartedType int startedType, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
+ if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
+ || attributionFlags == ATTRIBUTION_FLAGS_NONE
+ || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
+ // If this is not a successful start, or it is not a chain, or it is untrusted, return
return;
}
+ addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
+ attributionTag, attributionFlags, attributionChainId);
+ }
+
+ private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
+ String attributionTag, int attributionFlags, int attributionChainId) {
ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
attributionChainId, k -> new ArrayList<>());
AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
attributionFlags);
+ if (currentChain.contains(link)) {
+ return;
+ }
+
int currSize = currentChain.size();
if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
// if the list is empty, this link is the end, or the last link in the current chain
@@ -613,5 +657,21 @@
public boolean isStart() {
return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AccessChainLink)) {
+ return false;
+ }
+ AccessChainLink other = (AccessChainLink) obj;
+ return other.flags == flags && packageAndOpEquals(other.usage.op,
+ other.usage.packageName, other.usage.attributionTag, other.usage.uid);
+ }
+
+ public boolean packageAndOpEquals(String op, String packageName, String attributionTag,
+ int uid) {
+ return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName)
+ && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid;
+ }
}
}
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 3a108e7..06640cb 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,5 +18,6 @@
// Iterface to observe op starts
oneway interface IAppOpsStartedCallback {
- void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+ void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
+ int startedType, int attributionFlags, int attributionChainId);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ac39d30..1704452 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -550,6 +550,8 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
<protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+ <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+ <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
<protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
index aa050f3..2b9f952 100644
--- a/core/res/res/layout/splash_screen_view.xml
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -26,6 +26,7 @@
android:layout_width="wrap_content"
android:layout_gravity="center"
android:padding="0dp"
+ android:background="@null"
android:contentDescription="@string/splash_screen_view_icon_description"/>
<View android:id="@+id/splashscreen_branding_view"
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 199e82f..865f026 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1937,7 +1937,7 @@
<string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Phishing-Warnung"</string>
<string name="notification_work_profile_content_description" msgid="5296477955677725799">"Arbeitsprofil"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"Gewarnt"</string>
- <string name="notification_verified_content_description" msgid="6401483602782359391">"Bestätigt"</string>
+ <string name="notification_verified_content_description" msgid="6401483602782359391">"Verifiziert"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Maximieren"</string>
<string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Minimieren"</string>
<string name="expand_action_accessibility" msgid="1947657036871746627">"Maximierung ein-/auschalten"</string>
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index 4925209..872331c 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -67,7 +67,7 @@
@NonNull RippleAnimationSession enter(Canvas canvas) {
mStartTime = AnimationUtils.currentAnimationTimeMillis();
- if (isHwAccelerated(canvas)) {
+ if (useRTAnimations(canvas)) {
enterHardware((RecordingCanvas) canvas);
} else {
enterSoftware();
@@ -82,7 +82,7 @@
}
@NonNull RippleAnimationSession exit(Canvas canvas) {
- if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
+ if (useRTAnimations(canvas)) exitHardware((RecordingCanvas) canvas);
else exitSoftware();
return this;
}
@@ -102,8 +102,12 @@
return this;
}
- private boolean isHwAccelerated(Canvas canvas) {
- return canvas.isHardwareAccelerated() && !mForceSoftware;
+ private boolean useRTAnimations(Canvas canvas) {
+ if (mForceSoftware) return false;
+ if (!canvas.isHardwareAccelerated()) return false;
+ RecordingCanvas hwCanvas = (RecordingCanvas) canvas;
+ if (hwCanvas.mNode == null || !hwCanvas.mNode.isAttached()) return false;
+ return true;
}
private void exitSoftware() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
index 0f2f23e..531df30 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
@@ -159,13 +159,22 @@
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
+ void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ final TaskFragmentCreationParams fragmentOptions =
+ createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+ wct.createTaskFragment(fragmentOptions);
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ */
private void createTaskFragmentAndReparentActivity(
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
@NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
- wct.createTaskFragment(fragmentOptions)
- .reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
/**
@@ -176,11 +185,8 @@
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
@NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
@Nullable Bundle activityOptions) {
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
- wct.createTaskFragment(fragmentOptions)
- .startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent,
- activityOptions);
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
}
TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
index e43c5bf..a1ebb59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
@@ -20,9 +20,12 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityClient;
+import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application.ActivityLifecycleCallbacks;
+import android.app.Instrumentation;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -61,9 +64,13 @@
public SplitController() {
mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
+ ActivityThread activityThread = ActivityThread.currentActivityThread();
// Register a callback to be notified about activities being created.
- ActivityThread.currentActivityThread().getApplication().registerActivityLifecycleCallbacks(
+ activityThread.getApplication().registerActivityLifecycleCallbacks(
new LifecycleCallbacks());
+ // Intercept activity starts to route activities to new containers if necessary.
+ Instrumentation instrumentation = activityThread.getInstrumentation();
+ instrumentation.addMonitor(new ActivityStartMonitor());
}
public void setSplitRules(@NonNull List<ExtensionSplitRule> splitRules) {
@@ -118,7 +125,9 @@
}
container.setInfo(taskFragmentInfo);
- if (taskFragmentInfo.isEmpty()) {
+ // Check if there are no running activities - consider the container empty if there are no
+ // non-finishing activities left.
+ if (!taskFragmentInfo.hasRunningActivity()) {
cleanupContainer(container, true /* shouldFinishDependent */);
updateCallbackIfNecessary();
}
@@ -664,4 +673,40 @@
handler.post(r);
}
}
+
+ /**
+ * A monitor that intercepts all activity start requests originating in the client process and
+ * can amend them to target a specific task fragment to form a split.
+ */
+ private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+
+ @Override
+ public Instrumentation.ActivityResult onStartActivity(Context who, Intent intent,
+ Bundle options) {
+ // TODO(b/190433398): Check if the activity is configured to always be expanded.
+
+ // Check if activity should be put in a split with the activity that launched it.
+ if (!(who instanceof Activity)) {
+ return super.onStartActivity(who, intent, options);
+ }
+ final Activity launchingActivity = (Activity) who;
+
+ final ExtensionSplitPairRule splitPairRule = getSplitRule(
+ launchingActivity.getComponentName(), intent.getComponent(), getSplitRules());
+ if (splitPairRule == null) {
+ return super.onStartActivity(who, intent, options);
+ }
+
+ // Create a new split with an empty side container
+ final TaskFragmentContainer secondaryContainer = mPresenter
+ .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
+
+ // 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,
+ secondaryContainer.getTaskFragmentToken());
+
+ return super.onStartActivity(who, intent, options);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
index 7ad83aa..d8b1d2a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
@@ -83,6 +83,37 @@
}
/**
+ * Creates a new split with the primary activity and an empty secondary container.
+ * @return The newly created secondary container.
+ */
+ TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity,
+ @NonNull ExtensionSplitPairRule rule) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
+ final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+ primaryActivity, primaryRectBounds, null);
+
+ // Create new empty task fragment
+ TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+ createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
+ primaryActivity.getActivityToken(), secondaryRectBounds,
+ WINDOWING_MODE_MULTI_WINDOW);
+ secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
+
+ // Set adjacent to each other so that the containers below will be invisible.
+ wct.setAdjacentTaskFragments(
+ primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken());
+ applyTransaction(wct);
+
+ mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule);
+
+ return secondaryContainer;
+ }
+
+ /**
* Creates a new split container with the two provided activities.
* @param primaryActivity An activity that should be in the primary container. If it is not
* currently in an existing container, a new one will be created and the
@@ -99,48 +130,12 @@
final Rect parentBounds = getParentContainerBounds(primaryActivity);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
- TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
- primaryActivity.getActivityToken());
- if (primaryContainer == null) {
- primaryContainer = mController.newContainer(primaryActivity);
-
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(
- primaryContainer.getTaskFragmentToken(),
- primaryActivity.getActivityToken(),
- primaryRectBounds,
- WINDOWING_MODE_MULTI_WINDOW);
- wct.createTaskFragment(fragmentOptions);
-
- wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(),
- primaryActivity.getActivityToken());
-
- primaryContainer.setLastRequestedBounds(primaryRectBounds);
- } else {
- resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
- }
+ final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+ primaryActivity, primaryRectBounds, null);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
- TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity(
- secondaryActivity.getActivityToken());
- if (secondaryContainer == null || secondaryContainer == primaryContainer) {
- secondaryContainer = mController.newContainer(secondaryActivity);
-
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(
- secondaryContainer.getTaskFragmentToken(),
- secondaryActivity.getActivityToken(),
- secondaryRectBounds,
- WINDOWING_MODE_MULTI_WINDOW);
- wct.createTaskFragment(fragmentOptions);
-
- wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(),
- secondaryActivity.getActivityToken());
-
- secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
- } else {
- resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
- }
+ final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
+ secondaryActivity, secondaryRectBounds, primaryContainer);
// Set adjacent to each other so that the containers below will be invisible.
wct.setAdjacentTaskFragments(
@@ -151,6 +146,38 @@
}
/**
+ * Creates a new container or resizes an existing container for activity to the provided bounds.
+ * @param activity The activity to be re-parented to the container if necessary.
+ * @param containerToAvoid Re-parent from this container if an activity is already in it.
+ */
+ private TaskFragmentContainer prepareContainerForActivity(
+ @NonNull WindowContainerTransaction wct, @NonNull Activity activity,
+ @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+ TaskFragmentContainer container = mController.getContainerWithActivity(
+ activity.getActivityToken());
+ if (container == null || container == containerToAvoid) {
+ container = mController.newContainer(activity);
+
+ final TaskFragmentCreationParams fragmentOptions =
+ createFragmentOptions(
+ container.getTaskFragmentToken(),
+ activity.getActivityToken(),
+ bounds,
+ WINDOWING_MODE_MULTI_WINDOW);
+ wct.createTaskFragment(fragmentOptions);
+
+ wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
+ activity.getActivityToken());
+
+ container.setLastRequestedBounds(bounds);
+ } else {
+ resizeTaskFragmentIfRegistered(wct, container, bounds);
+ }
+
+ return container;
+ }
+
+ /**
* Starts a new activity to the side, creating a new split container. A new container will be
* created for the activity that will be started.
* @param launchingActivity An activity that should be in the primary container. If it is not
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 624b8b3..c2b6ffb 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -31,7 +31,7 @@
<string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string>
- <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"అనువర్తనంలో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+ <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
diff --git a/mms/OWNERS b/mms/OWNERS
index befc320..7f05a2a 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -2,7 +2,6 @@
tgunn@google.com
breadley@google.com
-hallliu@google.com
rgreenwalt@google.com
amitmahajan@google.com
fionaxu@google.com
@@ -10,7 +9,10 @@
jminjie@google.com
satk@google.com
shuoq@google.com
-refuhoo@google.com
nazaninb@google.com
sarahchin@google.com
-dbright@google.com
\ No newline at end of file
+xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
diff --git a/packages/CarrierDefaultApp/OWNERS b/packages/CarrierDefaultApp/OWNERS
index 5668840..0d23f05 100644
--- a/packages/CarrierDefaultApp/OWNERS
+++ b/packages/CarrierDefaultApp/OWNERS
@@ -1,7 +1,6 @@
set noparent
tgunn@google.com
breadley@google.com
-hallliu@google.com
rgreenwalt@google.com
amitmahajan@google.com
fionaxu@google.com
@@ -9,9 +8,11 @@
jminjie@google.com
satk@google.com
shuoq@google.com
-refuhoo@google.com
nazaninb@google.com
sarahchin@google.com
-dbright@google.com
xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
new file mode 100644
index 0000000..68834bc
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+interface Flag<T> {
+ val id: Int
+ val default: T
+}
+
+data class BooleanFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Boolean = false
+) : Flag<Boolean>
+
+data class StringFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: String = ""
+) : Flag<String>
+
+data class IntFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Int = 0
+) : Flag<Int>
+
+data class LongFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Long = 0
+) : Flag<Long>
+
+data class FloatFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Float = 0f
+) : Flag<Float>
+
+data class DoubleFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Double = 0.0
+) : Flag<Double>
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
new file mode 100644
index 0000000..d5b9243
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+/**
+ * List of {@link Flag} objects for use in SystemUI.
+ */
+public class Flags {
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
new file mode 100644
index 0000000..d153bd8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+
+/**
+ * Plugin for loading flag values from an alternate source of truth.
+ */
+@ProvidesInterface(action = FlagReaderPlugin.ACTION, version = FlagReaderPlugin.VERSION)
+public interface FlagReaderPlugin extends Plugin {
+ int VERSION = 1;
+ String ACTION = "com.android.systemui.flags.FLAG_READER_PLUGIN";
+
+ /** Returns a boolean value for the given flag. */
+ default boolean isEnabled(int id, boolean def) {
+ return def;
+ }
+
+ /** Returns a string value for the given flag id. */
+ default String getValue(int id, String def) {
+ return def;
+ }
+
+ /** Returns a int value for the given flag. */
+ default int getValue(int id, int def) {
+ return def;
+ }
+
+ /** Returns a long value for the given flag. */
+ default long getValue(int id, long def) {
+ return def;
+ }
+
+ /** Returns a float value for the given flag. */
+ default float getValue(int id, float def) {
+ return def;
+ }
+
+ /** Returns a double value for the given flag. */
+ default double getValue(int id, double def) {
+ return def;
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c6166..6016aaf 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -68,6 +68,16 @@
lockScreenWeight="400"
/>
</FrameLayout>
+ <FrameLayout
+ android:id="@+id/keyguard_smartspace_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/below_clock_padding_start"
+ android:paddingEnd="@dimen/below_clock_padding_end"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/lockscreen_clock_view"
+ />
+ <!-- either keyguard_status_area or keyguard_smartspace_container is visible -->
<include layout="@layout/keyguard_status_area"
android:id="@+id/keyguard_status_area"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index ce63082..f613a19 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,49 +27,44 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
- androidprv:layout_maxHeight="@dimen/keyguard_security_height"
- android:gravity="center_horizontal">
+ android:clipChildren="false"
+ android:clipToPadding="false">
- <FrameLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pattern_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/pattern_container"
- android:layout_height="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:layout_weight="1"
+ android:layoutDirection="ltr">
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_top_guideline"
android:layout_width="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="center_horizontal|bottom"
- android:clipChildren="false"
- android:clipToPadding="false">
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintGuide_percent="0"
+ android:orientation="horizontal" />
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPatternView"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_marginEnd="8dip"
- android:layout_marginBottom="4dip"
- android:layout_marginStart="8dip"
- android:layout_gravity="center_horizontal"
- android:gravity="center"
- android:clipChildren="false"
- android:clipToPadding="false" />
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintVertical_bias="1.0"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
- <include layout="@layout/keyguard_eca"
- android:id="@+id/keyguard_selector_fade_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginTop="@dimen/keyguard_eca_top_margin"
- android:gravity="center_horizontal" />
- </LinearLayout>
- </FrameLayout>
+ <include layout="@layout/keyguard_eca"
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal" />
</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6f7358c..b6b3cc5 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -87,6 +87,18 @@
layout="@layout/keyguard_status_view"
android:visibility="gone"/>
+ <FrameLayout
+ android:id="@+id/split_shade_smartspace_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/notification_side_paddings"
+ android:paddingEnd="@dimen/notification_side_paddings"
+ systemui:layout_constraintStart_toStartOf="@id/qs_edge_guideline"
+ systemui:layout_constraintEnd_toEndOf="parent"
+ systemui:layout_constraintTop_toTopOf="parent"
+ android:visibility="gone">
+ </FrameLayout>
+
<include layout="@layout/dock_info_overlay" />
<FrameLayout
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9e456cf..27b6182 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -17,13 +17,13 @@
package com.android.keyguard;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import android.app.WallpaperManager;
import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -93,8 +93,7 @@
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- // If set, will replace keyguard_status_area
- private View mSmartspaceView;
+ private ViewGroup mSmartspaceContainer;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private SmartspaceTransitionController mSmartspaceTransitionController;
@@ -139,6 +138,8 @@
mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ mSmartspaceContainer = mView.findViewById(R.id.keyguard_smartspace_container);
+ mSmartspaceController.setKeyguardStatusContainer(mSmartspaceContainer);
mClockViewController =
new AnimatableClockController(
@@ -170,35 +171,25 @@
mView.updateColors(getGradientColors());
updateAodIcons();
- if (mSmartspaceController.isEnabled()) {
- mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+ if (mSmartspaceController.isSmartspaceEnabled()) {
+ // "Enabled" doesn't mean smartspace is displayed here - inside mSmartspaceContainer -
+ // it might be a part of another view when in split shade. But it means that it CAN be
+ // displayed here, so we want to hide keyguard_status_area and set views relations
+ // accordingly.
View ksa = mView.findViewById(R.id.keyguard_status_area);
- int ksaIndex = mView.indexOfChild(ksa);
+ // we show either keyguard_status_area or smartspace, so when smartspace can be visible,
+ // keyguard_status_area should be hidden
ksa.setVisibility(View.GONE);
- // Place smartspace view below normal clock...
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
- MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
-
- mView.addView(mSmartspaceView, ksaIndex, lp);
- int startPadding = getContext().getResources()
- .getDimensionPixelSize(R.dimen.below_clock_padding_start);
- int endPadding = getContext().getResources()
- .getDimensionPixelSize(R.dimen.below_clock_padding_end);
- mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
-
updateClockLayout();
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
+ View nic = mView.findViewById(R.id.left_aligned_notification_icon_container);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
+ lp.addRule(RelativeLayout.BELOW, mSmartspaceContainer.getId());
nic.setLayoutParams(lp);
-
- mView.setSmartspaceView(mSmartspaceView);
- mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
+ mView.setSmartspaceView(mSmartspaceContainer);
+ mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceContainer);
}
}
@@ -221,10 +212,7 @@
// instance of this class. In order to fix this, we need to modify the plugin so that
// (a) we get a new view each time and (b) we can properly clean up an old view by making
// it unregister itself as a plugin listener.
- if (mSmartspaceView != null) {
- mView.removeView(mSmartspaceView);
- mSmartspaceView = null;
- }
+ mSmartspaceContainer.removeAllViews();
}
/**
@@ -237,7 +225,7 @@
}
private void updateClockLayout() {
- if (mSmartspaceController.isEnabled()) {
+ if (mSmartspaceController.isSmartspaceEnabled()) {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
lp.topMargin = getContext().getResources().getDimensionPixelSize(
@@ -302,8 +290,8 @@
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
- if (mSmartspaceView != null) {
- PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+ if (mSmartspaceContainer != null) {
+ PropertyAnimator.setProperty(mSmartspaceContainer, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -321,8 +309,8 @@
public void setChildrenAlphaExcludingSmartspace(float alpha) {
final Set<View> excludedViews = new HashSet<>();
- if (mSmartspaceView != null) {
- excludedViews.add(mSmartspaceView);
+ if (mSmartspaceContainer != null) {
+ excludedViews.add(mSmartspaceContainer);
}
setChildrenAlphaExcluding(alpha, excludedViews);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index b473f6d..75425e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -204,7 +204,8 @@
return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
- emergencyButtonController, mMessageAreaControllerFactory);
+ emergencyButtonController, mMessageAreaControllerFactory,
+ mDevicePostureController);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 98e7fb4..a35aedf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,6 +15,8 @@
*/
package com.android.keyguard;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemClock;
@@ -22,16 +24,19 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternView;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
public class KeyguardPatternView extends KeyguardInputView
implements AppearAnimationCreator<LockPatternView.CellState> {
@@ -68,7 +73,7 @@
KeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
- private ViewGroup mContainer;
+ private ConstraintLayout mContainer;
public KeyguardPatternView(Context context) {
this(context, null);
@@ -90,6 +95,18 @@
mContext, android.R.interpolator.fast_out_linear_in));
}
+ void onDevicePostureChanged(@DevicePostureInt int posture) {
+ // Update the guideline based on the device posture...
+ float halfOpenPercentage =
+ mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
+
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainer);
+ cs.setGuidelinePercent(R.id.pin_pad_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+ ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainer);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index d5be7ba..94e07b7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -38,6 +38,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import java.util.List;
@@ -56,6 +57,9 @@
private final FalsingCollector mFalsingCollector;
private final EmergencyButtonController mEmergencyButtonController;
private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+ private final DevicePostureController mPostureController;
+ private final DevicePostureController.Callback mPostureCallback =
+ posture -> mView.onDevicePostureChanged(posture);
private KeyguardMessageAreaController mMessageAreaController;
private LockPatternView mLockPatternView;
@@ -192,7 +196,8 @@
LatencyTracker latencyTracker,
FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
- KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ DevicePostureController postureController) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
@@ -203,6 +208,7 @@
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = mMessageAreaControllerFactory.create(kma);
mLockPatternView = mView.findViewById(R.id.lockPatternView);
+ mPostureController = postureController;
}
@Override
@@ -235,6 +241,7 @@
getKeyguardSecurityCallback().onCancelClicked();
});
}
+ mPostureController.addCallback(mPostureCallback);
}
@Override
@@ -247,6 +254,7 @@
if (cancelBtn != null) {
cancelBtn.setOnClickListener(null);
}
+ mPostureController.removeCallback(mPostureCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 23c4413..b2db86f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -115,6 +115,7 @@
mSecureSettings = secureSettings;
mCallback = callback;
mProximitySensor = proximitySensor;
+ mProximitySensor.setTag(TAG);
mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
mListeningProxSensors = !mSelectivelyRegisterProxSensors;
mScreenOffUdfpsEnabled =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 1fec7a6..08247a8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -16,6 +16,7 @@
package com.android.systemui.flags;
+import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
@@ -25,6 +26,9 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.FlagReaderPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.wrapper.BuildInfo;
import javax.inject.Inject;
@@ -54,18 +58,60 @@
public class FeatureFlagReader {
private final Resources mResources;
private final boolean mAreFlagsOverrideable;
+ private final PluginManager mPluginManager;
private final SystemPropertiesHelper mSystemPropertiesHelper;
private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
+ private FlagReaderPlugin mPlugin = new FlagReaderPlugin(){};
+
@Inject
public FeatureFlagReader(
@Main Resources resources,
BuildInfo build,
+ PluginManager pluginManager,
SystemPropertiesHelper systemPropertiesHelper) {
mResources = resources;
+ mPluginManager = pluginManager;
mSystemPropertiesHelper = systemPropertiesHelper;
mAreFlagsOverrideable =
build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
+
+ mPluginManager.addPluginListener(mPluginListener, FlagReaderPlugin.class);
+ }
+
+ private final PluginListener<FlagReaderPlugin> mPluginListener =
+ new PluginListener<FlagReaderPlugin>() {
+ public void onPluginConnected(FlagReaderPlugin plugin, Context context) {
+ mPlugin = plugin;
+ }
+
+ public void onPluginDisconnected(FlagReaderPlugin plugin) {
+ mPlugin = new FlagReaderPlugin() {};
+ }
+ };
+
+ boolean isEnabled(BooleanFlag flag) {
+ return mPlugin.isEnabled(flag.getId(), flag.getDefault());
+ }
+
+ String getValue(StringFlag flag) {
+ return mPlugin.getValue(flag.getId(), flag.getDefault());
+ }
+
+ int getValue(IntFlag flag) {
+ return mPlugin.getValue(flag.getId(), flag.getDefault());
+ }
+
+ long getValue(LongFlag flag) {
+ return mPlugin.getValue(flag.getId(), flag.getDefault());
+ }
+
+ float getValue(FloatFlag flag) {
+ return mPlugin.getValue(flag.getId(), flag.getDefault());
+ }
+
+ double getValue(DoubleFlag flag) {
+ return mPlugin.getValue(flag.getId(), flag.getDefault());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 5882179..0c9e6de 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -22,6 +22,10 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
import javax.inject.Inject;
/**
@@ -40,6 +44,54 @@
mContext = context;
}
+ /**
+ * @param flag The {@link BooleanFlag} of interest.
+ * @return The value of the flag.
+ */
+ public boolean isEnabled(BooleanFlag flag) {
+ return mFlagReader.isEnabled(flag);
+ }
+
+ /**
+ * @param flag The {@link StringFlag} of interest.
+ * @return The value of the flag.
+ */
+ public String getValue(StringFlag flag) {
+ return mFlagReader.getValue(flag);
+ }
+
+ /**
+ * @param flag The {@link IntFlag} of interest.
+ * @return The value of the flag.
+ */
+ public int getValue(IntFlag flag) {
+ return mFlagReader.getValue(flag);
+ }
+
+ /**
+ * @param flag The {@link LongFlag} of interest.
+ * @return The value of the flag.
+ */
+ public long getValue(LongFlag flag) {
+ return mFlagReader.getValue(flag);
+ }
+
+ /**
+ * @param flag The {@link FloatFlag} of interest.
+ * @return The value of the flag.
+ */
+ public float getValue(FloatFlag flag) {
+ return mFlagReader.getValue(flag);
+ }
+
+ /**
+ * @param flag The {@link DoubleFlag} of interest.
+ * @return The value of the flag.
+ */
+ public double getValue(DoubleFlag flag) {
+ return mFlagReader.getValue(flag);
+ }
+
public boolean isNewNotifPipelineEnabled() {
return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
}
@@ -107,4 +159,31 @@
public static boolean isProviderModelSettingEnabled(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
}
+
+ private Map<Integer, Flag<?>> collectFlags() {
+ Map<Integer, Flag<?>> flags = new HashMap<>();
+
+ Field[] fields = this.getClass().getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ //flags.add((Flag<?>) field.get(null));
+ Flag flag = (Flag) field.get(null);
+ flags.put(flag.getId(), flag);
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ return flags;
+ }
+
+ /** Simple interface for beinga alerted when a specific flag changes value. */
+ public interface Listener {
+ /** */
+ void onFlagChanged(Flag<?> flag);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index aaa3bf0..2bf4bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -106,7 +106,7 @@
static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
- SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
+ SystemProperties.getBoolean("persist.debug.per_window_input_rotation", true);
private ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f754494..4e09bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.LifecycleFragment;
@@ -414,6 +415,12 @@
return mQSPanelController;
}
+ public void setBrightnessMirrorController(
+ BrightnessMirrorController brightnessMirrorController) {
+ mQSPanelController.setBrightnessMirror(brightnessMirrorController);
+ mQuickQSPanelController.setBrightnessMirror(brightnessMirrorController);
+ }
+
@Override
public boolean isShowingDetail() {
return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f3d071e..6e09f22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
@@ -60,10 +61,9 @@
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
private final FalsingManager mFalsingManager;
private final BrightnessController mBrightnessController;
- private final BrightnessSlider.Factory mBrightnessSliderFactory;
private final BrightnessSlider mBrightnessSlider;
+ private final BrightnessMirrorHandler mBrightnessMirrorHandler;
- private BrightnessMirrorController mBrightnessMirrorController;
private boolean mGridContentVisible = true;
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
@@ -75,14 +75,10 @@
if (mView.isListening()) {
refreshAllTiles();
}
- updateBrightnessMirror();
mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
}
};
- private final BrightnessMirrorController.BrightnessMirrorListener mBrightnessMirrorListener =
- mirror -> updateBrightnessMirror();
-
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -110,12 +106,12 @@
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
mFalsingManager = falsingManager;
mQsSecurityFooter.setHostEnvironment(qstileHost);
- mBrightnessSliderFactory = brightnessSliderFactory;
- mBrightnessSlider = mBrightnessSliderFactory.create(getContext(), mView);
+ mBrightnessSlider = brightnessSliderFactory.create(getContext(), mView);
mView.setBrightnessView(mBrightnessSlider.getRootView());
mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
+ mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
}
@Override
@@ -142,9 +138,7 @@
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
switchTileLayout(true);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
- }
+ mBrightnessMirrorHandler.onQsPanelAttached();
((PagedTileLayout) mView.getOrCreateTileLayout())
.setOnTouchListener(mTileLayoutTouchListener);
@@ -160,9 +154,7 @@
protected void onViewDetached() {
mTunerService.removeTunable(mView);
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
- }
+ mBrightnessMirrorHandler.onQsPanelDettached();
super.onViewDetached();
}
@@ -196,23 +188,8 @@
}
}
- /** */
public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
- mBrightnessMirrorController = brightnessMirrorController;
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
- }
- mBrightnessMirrorController = brightnessMirrorController;
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
- }
- updateBrightnessMirror();
- }
-
- private void updateBrightnessMirror() {
- if (mBrightnessMirrorController != null) {
- mBrightnessSlider.setMirrorControllerAndMirror(mBrightnessMirrorController);
- }
+ mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
/** Get the QSTileHost this panel uses. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
index 7c81abc..14374ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
@@ -19,6 +19,8 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSlider
+import com.android.systemui.settings.brightness.MirroredBrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
import javax.inject.Inject
/**
@@ -27,7 +29,7 @@
*/
class QuickQSBrightnessController @VisibleForTesting constructor(
private val brightnessControllerFactory: () -> BrightnessController
-) {
+) : MirroredBrightnessController {
@Inject constructor(
brightnessControllerFactory: BrightnessController.Factory,
@@ -42,6 +44,7 @@
private var isListening = false
private var brightnessController: BrightnessController? = null
+ private var mirrorController: BrightnessMirrorController? = null
fun init(shouldUseSplitNotificationShade: Boolean) {
refreshVisibility(shouldUseSplitNotificationShade)
@@ -77,6 +80,11 @@
}
}
+ override fun setMirror(controller: BrightnessMirrorController) {
+ mirrorController = controller
+ mirrorController?.let { brightnessController?.setMirror(it) }
+ }
+
private fun hideBrightnessSlider() {
brightnessController?.hideSlider()
}
@@ -84,11 +92,10 @@
private fun showBrightnessSlider() {
if (brightnessController == null) {
brightnessController = brightnessControllerFactory()
- }
- brightnessController?.showSlider()
- if (!isListening) {
+ mirrorController?.also { brightnessController?.setMirror(it) }
brightnessController?.registerCallbacks()
isListening = true
}
+ brightnessController?.showSlider()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 74cd50c..8c7a2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -29,6 +29,8 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import java.util.ArrayList;
import java.util.List;
@@ -50,6 +52,7 @@
// brightness is visible only in split shade
private final QuickQSBrightnessController mBrightnessController;
+ private final BrightnessMirrorHandler mBrightnessMirrorHandler;
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
@@ -63,6 +66,7 @@
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
mBrightnessController = quickQSBrightnessController;
+ mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
}
@Override
@@ -78,12 +82,14 @@
protected void onViewAttached() {
super.onViewAttached();
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mBrightnessMirrorHandler.onQsPanelAttached();
}
@Override
protected void onViewDetached() {
super.onViewDetached();
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mBrightnessMirrorHandler.onQsPanelDettached();
}
@Override
@@ -132,4 +138,8 @@
public int getNumQuickTiles() {
return mView.getNumQuickTiles();
}
+
+ public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ mBrightnessMirrorHandler.setController(brightnessMirrorController);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index a9ebcad..185b8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -50,12 +50,13 @@
import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import java.util.ArrayList;
import javax.inject.Inject;
-public class BrightnessController implements ToggleSlider.Listener {
+public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "StatusBar.BrightnessController";
private static final int SLIDER_ANIMATION_DURATION = 3000;
@@ -109,6 +110,11 @@
private ValueAnimator mSliderAnimator;
+ @Override
+ public void setMirror(BrightnessMirrorController controller) {
+ mControl.setMirrorControllerAndMirror(controller);
+ }
+
public interface BrightnessStateChangeCallback {
/** Indicates that some of the brightness settings have changed */
void onBrightnessLevelChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
new file mode 100644
index 0000000..51aa339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
+
+class BrightnessMirrorHandler(private val brightnessController: MirroredBrightnessController) {
+
+ private var mirrorController: BrightnessMirrorController? = null
+
+ private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+
+ fun onQsPanelAttached() {
+ mirrorController?.addCallback(brightnessMirrorListener)
+ }
+
+ fun onQsPanelDettached() {
+ mirrorController?.removeCallback(brightnessMirrorListener)
+ }
+
+ fun setController(controller: BrightnessMirrorController) {
+ mirrorController?.removeCallback(brightnessMirrorListener)
+ mirrorController = controller
+ mirrorController?.addCallback(brightnessMirrorListener)
+ updateBrightnessMirror()
+ }
+
+ private fun updateBrightnessMirror() {
+ mirrorController?.let { brightnessController.setMirror(it) }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
index 896106a..b0e320a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
@@ -140,13 +140,7 @@
@Override
public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
mMirrorController = c;
- if (c != null) {
- setMirror(c.getToggleSlider());
- } else {
- // If there's no mirror, we may be the ones dispatching, events but we should not mirror
- // them
- mView.setOnDispatchTouchEventListener(null);
- }
+ setMirror(c.getToggleSlider());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
new file mode 100644
index 0000000..8d857de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 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.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+
+/**
+ * Indicates controller that has brightness slider and uses [BrightnessMirrorController]
+ */
+interface MirroredBrightnessController {
+ fun setMirror(controller: BrightnessMirrorController)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index f2060b7..fdbe728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -31,6 +31,8 @@
import android.os.UserHandle
import android.provider.Settings
import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
import android.view.ViewGroup
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -44,14 +46,21 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.AnimatableProperty
+import com.android.systemui.statusbar.notification.PropertyAnimator
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
-import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
+private val ANIMATION_PROPERTIES = AnimationProperties()
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong())
+
/**
* Controller for managing the smartspace view on the lockscreen
*/
@@ -72,10 +81,15 @@
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
+
+ var splitShadeContainer: ViewGroup? = null
+ private var singlePaneContainer: ViewGroup? = null
+
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private lateinit var smartspaceView: SmartspaceView
+ // smartspace casted to View
lateinit var view: View
private set
@@ -83,12 +97,60 @@
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
- fun isEnabled(): Boolean {
+ private var isAod = false
+ private var isSplitShade = false
+
+ fun isSmartspaceEnabled(): Boolean {
execution.assertIsMainThread()
return featureFlags.isSmartspaceEnabled && plugin != null
}
+ fun setKeyguardStatusContainer(container: ViewGroup) {
+ singlePaneContainer = container
+ // reattach smartspace if necessary as this might be a new container
+ updateSmartSpaceContainer()
+ }
+
+ fun onSplitShadeChanged(splitShade: Boolean) {
+ isSplitShade = splitShade
+ updateSmartSpaceContainer()
+ }
+
+ private fun updateSmartSpaceContainer() {
+ if (!isSmartspaceEnabled()) return
+ // in AOD we always want to show smartspace on the left i.e. in singlePaneContainer
+ if (isSplitShade && !isAod) {
+ switchContainerVisibility(
+ newParent = splitShadeContainer,
+ oldParent = singlePaneContainer)
+ } else {
+ switchContainerVisibility(
+ newParent = singlePaneContainer,
+ oldParent = splitShadeContainer)
+ }
+ requestSmartspaceUpdate()
+ }
+
+ private fun switchContainerVisibility(newParent: ViewGroup?, oldParent: ViewGroup?) {
+ // it might be the case that smartspace was already attached and we just needed to update
+ // visibility, e.g. going from lockscreen -> unlocked -> lockscreen
+ if (newParent?.childCount == 0) {
+ oldParent?.removeAllViews()
+ newParent.addView(buildAndConnectView(newParent))
+ }
+ oldParent?.visibility = GONE
+ newParent?.visibility = VISIBLE
+ }
+
+ fun setSplitShadeSmartspaceAlpha(alpha: Float) {
+ // the other container's alpha is modified as a part of keyguard status view, so we don't
+ // have to do that here
+ if (splitShadeContainer?.visibility == VISIBLE) {
+ splitShadeContainer?.alpha = alpha
+ }
+ }
+
/**
* Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
* are idempotent until [disconnect] is called.
@@ -96,7 +158,7 @@
fun buildAndConnectView(parent: ViewGroup): View {
execution.assertIsMainThread()
- if (!isEnabled()) {
+ if (!isSmartspaceEnabled()) {
throw RuntimeException("Cannot build view when not enabled")
}
@@ -182,7 +244,6 @@
userTracker.removeCallback(userTrackerCallback)
contentResolver.unregisterContentObserver(settingsObserver)
configurationController.removeCallback(configChangeListener)
- statusBarStateController.removeCallback(statusBarStateListener)
session = null
plugin?.onTargetsAvailable(emptyList())
@@ -198,6 +259,13 @@
plugin?.unregisterListener(listener)
}
+ fun shiftSplitShadeSmartspace(y: Int, animate: Boolean) {
+ if (splitShadeContainer?.visibility == VISIBLE) {
+ PropertyAnimator.setProperty(splitShadeContainer, AnimatableProperty.Y, y.toFloat(),
+ ANIMATION_PROPERTIES, animate)
+ }
+ }
+
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
val filteredTargets = targets.filter(::filterSmartspaceTarget)
@@ -233,6 +301,23 @@
execution.assertIsMainThread()
smartspaceView.setDozeAmount(eased)
}
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ isAod = isDozing
+ updateSmartSpaceContainer()
+ }
+
+ override fun onStateChanged(newState: Int) {
+ if (newState == StatusBarState.KEYGUARD) {
+ if (isSmartspaceEnabled()) {
+ updateSmartSpaceContainer()
+ }
+ } else {
+ splitShadeContainer?.visibility = GONE
+ singlePaneContainer?.visibility = GONE
+ disconnect()
+ }
+ }
}
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 34929f2..880e558 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -407,6 +407,7 @@
private boolean mBackwardScrollable;
private NotificationShelf mShelf;
private int mMaxDisplayedNotifications = -1;
+ private float mKeyguardBottomPadding = -1;
private int mStatusBarHeight;
private int mMinInteractionHeight;
private final Rect mClipRect = new Rect();
@@ -742,6 +743,16 @@
mDebugPaint.setColor(Color.YELLOW);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ y = (int) mMaxLayoutHeight;
+ mDebugPaint.setColor(Color.MAGENTA);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+ if (mKeyguardBottomPadding >= 0) {
+ y = getHeight() - (int) mKeyguardBottomPadding;
+ mDebugPaint.setColor(Color.GRAY);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ }
+
y = getHeight() - getEmptyBottomMargin();
mDebugPaint.setColor(Color.GREEN);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -4784,6 +4795,16 @@
}
}
+ /**
+ * This is used for debugging only; it will be used to draw the otherwise invisible line which
+ * NotificationPanelViewController treats as the bottom when calculating how many notifications
+ * appear on the keyguard.
+ * Setting a negative number will disable rendering this line.
+ */
+ public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+ mKeyguardBottomPadding = keyguardBottomPadding;
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
mShouldShowShelfOnly = shouldShowShelfOnly;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 417418a..a865c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1218,6 +1218,16 @@
mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
}
+ /**
+ * This is used for debugging only; it will be used to draw the otherwise invisible line which
+ * NotificationPanelViewController treats as the bottom when calculating how many notifications
+ * appear on the keyguard.
+ * Setting a negative number will disable rendering this line.
+ */
+ public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+ mView.setKeyguardBottomPadding(keyguardBottomPadding);
+ }
+
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a0af389..7908d84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -231,7 +231,9 @@
* possible if AOD isn't even enabled or if the flag is disabled.
*/
public boolean canControlUnlockedScreenOff() {
- return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
+ return getAlwaysOn()
+ && mFeatureFlags.useNewLockscreenAnimations()
+ && !getDisplayNeedsBlanking();
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c052..19c2585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -33,23 +33,11 @@
*/
public class KeyguardClockPositionAlgorithm {
/**
- * How much the clock height influences the shade position.
- * 0 means nothing, 1 means move the shade up by the height of the clock
- * 0.5f means move the shade up by half of the size of the clock.
- */
- private static float CLOCK_HEIGHT_WEIGHT = 0.7f;
-
- /**
* Margin between the bottom of the status view and the notification shade.
*/
private int mStatusViewBottomMargin;
/**
- * Height of the parent view - display size in px.
- */
- private int mHeight;
-
- /**
* Height of {@link KeyguardStatusView}.
*/
private int mKeyguardStatusHeight;
@@ -68,21 +56,6 @@
private int mUserSwitchPreferredY;
/**
- * Whether or not there is a custom clock face on keyguard.
- */
- private boolean mHasCustomClock;
-
- /**
- * Whether or not the NSSL contains any visible notifications.
- */
- private boolean mHasVisibleNotifs;
-
- /**
- * Height of notification stack: Sum of height of each notification.
- */
- private int mNotificationStackHeight;
-
- /**
* Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
* avatar.
*/
@@ -148,6 +121,7 @@
private int mUnlockedStackScrollerPadding;
private boolean mIsSplitShade;
+ private int mSplitShadeSmartspaceHeight;
/**
* Refreshes the dimension values.
@@ -170,28 +144,25 @@
* Sets up algorithm values.
*/
public void setup(int keyguardStatusBarHeaderHeight, int maxShadeBottom,
- int notificationStackHeight, float panelExpansion, int parentHeight,
- int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
- boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
+ float panelExpansion,
+ int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, float dark,
float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
- float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+ float qsExpansion, int cutoutTopInset, int splitShadeSmartspaceHeight,
+ boolean isSplitShade) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
mMaxShadeBottom = maxShadeBottom;
- mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
- mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin;
mUserSwitchHeight = userSwitchHeight;
mUserSwitchPreferredY = userSwitchPreferredY;
- mHasCustomClock = hasCustomClock;
- mHasVisibleNotifs = hasVisibleNotifs;
mDarkAmount = dark;
mOverStretchAmount = overStrechAmount;
mBypassEnabled = bypassEnabled;
mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
mQsExpansion = qsExpansion;
mCutoutTopInset = cutoutTopInset;
+ mSplitShadeSmartspaceHeight = splitShadeSmartspaceHeight;
mIsSplitShade = isSplitShade;
}
@@ -213,7 +184,7 @@
if (mBypassEnabled) {
return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
} else if (mIsSplitShade) {
- return clockYPosition;
+ return clockYPosition + mSplitShadeSmartspaceHeight;
} else {
return clockYPosition + mKeyguardStatusHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3b65ce0..66d5960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -138,6 +138,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -229,6 +230,7 @@
private final HeightListener mHeightListener = new HeightListener();
private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
private final SettingsChangeObserver mSettingsChangeObserver;
+ private final LockscreenSmartspaceController mLockscreenSmartspaceController;
@VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
new StatusBarStateListener();
@@ -338,6 +340,8 @@
private final SplitShadeHeaderController mSplitShadeHeaderController;
private final RecordingController mRecordingController;
private boolean mShouldUseSplitNotificationShade;
+ // The bottom padding reserved for elements of the keyguard measuring notifications
+ private float mKeyguardNotificationBottomPadding;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -353,6 +357,8 @@
private KeyguardStatusViewController mKeyguardStatusViewController;
private LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
+ private FrameLayout mSplitShadeSmartspaceContainer;
+
private boolean mAnimateNextPositionUpdate;
private float mQuickQsOffsetHeight;
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -734,6 +740,7 @@
@Main Executor uiExecutor,
SecureSettings secureSettings,
SplitShadeHeaderController splitShadeHeaderController,
+ LockscreenSmartspaceController lockscreenSmartspaceController,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
NotificationRemoteInputManager remoteInputManager,
ControlsComponent controlsComponent) {
@@ -765,6 +772,7 @@
mQSDetailDisplayer = qsDetailDisplayer;
mFragmentService = fragmentService;
mSettingsChangeObserver = new SettingsChangeObserver(handler);
+ mLockscreenSmartspaceController = lockscreenSmartspaceController;
mShouldUseSplitNotificationShade =
Utils.shouldUseSplitNotificationShade(mResources);
mView.setWillNotDraw(!DEBUG);
@@ -859,6 +867,9 @@
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
+ mSplitShadeSmartspaceContainer = mView.findViewById(R.id.split_shade_smartspace_container);
+ mLockscreenSmartspaceController.setSplitShadeContainer(mSplitShadeSmartspaceContainer);
+ mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade);
UserAvatarView userAvatarView = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -1071,7 +1082,7 @@
mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
updateKeyguardStatusViewAlignment(false /* animate */);
-
+ mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade);
mKeyguardMediaController.refreshMediaPosition();
}
@@ -1206,9 +1217,12 @@
if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
+ mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
+ mKeyguardNotificationBottomPadding);
} else {
// no max when not on the keyguard
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
+ mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
}
}
@@ -1331,16 +1345,15 @@
? 1.0f : mInterpolatedDarkAmount;
mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
totalHeight - bottomPadding,
- mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
expandedFraction,
- totalHeight,
mKeyguardStatusViewController.getLockscreenHeight(),
userIconHeight,
- userSwitcherPreferredY, hasCustomClock(),
- hasVisibleNotifications, darkamount, mOverStretchAmount,
+ userSwitcherPreferredY,
+ darkamount, mOverStretchAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
+ mSplitShadeSmartspaceContainer.getHeight(),
mShouldUseSplitNotificationShade);
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
@@ -1360,6 +1373,9 @@
mClockPositionResult.userSwitchY,
animateClock);
}
+ // no need to translate in X axis - horizontal position is determined by constraints
+ mLockscreenSmartspaceController
+ .shiftSplitShadeSmartspace(mClockPositionResult.clockY, animateClock);
updateNotificationTranslucency();
updateClock();
}
@@ -1411,6 +1427,7 @@
float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
bottomPadding = Math.max(lockIconPadding, bottomPadding);
+ mKeyguardNotificationBottomPadding = bottomPadding;
float availableSpace =
mNotificationStackScrollLayoutController.getHeight()
@@ -1526,6 +1543,7 @@
if (mKeyguardUserSwitcherController != null) {
mKeyguardUserSwitcherController.setAlpha(alpha);
}
+ mLockscreenSmartspaceController.setSplitShadeSmartspaceAlpha(alpha);
}
public void animateToFullShade(long delay) {
@@ -3707,6 +3725,7 @@
public void dozeTimeTick() {
mKeyguardBottomArea.dozeTimeTick();
mKeyguardStatusViewController.dozeTimeTick();
+ mLockscreenSmartspaceController.requestSmartspaceUpdate();
if (mInterpolatedDarkAmount > 0) {
positionClockAndNotifications();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11..70a46b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
+import android.util.Log;
import android.util.Pair;
import android.view.DisplayCutout;
import android.view.Gravity;
@@ -42,7 +43,6 @@
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.leak.RotationUtils;
import java.util.List;
@@ -52,7 +52,6 @@
private static final String TAG = "PhoneStatusBarView";
private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean DEBUG_GESTURES = false;
- private final CommandQueue mCommandQueue;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
StatusBar mBar;
@@ -81,6 +80,8 @@
@Nullable
private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
+ private PanelEnabledProvider mPanelEnabledProvider;
+
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
*/
@@ -89,7 +90,6 @@
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mCommandQueue = Dependency.get(CommandQueue.class);
mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
}
@@ -177,7 +177,11 @@
@Override
public boolean panelEnabled() {
- return mCommandQueue.panelsEnabled();
+ if (mPanelEnabledProvider == null) {
+ Log.e(TAG, "panelEnabledProvider is null; defaulting to super class.");
+ return super.panelEnabled();
+ }
+ return mPanelEnabledProvider.panelEnabled();
}
@Override
@@ -294,6 +298,11 @@
}
}
+ /** Set the {@link PanelEnabledProvider} to use. */
+ public void setPanelEnabledProvider(PanelEnabledProvider panelEnabledProvider) {
+ mPanelEnabledProvider = panelEnabledProvider;
+ }
+
private void updateScrimFraction() {
float scrimFraction = mPanelFraction;
if (mMinFraction < 1.0f) {
@@ -391,4 +400,10 @@
protected boolean shouldPanelBeVisible() {
return mHeadsUpVisible || super.shouldPanelBeVisible();
}
+
+ /** An interface that will provide whether panel is enabled. */
+ interface PanelEnabledProvider {
+ /** Returns true if the panel is enabled and false otherwise. */
+ boolean panelEnabled();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
new file mode 100644
index 0000000..b36b67d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.ViewController;
+
+/** Controller for {@link PhoneStatusBarView}. */
+public class PhoneStatusBarViewController extends ViewController<PhoneStatusBarView> {
+
+ protected PhoneStatusBarViewController(
+ PhoneStatusBarView view,
+ CommandQueue commandQueue) {
+ super(view);
+ mView.setPanelEnabledProvider(commandQueue::panelsEnabled);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6cafa0d..4016b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -462,6 +462,7 @@
protected NotificationShadeWindowView mNotificationShadeWindowView;
protected StatusBarWindowView mPhoneStatusBarWindow;
protected PhoneStatusBarView mStatusBarView;
+ private PhoneStatusBarViewController mPhoneStatusBarViewController;
private AuthRippleController mAuthRippleController;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected NotificationShadeWindowController mNotificationShadeWindowController;
@@ -1206,6 +1207,9 @@
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
+ mPhoneStatusBarViewController =
+ new PhoneStatusBarViewController(mStatusBarView, mCommandQueue);
+ mPhoneStatusBarViewController.init();
mBatteryMeterViewController = new BatteryMeterViewController(
mStatusBarView.findViewById(R.id.battery)
@@ -1365,7 +1369,7 @@
QS qs = (QS) f;
if (qs instanceof QSFragment) {
mQSPanelController = ((QSFragment) qs).getQSPanelController();
- mQSPanelController.setBrightnessMirror(mBrightnessMirrorController);
+ ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index b38fc77..90e022a5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -290,15 +290,18 @@
return;
}
- if (!mSecondaryThresholdSensor.isLoaded()) {
+
+ if (!mSecondaryThresholdSensor.isLoaded()) { // No secondary
logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
onSensorEvent(event);
- } else if (event.getBelow()) {
+ } else if (event.getBelow()) { // Covered? Check secondary.
logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
if (mCancelSecondaryRunnable != null) {
mCancelSecondaryRunnable.run();
}
mSecondaryThresholdSensor.resume();
+ } else { // Uncovered. Report immediately.
+ onSensorEvent(event);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..ce65733 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -29,6 +28,7 @@
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -104,6 +104,8 @@
private AnimatableClockView mLargeClockView;
@Mock
private FrameLayout mLargeClockFrame;
+ @Mock
+ private ViewGroup mSmartspaceContainer;
private final View mFakeSmartspaceView = new View(mContext);
@@ -123,6 +125,8 @@
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
+ when(mView.findViewById(R.id.keyguard_smartspace_container))
+ .thenReturn(mSmartspaceContainer);
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -210,7 +214,7 @@
@Test
public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
@@ -219,7 +223,7 @@
@Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
- when(mSmartspaceController.isEnabled()).thenReturn(false);
+ when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(false);
mController.init();
assertEquals(View.VISIBLE, mStatusArea.getVisibility());
@@ -227,17 +231,16 @@
@Test
public void testDetachRemovesSmartspaceView() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
- verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mView).removeView(mFakeSmartspaceView);
+ verify(mSmartspaceContainer).removeAllViews();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index bb71bed8..8e1e42a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,6 +63,8 @@
private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
@Mock
private lateinit var mLockPatternView: LockPatternView
+ @Mock
+ private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
@@ -78,7 +81,7 @@
mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory)
+ mKeyguardMessageAreaControllerFactory, mPostureController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
index 223714c..7bc5f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.wrapper.BuildInfo;
import org.junit.Before;
@@ -43,6 +44,7 @@
public class FeatureFlagReaderTest extends SysuiTestCase {
@Mock private Resources mResources;
@Mock private BuildInfo mBuildInfo;
+ @Mock private PluginManager mPluginManager;
@Mock private SystemPropertiesHelper mSystemPropertiesHelper;
private FeatureFlagReader mReader;
@@ -63,7 +65,8 @@
private void initialize(boolean isDebuggable, boolean isOverrideable) {
when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
- mReader = new FeatureFlagReader(mResources, mBuildInfo, mSystemPropertiesHelper);
+ mReader = new FeatureFlagReader(
+ mResources, mBuildInfo, mPluginManager, mSystemPropertiesHelper);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
new file mode 100644
index 0000000..25c3028
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+public class FlagsTest extends SysuiTestCase {
+
+ @Test
+ public void testDuplicateFlagIdCheckWorks() {
+ List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
+ Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+ assertWithMessage(generateAssertionMessage(duplicates))
+ .that(duplicates.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testNoDuplicateFlagIds() {
+ List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
+ Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+ assertWithMessage(generateAssertionMessage(duplicates))
+ .that(duplicates.size()).isEqualTo(0);
+ }
+
+ private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Duplicate flag keys found: {");
+ for (int id : duplicates.keySet()) {
+ stringBuilder
+ .append(" ")
+ .append(id)
+ .append(": [")
+ .append(String.join(", ", duplicates.get(id)))
+ .append("]");
+ }
+ stringBuilder.append(" }");
+
+ return stringBuilder.toString();
+ }
+
+ private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
+ List<Pair<String, Flag<?>>> flags = new ArrayList<>();
+
+ Field[] fields = clz.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ return flags;
+ }
+
+ private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
+ Map<Integer, List<String>> grouping = new HashMap<>();
+
+ for (Pair<String, Flag<?>> flag : flags) {
+ grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
+ grouping.get(flag.second.getId()).add(flag.first);
+ }
+
+ Map<Integer, List<String>> result = new HashMap<>();
+ for (Integer id : grouping.keySet()) {
+ if (grouping.get(id).size() > 1) {
+ result.put(id, grouping.get(id));
+ }
+ }
+
+ return result;
+ }
+
+ private static class DuplicateFlagContainer {
+ public static final BooleanFlag A_FLAG = new BooleanFlag(0);
+ public static final BooleanFlag B_FLAG = new BooleanFlag(0);
+ public static final StringFlag C_FLAG = new StringFlag(0);
+
+ public static final BooleanFlag D_FLAG = new BooleanFlag(1);
+
+ public static final DoubleFlag E_FLAG = new DoubleFlag(3);
+ public static final DoubleFlag F_FLAG = new DoubleFlag(3);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
index f8373ff..de1d86b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
@@ -19,13 +19,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.brightness.BrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.never
+import org.mockito.Mockito.mock
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -104,4 +106,13 @@
verify(brightnessController, never()).registerCallbacks()
}
+
+ @Test
+ fun testMirrorIsSetWhenSliderIsShown() {
+ val mirrorController = mock(BrightnessMirrorController::class.java)
+ quickQSBrightnessController.setMirror(mirrorController)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController).setMirror(mirrorController)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
index e0187bd..bceb928 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
@@ -108,15 +108,6 @@
}
@Test
- fun testNullMirrorControllerNotTrackingTouch() {
- mController.setMirrorControllerAndMirror(null)
-
- verify(brightnessSliderView, never()).max
- verify(brightnessSliderView, never()).value
- verify(brightnessSliderView).setOnDispatchTouchEventListener(isNull())
- }
-
- @Test
fun testNullMirrorNotTrackingTouch() {
whenever(mirrorController.toggleSlider).thenReturn(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index a2bb0af..756e984 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -389,7 +389,6 @@
verify(userTracker).removeCallback(userListener)
verify(contentResolver).unregisterContentObserver(settingsObserver)
verify(configurationController).removeCallback(configChangeListener)
- verify(statusBarStateController).removeCallback(statusBarStateListener)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 690b841..2e76bd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -38,35 +38,27 @@
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
- private static final boolean HAS_CUSTOM_CLOCK = false;
- private static final boolean HAS_VISIBLE_NOTIFS = false;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
- private int mNotificationStackHeight;
private float mPanelExpansion;
private int mKeyguardStatusHeight;
private float mDark;
- private boolean mHasCustomClock;
- private boolean mHasVisibleNotifs;
private float mQsExpansion;
- private int mCutoutTopInset = 0; // in pixels
+ private int mCutoutTopInsetPx = 0;
+ private int mSplitShadeSmartspaceHeightPx = 0;
private boolean mIsSplitShade = false;
@Before
public void setUp() {
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
-
- mHasCustomClock = HAS_CUSTOM_CLOCK;
- mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
}
@Test
public void clockPositionTopOfScreenOnAOD() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -79,11 +71,10 @@
@Test
public void clockPositionBelowCutout() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
- mCutoutTopInset = 300;
+ mCutoutTopInsetPx = 300;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is below the cutout
@@ -97,7 +88,6 @@
public void clockPositionAdjustsForKeyguardStatusOnAOD() {
// GIVEN on AOD with a clock of height 100
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = 100;
// WHEN the clock position algorithm is run
positionClock();
@@ -112,7 +102,6 @@
public void clockPositionLargeClockOnAOD() {
// GIVEN on AOD with a full screen clock
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -125,9 +114,8 @@
@Test
public void clockPositionTopOfScreenOnLockScreen() {
- // GIVEN on lock screen with stack scroll and clock of 0 height
+ // GIVEN on lock screen with clock of 0 height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -138,24 +126,9 @@
}
@Test
- public void clockPositionWithStackScrollExpandOnLockScreen() {
- // GIVEN on lock screen with stack scroll of height 500
- givenLockScreen();
- mNotificationStackHeight = 500;
- mKeyguardStatusHeight = EMPTY_HEIGHT;
- // WHEN the clock position algorithm is run
- positionClock();
- // THEN the clock Y position stays to the top
- assertThat(mClockPosition.clockY).isEqualTo(0);
- // AND the clock is positioned on the left.
- assertThat(mClockPosition.clockX).isEqualTo(0);
- }
-
- @Test
public void clockPositionWithPartialDragOnLockScreen() {
// GIVEN dragging up on lock screen
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.5f;
// WHEN the clock position algorithm is run
@@ -171,7 +144,6 @@
public void clockPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
@@ -184,7 +156,6 @@
public void largeClockOnLockScreenIsTransparent() {
// GIVEN on lock screen with a full screen clock
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -194,9 +165,8 @@
@Test
public void notifPositionTopOfScreenOnAOD() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -208,7 +178,6 @@
public void notifPositionIndependentOfKeyguardStatusHeightOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = 100;
// WHEN the position algorithm is run
positionClock();
@@ -220,7 +189,6 @@
public void notifPositionWithLargeClockOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -230,9 +198,8 @@
@Test
public void notifPositionMiddleOfScreenOnLockScreen() {
- // GIVEN on lock screen and both stack scroll and clock have 0 height
+ // GIVEN on lock screen and clock has 0 height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -241,47 +208,21 @@
}
@Test
- public void notifPositionAdjustsForStackHeightOnLockScreen() {
- // GIVEN on lock screen and stack scroller has a nonzero height
- givenLockScreen();
- mNotificationStackHeight = 500;
- mKeyguardStatusHeight = EMPTY_HEIGHT;
- // WHEN the position algorithm is run
- positionClock();
- // THEN the notif padding adjusts for keyguard status height
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
- }
-
- @Test
public void notifPositionAdjustsForClockHeightOnLockScreen() {
// GIVEN on lock screen and stack scroller has a nonzero height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
- // THEN the notif padding adjusts for both clock and notif stack.
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
- }
- @Test
- public void notifPositionAdjustsForStackHeightAndClockHeightOnLockScreen() {
- // GIVEN on lock screen and stack scroller has a nonzero height
- givenLockScreen();
- mNotificationStackHeight = 500;
- mKeyguardStatusHeight = 200;
- // WHEN the position algorithm is run
- positionClock();
- // THEN the notifs are placed below the statusview
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
}
@Test
public void notifPositionAlignedWithClockInSplitShadeMode() {
- // GIVEN on lock screen and split shade mode
givenLockScreen();
mIsSplitShade = true;
- mHasCustomClock = true;
+ mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding DOESN'T adjust for keyguard status height.
@@ -289,10 +230,20 @@
}
@Test
+ public void notifPositionAdjustedBySmartspaceHeightInSplitShadeMode() {
+ givenLockScreen();
+ mSplitShadeSmartspaceHeightPx = 200;
+ mIsSplitShade = true;
+ // WHEN the position algorithm is run
+ positionClock();
+
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
+ }
+
+ @Test
public void notifPositionWithLargeClockOnLockScreen() {
// GIVEN on lock screen and clock has a nonzero height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -304,7 +255,6 @@
public void notifPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
@@ -317,19 +267,18 @@
public void notifPositionWithLargeClockFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up and a full screen clock
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
positionClock();
- // THEN the notif padding is zero.
+
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(
(int) (mKeyguardStatusHeight * .667f));
}
@Test
public void clockHiddenWhenQsIsExpanded() {
- // GIVEN on the lock screen with a custom clock and visible notifications
+ // GIVEN on the lock screen with visible notifications
givenLockScreen();
mQsExpansion = 1;
// WHEN the clock position algorithm is run
@@ -349,12 +298,12 @@
}
private void positionClock() {
- mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+ mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT,
+ mPanelExpansion, mKeyguardStatusHeight,
0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
- mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+ mDark, ZERO_DRAG, false /* bypassEnabled */,
0 /* unlockedStackScrollerPadding */, mQsExpansion,
- mCutoutTopInset, mIsSplitShade);
+ mCutoutTopInsetPx, mSplitShadeSmartspaceHeightPx, mIsSplitShade);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cbaca3a..1387b8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -63,6 +63,7 @@
import android.view.ViewStub;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -112,6 +113,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -300,6 +302,10 @@
private RecordingController mRecordingController;
@Mock
private ControlsComponent mControlsComponent;
+ @Mock
+ private LockscreenSmartspaceController mLockscreenSmartspaceController;
+ @Mock
+ private FrameLayout mSplitShadeSmartspaceContainer;
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -349,6 +355,8 @@
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
+ when(mView.findViewById(R.id.split_shade_smartspace_container))
+ .thenReturn(mSplitShadeSmartspaceContainer);
mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
@@ -442,6 +450,7 @@
new FakeExecutor(new FakeSystemClock()),
mSecureSettings,
mSplitShadeHeaderController,
+ mLockscreenSmartspaceController,
mUnlockedScreenOffAnimationController,
mNotificationRemoteInputManager,
mControlsComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
new file mode 100644
index 0000000..50cea07
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class PhoneStatusBarViewControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var commandQueue: CommandQueue
+
+ private lateinit var view: PhoneStatusBarView
+ private lateinit var controller: PhoneStatusBarViewController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ view = PhoneStatusBarView(mContext, null)
+ controller = PhoneStatusBarViewController(view, commandQueue)
+ }
+
+ @Test
+ fun constructor_setsPanelEnabledProviderOnView() {
+ var providerUsed = false
+ `when`(commandQueue.panelsEnabled()).then {
+ providerUsed = true
+ true
+ }
+
+ // If the constructor correctly set a [PanelEnabledProvider], then it should be used
+ // when [PhoneStatusBarView.panelEnabled] is called.
+ view.panelEnabled()
+
+ assertThat(providerUsed).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
new file mode 100644
index 0000000..49ab6eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class PhoneStatusBarViewTest : SysuiTestCase() {
+
+ private lateinit var view: PhoneStatusBarView
+
+ @Before
+ fun setUp() {
+ view = PhoneStatusBarView(mContext, null)
+ }
+
+ @Test
+ fun panelEnabled_providerReturnsTrue_returnsTrue() {
+ view.setPanelEnabledProvider { true }
+
+ assertThat(view.panelEnabled()).isTrue()
+ }
+
+ @Test
+ fun panelEnabled_providerReturnsFalse_returnsFalse() {
+ view.setPanelEnabledProvider { false }
+
+ assertThat(view.panelEnabled()).isFalse()
+ }
+
+ @Test
+ fun panelEnabled_noProvider_noCrash() {
+ view.panelEnabled()
+ // No assert needed, just testing no crash
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index cc2afe2..a34c598 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -60,6 +61,68 @@
}
@Test
+ public void testInitiallyAbovePrimary() {
+
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+ assertTrue(mProximitySensor.isRegistered());
+ assertFalse(mThresholdSensorPrimary.isPaused());
+ assertTrue(mThresholdSensorSecondary.isPaused());
+ assertNull(listener.mLastEvent);
+ assertEquals(0, listener.mCallCount);
+
+ mThresholdSensorPrimary.triggerEvent(false, 0);
+ assertNotNull(listener.mLastEvent);
+ assertFalse(listener.mLastEvent.getBelow());
+ assertEquals(1, listener.mCallCount);
+ }
+
+ @Test
+ public void testInitiallyBelowPrimaryAboveSecondary() {
+
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+ assertTrue(mProximitySensor.isRegistered());
+ assertFalse(mThresholdSensorPrimary.isPaused());
+ assertTrue(mThresholdSensorSecondary.isPaused());
+ assertNull(listener.mLastEvent);
+ assertEquals(0, listener.mCallCount);
+
+ mThresholdSensorPrimary.triggerEvent(true, 0);
+ assertNull(listener.mLastEvent);
+ assertEquals(0, listener.mCallCount);
+
+ mThresholdSensorSecondary.triggerEvent(false, 1);
+ assertNotNull(listener.mLastEvent);
+ assertFalse(listener.mLastEvent.getBelow());
+ assertEquals(1, listener.mCallCount);
+ }
+
+ @Test
+ public void testInitiallyBelowPrimaryAndSecondary() {
+
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+ assertTrue(mProximitySensor.isRegistered());
+ assertFalse(mThresholdSensorPrimary.isPaused());
+ assertTrue(mThresholdSensorSecondary.isPaused());
+ assertNull(listener.mLastEvent);
+ assertEquals(0, listener.mCallCount);
+
+ mThresholdSensorPrimary.triggerEvent(true, 0);
+ assertNull(listener.mLastEvent);
+ assertEquals(0, listener.mCallCount);
+
+ mThresholdSensorSecondary.triggerEvent(true, 1);
+ assertNotNull(listener.mLastEvent);
+ assertTrue(listener.mLastEvent.getBelow());
+ assertEquals(1, listener.mCallCount);
+ }
+
+ @Test
public void testPrimaryBelowDoesNotInvokeSecondary() {
TestableListener listener = new TestableListener();
@@ -74,8 +137,6 @@
mThresholdSensorPrimary.triggerEvent(false, 0);
assertFalse(mThresholdSensorPrimary.isPaused());
assertTrue(mThresholdSensorSecondary.isPaused());
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
}
@Test
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 07847f1..45e6f1ec 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -102,6 +102,7 @@
import android.appwidget.AppWidgetManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.ComponentName.WithComponentName;
import android.content.Context;
@@ -307,6 +308,7 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ @Overridable
static final long FGS_BG_START_RESTRICTION_CHANGE_ID = 170668199L;
/**
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6d7966f..64b9bd9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -45,6 +45,9 @@
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
import static android.app.AppOpsManager.OpEventProxyInfo;
import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
@@ -1238,6 +1241,11 @@
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
tag, true, event.getAttributionFlags(), event.getAttributionChainId());
}
+ // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
+ // TODO ntmyren: figure out how to get the real mode.
+ scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+ tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
+ event.getAttributionFlags(), event.getAttributionChainId());
}
mPausedInProgressEvents = null;
}
@@ -3945,13 +3953,15 @@
}
boolean isRestricted = false;
+ int startType = START_TYPE_FAILED;
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED);
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
}
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
@@ -3977,7 +3987,7 @@
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode);
+ flags, uidMode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
@@ -3993,7 +4003,7 @@
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode);
+ flags, mode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
@@ -4011,12 +4021,14 @@
attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags, attributionFlags,
attributionChainId);
+ startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED);
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
}
}
@@ -4187,7 +4199,9 @@
}
private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
+ String attributionTag, @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null;
final int callbackListCount = mStartedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -4213,12 +4227,13 @@
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result));
+ result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
+ @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity();
try {
final int callbackCount = callbacks.size();
@@ -4226,7 +4241,7 @@
final StartedCallback callback = callbacks.valueAt(i);
try {
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result);
+ result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index f4327e8..6f38ed0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -82,7 +82,7 @@
private long mStartTimeMs;
- protected boolean mAuthAttempted;
+ private boolean mAuthAttempted;
// TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
// the state. We should think of a way to improve this in the future.
@@ -98,6 +98,12 @@
*/
protected abstract void handleLifecycleAfterAuth(boolean authenticated);
+ /**
+ * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+ * etc)
+ */
+ public abstract boolean wasUserDetected();
+
public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
@@ -180,7 +186,8 @@
+ ", isBP: " + isBiometricPrompt()
+ ", listener: " + listener
+ ", requireConfirmation: " + mRequireConfirmation
- + ", user: " + getTargetUserId());
+ + ", user: " + getTargetUserId()
+ + ", clientMonitor: " + toString());
final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
if (isCryptoOperation()) {
@@ -304,6 +311,11 @@
public void handleLifecycleAfterAuth() {
AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
}
+
+ @Override
+ public void sendAuthenticationCanceled() {
+ sendCancelOnly(listener);
+ }
});
} else {
// Allow system-defined limit of number of attempts before giving up
@@ -338,10 +350,30 @@
public void handleLifecycleAfterAuth() {
AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
}
+
+ @Override
+ public void sendAuthenticationCanceled() {
+ sendCancelOnly(listener);
+ }
});
}
}
+ private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
+ if (listener == null) {
+ Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null");
+ return;
+ }
+ try {
+ listener.onError(getSensorId(),
+ getCookie(),
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
@Override
public void onAcquired(int acquiredInfo, int vendorCode) {
super.onAcquired(acquiredInfo, vendorCode);
@@ -355,9 +387,11 @@
}
@Override
- public void onError(int errorCode, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
mState = STATE_STOPPED;
+
+ CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError);
}
/**
@@ -419,4 +453,8 @@
public boolean interruptsPrecedingClients() {
return true;
}
+
+ public boolean wasAuthAttempted() {
+ return mAuthAttempted;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index a15ecad..25d4a38 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.util.Slog;
@@ -54,7 +55,7 @@
/**
* Callback interface notifying the owner of "results" from the CoexCoordinator's business
- * logic.
+ * logic for accept and reject.
*/
interface Callback {
/**
@@ -73,6 +74,22 @@
* from scheduler if auth was successful).
*/
void handleLifecycleAfterAuth();
+
+ /**
+ * Requests the owner to notify the caller that authentication was canceled.
+ */
+ void sendAuthenticationCanceled();
+ }
+
+ /**
+ * Callback interface notifying the owner of "results" from the CoexCoordinator's business
+ * logic for errors.
+ */
+ interface ErrorCallback {
+ /**
+ * Requests the owner to initiate a vibration for this event.
+ */
+ void sendHapticFeedback();
}
private static CoexCoordinator sInstance;
@@ -198,6 +215,9 @@
mClientMap.remove(sensorType);
}
+ /**
+ * Notify the coordinator that authentication succeeded (accepted)
+ */
public void onAuthenticationSucceeded(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@NonNull Callback callback) {
@@ -268,6 +288,9 @@
}
}
+ /**
+ * Notify the coordinator that a rejection has occurred.
+ */
public void onAuthenticationRejected(long currentTimeMillis,
@NonNull AuthenticationClient<?> client,
@LockoutTracker.LockoutMode int lockoutMode,
@@ -352,11 +375,63 @@
}
}
+ /**
+ * Notify the coordinator that an error has occurred.
+ */
+ public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
+ @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+ // Figure out non-coex state
+ final boolean shouldUsuallyVibrate;
+ if (isCurrentFaceAuth(client)) {
+ final boolean notDetectedOnKeyguard = client.isKeyguard() && !client.wasUserDetected();
+ final boolean authAttempted = client.wasAuthAttempted();
+
+ switch (error) {
+ case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
+ case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
+ shouldUsuallyVibrate = authAttempted && !notDetectedOnKeyguard;
+ break;
+ default:
+ shouldUsuallyVibrate = false;
+ break;
+ }
+ } else {
+ shouldUsuallyVibrate = false;
+ }
+
+ // Figure out coex state
+ final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+ final boolean hapticSuppressedByCoex;
+
+ if (keyguardAdvancedLogic) {
+ if (isSingleAuthOnly(client)) {
+ hapticSuppressedByCoex = false;
+ } else {
+ hapticSuppressedByCoex = isCurrentFaceAuth(client)
+ && !client.isKeyguardBypassEnabled();
+ }
+ } else {
+ hapticSuppressedByCoex = false;
+ }
+
+ // Combine and send feedback if appropriate
+ Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
+ + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
+ if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
+ callback.sendHapticFeedback();
+ }
+ }
+
@Nullable
private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) {
for (SuccessfulAuth auth : mSuccessfulAuths) {
if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) {
- Slog.d(TAG, "Removing stale auth: " + auth);
+ // TODO(b/193089985): This removes the auth but does not notify the client with
+ // an appropriate lifecycle event (such as ERROR_CANCELED), and violates the
+ // API contract. However, this might be OK for now since the validity duration
+ // is way longer than the time it takes to auth with fingerprint.
+ Slog.e(TAG, "Removing stale auth: " + auth);
mSuccessfulAuths.remove(auth);
} else if (auth.mSensorType == SENSOR_TYPE_FACE) {
mSuccessfulAuths.remove(auth);
@@ -367,9 +442,13 @@
}
private void removeAndFinishAllFaceFromQueue() {
+ // Note that these auth are all successful, but have never notified the client (e.g.
+ // keyguard). To comply with the authentication lifecycle, we must notify the client that
+ // auth is "done". The safest thing to do is to send ERROR_CANCELED.
for (SuccessfulAuth auth : mSuccessfulAuths) {
if (auth.mSensorType == SENSOR_TYPE_FACE) {
- Slog.d(TAG, "Removing from queue and finishing: " + auth);
+ Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth);
+ auth.mCallback.sendAuthenticationCanceled();
auth.mCallback.handleLifecycleAfterAuth();
mSuccessfulAuths.remove(auth);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f7fd8d0..d66a279 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -127,7 +127,8 @@
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -160,7 +161,7 @@
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -169,25 +170,8 @@
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- case BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL:
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- break;
- default:
- break;
+ if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
+ BiometricNotificationUtils.showReEnrollmentNotification(getContext());
}
super.onError(error, vendorCode);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index c33b957..33950af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -115,7 +115,8 @@
}
}
- private boolean wasUserDetected() {
+ @Override
+ public boolean wasUserDetected() {
// Do not provide haptic feedback if the user was not detected, and an error (usually
// ERROR_TIMEOUT) is received.
return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -147,7 +148,7 @@
}
@Override
- public void onError(int error, int vendorCode) {
+ public void onError(@BiometricConstants.Errors int error, int vendorCode) {
mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
getStartTimeMs(),
System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -156,24 +157,6 @@
vendorCode,
getTargetUserId()));
- switch (error) {
- case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
- if (!wasUserDetected() && !isBiometricPrompt()) {
- // No vibration if user was not detected on keyguard
- break;
- }
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
- case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
- if (mAuthAttempted) {
- // Only vibrate if auth was attempted. If the user was already locked out prior
- // to starting authentication, do not vibrate.
- vibrateError();
- }
- break;
- default:
- break;
- }
-
super.onError(error, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8835c1e0..37ee76a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -106,6 +106,12 @@
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 83f1480..5060744 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -153,6 +153,12 @@
}
@Override
+ public boolean wasUserDetected() {
+ // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+ return false;
+ }
+
+ @Override
public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
return super.handleFailedAttempt(userId);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6fb9e58..a23d6cf 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -180,7 +180,7 @@
private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
- SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
+ SystemProperties.getBoolean("persist.debug.per_window_input_rotation", true);
// Pointer to native input manager service object.
private final long mPtr;
@@ -189,7 +189,7 @@
private final InputManagerHandler mHandler;
// Context cache used for loading pointer resources.
- private Context mDisplayContext;
+ private Context mPointerIconDisplayContext;
private final File mDoubleTouchGestureEnableFile;
@@ -839,21 +839,31 @@
throw new IllegalArgumentException("mode is invalid");
}
if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
- if (event instanceof MotionEvent) {
- final Context dispCtx = getContextForDisplay(event.getDisplayId());
- final Display display = dispCtx.getDisplay();
+ // Motion events that are pointer events or relative mouse events will need to have the
+ // inverse display rotation applied to them.
+ if (event instanceof MotionEvent
+ && (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+ || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE))) {
+ Context displayContext = getContextForDisplay(event.getDisplayId());
+ if (displayContext == null) {
+ displayContext = Objects.requireNonNull(
+ getContextForDisplay(Display.DEFAULT_DISPLAY));
+ }
+ final Display display = displayContext.getDisplay();
final int rotation = display.getRotation();
if (rotation != ROTATION_0) {
final MotionEvent motion = (MotionEvent) event;
// Injections are currently expected to be in the space of the injector (ie.
- // usually assumed to be post-rotated). Thus we need to unrotate into raw
+ // usually assumed to be post-rotated). Thus we need to un-rotate into raw
// input coordinates for dispatch.
final Point sz = new Point();
- display.getRealSize(sz);
- if ((rotation % 2) != 0) {
- final int tmpX = sz.x;
- sz.x = sz.y;
- sz.y = tmpX;
+ if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ display.getRealSize(sz);
+ if ((rotation % 2) != 0) {
+ final int tmpX = sz.x;
+ sz.x = sz.y;
+ sz.y = tmpX;
+ }
}
motion.applyTransform(MotionEvent.createRotateMatrix(
(4 - rotation), sz.x, sz.y));
@@ -1742,6 +1752,11 @@
/** Clean up input window handles of the given display. */
public void onDisplayRemoved(int displayId) {
+ if (mPointerIconDisplayContext != null
+ && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+ mPointerIconDisplayContext = null;
+ }
+
nativeDisplayRemoved(mPtr, displayId);
}
@@ -2971,24 +2986,43 @@
// Native callback.
private PointerIcon getPointerIcon(int displayId) {
- return PointerIcon.getDefaultIcon(getContextForDisplay(displayId));
+ return PointerIcon.getDefaultIcon(getContextForPointerIcon(displayId));
}
- private Context getContextForDisplay(int displayId) {
- if (mDisplayContext != null && mDisplayContext.getDisplay().getDisplayId() == displayId) {
- return mDisplayContext;
- }
-
- if (mContext.getDisplay().getDisplayId() == displayId) {
- mDisplayContext = mContext;
- return mDisplayContext;
+ @NonNull
+ private Context getContextForPointerIcon(int displayId) {
+ if (mPointerIconDisplayContext != null
+ && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+ return mPointerIconDisplayContext;
}
// Create and cache context for non-default display.
- final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ mPointerIconDisplayContext = getContextForDisplay(displayId);
+
+ // Fall back to default display if the requested displayId does not exist.
+ if (mPointerIconDisplayContext == null) {
+ mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY);
+ }
+ return mPointerIconDisplayContext;
+ }
+
+ @Nullable
+ private Context getContextForDisplay(int displayId) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ return null;
+ }
+ if (mContext.getDisplay().getDisplayId() == displayId) {
+ return mContext;
+ }
+
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
final Display display = displayManager.getDisplay(displayId);
- mDisplayContext = mContext.createDisplayContext(display);
- return mDisplayContext;
+ if (display == null) {
+ return null;
+ }
+
+ return mContext.createDisplayContext(display);
}
// Native callback.
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index bf82bd8..88dd033 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -176,6 +176,10 @@
name = orig.name;
realName = orig.realName;
doCopy(orig);
+ // Clone the user states.
+ for (int i = 0; i < mUserState.size(); i++) {
+ mUserState.put(mUserState.keyAt(i), new PackageUserState(mUserState.valueAt(i)));
+ }
}
public void setInstallerPackageName(String packageName) {
@@ -314,6 +318,7 @@
void setInstalled(boolean inst, int userId) {
modifyUserState(userId).installed = inst;
+ onChanged();
}
boolean getInstalled(int userId) {
@@ -326,6 +331,7 @@
void setInstallReason(int installReason, int userId) {
modifyUserState(userId).installReason = installReason;
+ onChanged();
}
int getUninstallReason(int userId) {
@@ -334,10 +340,13 @@
void setUninstallReason(@UninstallReason int uninstallReason, int userId) {
modifyUserState(userId).uninstallReason = uninstallReason;
+ onChanged();
}
boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
- return modifyUserState(userId).setOverlayPaths(overlayPaths);
+ boolean returnValue = modifyUserState(userId).setOverlayPaths(overlayPaths);
+ onChanged();
+ return returnValue;
}
OverlayPaths getOverlayPaths(int userId) {
@@ -346,7 +355,10 @@
boolean setOverlayPathsForLibrary(String libName, OverlayPaths overlayPaths,
int userId) {
- return modifyUserState(userId).setSharedLibraryOverlayPaths(libName, overlayPaths);
+ boolean returnValue = modifyUserState(userId)
+ .setSharedLibraryOverlayPaths(libName, overlayPaths);
+ onChanged();
+ return returnValue;
}
Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) {
@@ -395,6 +407,7 @@
void setCeDataInode(long ceDataInode, int userId) {
modifyUserState(userId).ceDataInode = ceDataInode;
+ onChanged();
}
boolean getStopped(int userId) {
@@ -403,6 +416,7 @@
void setStopped(boolean stop, int userId) {
modifyUserState(userId).stopped = stop;
+ onChanged();
}
boolean getNotLaunched(int userId) {
@@ -411,6 +425,7 @@
void setNotLaunched(boolean stop, int userId) {
modifyUserState(userId).notLaunched = stop;
+ onChanged();
}
boolean getHidden(int userId) {
@@ -419,6 +434,7 @@
void setHidden(boolean hidden, int userId) {
modifyUserState(userId).hidden = hidden;
+ onChanged();
}
int getDistractionFlags(int userId) {
@@ -427,6 +443,7 @@
void setDistractionFlags(int distractionFlags, int userId) {
modifyUserState(userId).distractionFlags = distractionFlags;
+ onChanged();
}
boolean getSuspended(int userId) {
@@ -487,6 +504,7 @@
void setInstantApp(boolean instantApp, int userId) {
modifyUserState(userId).instantApp = instantApp;
+ onChanged();
}
boolean getVirtulalPreload(int userId) {
@@ -495,6 +513,7 @@
void setVirtualPreload(boolean virtualPreload, int userId) {
modifyUserState(userId).virtualPreload = virtualPreload;
+ onChanged();
}
void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
@@ -547,20 +566,24 @@
void setEnabledComponents(ArraySet<String> components, int userId) {
modifyUserState(userId).enabledComponents = components;
+ onChanged();
}
void setDisabledComponents(ArraySet<String> components, int userId) {
modifyUserState(userId).disabledComponents = components;
+ onChanged();
}
void setEnabledComponentsCopy(ArraySet<String> components, int userId) {
modifyUserState(userId).enabledComponents = components != null
? new ArraySet<String>(components) : null;
+ onChanged();
}
void setDisabledComponentsCopy(ArraySet<String> components, int userId) {
modifyUserState(userId).disabledComponents = components != null
? new ArraySet<String>(components) : null;
+ onChanged();
}
PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) {
@@ -582,10 +605,12 @@
void addDisabledComponent(String componentClassName, int userId) {
modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName);
+ onChanged();
}
void addEnabledComponent(String componentClassName, int userId) {
modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName);
+ onChanged();
}
boolean enableComponentLPw(String componentClassName, int userId) {
@@ -593,6 +618,9 @@
boolean changed = state.disabledComponents != null
? state.disabledComponents.remove(componentClassName) : false;
changed |= state.enabledComponents.add(componentClassName);
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -601,6 +629,9 @@
boolean changed = state.enabledComponents != null
? state.enabledComponents.remove(componentClassName) : false;
changed |= state.disabledComponents.add(componentClassName);
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -610,6 +641,9 @@
? state.disabledComponents.remove(componentClassName) : false;
changed |= state.enabledComponents != null
? state.enabledComponents.remove(componentClassName) : false;
+ if (changed) {
+ onChanged();
+ }
return changed;
}
@@ -701,6 +735,7 @@
PackageSettingBase setPath(@NonNull File path) {
this.mPath = path;
this.mPathString = path.toString();
+ onChanged();
return this;
}
@@ -722,7 +757,9 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
@Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
- return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+ boolean returnValue = modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+ onChanged();
+ return returnValue;
}
/**
@@ -732,6 +769,7 @@
*/
public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
modifyUserState(userId).resetOverrideComponentLabelIcon();
+ onChanged();
}
/**
@@ -741,6 +779,7 @@
*/
public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
modifyUserState(userId).splashScreenTheme = themeName;
+ onChanged();
}
/**
@@ -776,6 +815,7 @@
*/
public void setStatesOnCommit() {
incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
+ onChanged();
}
/**
@@ -783,6 +823,7 @@
*/
public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
incrementalStates.setCallback(callback);
+ onChanged();
}
/**
@@ -791,6 +832,7 @@
*/
public void setLoadingProgress(float progress) {
incrementalStates.setProgress(progress);
+ onChanged();
}
public long getFirstInstallTime() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 005a62a..20958aa 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -633,6 +633,7 @@
if (crossPackage) {
startLaunchTrace(info);
}
+ scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
return;
}
@@ -654,13 +655,7 @@
// As abort for no process switch.
launchObserverNotifyIntentFailed();
}
- if (launchedActivity.mDisplayContent.isSleeping()) {
- // It is unknown whether the activity can be drawn or not, e.g. it depends on the
- // keyguard states and the attributes or flags set by the activity. If the activity
- // keeps invisible in the grace period, the tracker will be cancelled so it won't get
- // a very long launch time that takes unlocking as the end of launch.
- scheduleCheckActivityToBeDrawn(launchedActivity, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
- }
+ scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
// If the previous transitions are no longer visible, abort them to avoid counting the
// launch time when resuming from back stack. E.g. launch 2 independent tasks in a short
@@ -675,6 +670,16 @@
}
}
+ private void scheduleCheckActivityToBeDrawnIfSleeping(@NonNull ActivityRecord r) {
+ if (r.mDisplayContent.isSleeping()) {
+ // It is unknown whether the activity can be drawn or not, e.g. it depends on the
+ // keyguard states and the attributes or flags set by the activity. If the activity
+ // keeps invisible in the grace period, the tracker will be cancelled so it won't get
+ // a very long launch time that takes unlocking as the end of launch.
+ scheduleCheckActivityToBeDrawn(r, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
+ }
+ }
+
/**
* Notifies the tracker that all windows of the app have been drawn.
*
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 10c3f78..bc5ace8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -702,8 +702,6 @@
private boolean mInSizeCompatModeForBounds = false;
// Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
- // TODO(b/182268157): Aspect ratio can also be applie in resolveFixedOrientationConfiguration
- // but that isn't reflected in this boolean.
private boolean mIsAspectRatioApplied = false;
// Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
@@ -7504,11 +7502,14 @@
* requested orientation. If not, it may be necessary to letterbox the window.
* @param parentBounds are the new parent bounds passed down to the activity and should be used
* to compute the stable bounds.
- * @param outBounds will store the stable bounds, which are the bounds with insets applied.
- * These should be used to compute letterboxed bounds if orientation is not
- * respected when insets are applied.
+ * @param outStableBounds will store the stable bounds, which are the bounds with insets
+ * applied, if orientation is not respected when insets are applied.
+ * Otherwise outStableBounds will be empty. Stable bounds should be used
+ * to compute letterboxed bounds if orientation is not respected when
+ * insets are applied.
*/
- private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
+ private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+ outStableBounds.setEmpty();
if (mDisplayContent == null) {
return true;
}
@@ -7523,17 +7524,21 @@
// Compute orientation from stable parent bounds (= parent bounds with insets applied)
final Task task = getTask();
task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
- outBounds /* outStableBounds */, parentBounds /* bounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */,
mDisplayContent.getDisplayInfo());
- final int orientationWithInsets = outBounds.height() >= outBounds.width()
+ final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
// display rotation will not be enough to respect orientation. However, even if they do
// not match but the orientation with insets applied matches the requested orientation, then
// there is no need to modify the bounds because when insets are applied, the activity will
// have the desired orientation.
- return orientation == orientationWithInsets
+ final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
+ if (orientationRespectedWithInsets) {
+ outStableBounds.setEmpty();
+ }
+ return orientationRespectedWithInsets;
}
/**
@@ -7551,9 +7556,10 @@
int windowingMode) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
- final Rect containerBounds = new Rect(parentBounds);
+ final Rect stableBounds = new Rect();
+ // If orientation is respected when insets are applied, then stableBounds will be empty.
boolean orientationRespectedWithInsets =
- orientationRespectedWithInsets(parentBounds, containerBounds);
+ orientationRespectedWithInsets(parentBounds, stableBounds);
if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
// No need to letterbox because of fixed orientation. Display will handle
// fixed-orientation requests and a display rotation is enough to respect requested
@@ -7590,71 +7596,68 @@
return;
}
- // TODO(b/182268157) merge aspect ratio logic here and in
- // {@link ActivityRecord#applyAspectRatio}
- // if no aspect ratio constraints are provided, parent aspect ratio is used
- float aspectRatio = computeAspectRatio(parentBounds);
+ // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
+ // bounds or stable bounds to unify aspect ratio logic.
+ final Rect parentBoundsWithInsets = orientationRespectedWithInsets
+ ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+ final Rect containingBounds = new Rect();
+ final Rect containingBoundsWithInsets = new Rect();
+ // Need to shrink the containing bounds into a square because the parent orientation
+ // does not match the activity requested orientation.
+ if (forcedOrientation == ORIENTATION_LANDSCAPE) {
+ // Landscape is defined as width > height. Make the container respect landscape
+ // orientation by shrinking height to one less than width. Landscape activity will be
+ // vertically centered within parent bounds with insets, so position vertical bounds
+ // within parent bounds with insets to prevent insets from unnecessarily trimming
+ // vertical bounds.
+ final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
+ parentBoundsWithInsets.bottom);
+ containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
+ bottom);
+ containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+ parentBoundsWithInsets.right, bottom);
+ } else {
+ // Portrait is defined as width <= height. Make the container respect portrait
+ // orientation by shrinking width to match height. Portrait activity will be
+ // horizontally centered within parent bounds with insets, so position horizontal bounds
+ // within parent bounds with insets to prevent insets from unnecessarily trimming
+ // horizontal bounds.
+ final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
+ parentBoundsWithInsets.right);
+ containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
+ parentBounds.bottom);
+ containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+ right, parentBoundsWithInsets.bottom);
+ }
+
+ // Store the current bounds to be able to revert to size compat mode values below if needed.
+ final Rect prevResolvedBounds = new Rect(resolvedBounds);
+ resolvedBounds.set(containingBounds);
// Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
// set-fixed-orientation-letterbox-aspect-ratio.
final float letterboxAspectRatioOverride =
mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
- aspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
- ? letterboxAspectRatioOverride : aspectRatio;
+ final float desiredAspectRatio =
+ letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+ ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
+ // Apply aspect ratio to resolved bounds
+ mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
+ containingBounds, desiredAspectRatio, true);
- // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
- // order to use the extra available space.
- final float maxAspectRatio = info.getMaxAspectRatio();
- final float minAspectRatio = info.getMinAspectRatio();
- if (aspectRatio > maxAspectRatio && maxAspectRatio != 0) {
- aspectRatio = maxAspectRatio;
- } else if (aspectRatio < minAspectRatio) {
- aspectRatio = minAspectRatio;
- }
-
- // Store the current bounds to be able to revert to size compat mode values below if needed.
- final Rect prevResolvedBounds = new Rect(resolvedBounds);
-
- // Compute other dimension based on aspect ratio. Use bounds intersected with insets, stored
- // in containerBounds after calling {@link ActivityRecord#orientationRespectedWithInsets()},
- // to ensure that aspect ratio is respected after insets are applied.
- int activityWidth;
- int activityHeight;
+ // Vertically center if orientation is landscape. Center within parent bounds with insets
+ // to ensure that insets do not trim height. Bounds will later be horizontally centered in
+ // {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- activityWidth = parentBounds.width();
- // Compute height from stable bounds width to ensure orientation respected after insets.
- activityHeight = (int) Math.rint(containerBounds.width() / aspectRatio);
- // Landscape is defined as width > height. To ensure activity is landscape when aspect
- // ratio is close to 1, reduce the height by one pixel.
- if (activityWidth == activityHeight) {
- activityHeight -= 1;
- }
- // Center vertically within stable bounds in landscape to ensure insets do not trim
- // height.
- final int top = containerBounds.centerY() - activityHeight / 2;
- resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + activityHeight);
- } else {
- activityHeight = parentBounds.height();
- // Compute width from stable bounds height to ensure orientation respected after insets.
- activityWidth = (int) Math.rint(containerBounds.height() / aspectRatio);
- // Center horizontally in portrait. For now, align to left and allow
- // {@link ActivityRecord#updateResolvedBoundsHorizontalPosition()} to center
- // horizontally. Exclude left insets from parent to ensure cutout does not trim width.
- final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
- resolvedBounds.set(parentAppBounds.left, parentBounds.top,
- parentAppBounds.left + activityWidth, parentBounds.bottom);
+ final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY();
+ resolvedBounds.offset(0, offsetY);
}
if (mCompatDisplayInsets != null) {
mCompatDisplayInsets.getBoundsByRotation(
mTmpBounds, newParentConfig.windowConfiguration.getRotation());
- // Insets may differ between different rotations, for example in the case of a display
- // cutout. To ensure consistent bounds across rotations, compare the activity dimensions
- // minus insets from the rotation the compat bounds were computed in.
- Task.intersectWithInsetsIfFits(mTmpBounds, parentBounds,
- mCompatDisplayInsets.mStableInsets[mCompatDisplayInsets.mOriginalRotation]);
- if (activityWidth != mTmpBounds.width()
- || activityHeight != mTmpBounds.height()) {
+ if (resolvedBounds.width() != mTmpBounds.width()
+ || resolvedBounds.height() != mTmpBounds.height()) {
// The app shouldn't be resized, we only do fixed orientation letterboxing if the
// compat bounds are also from the same fixed orientation letterbox. Otherwise,
// clear the fixed orientation bounds to show app in size compat mode.
@@ -7690,8 +7693,6 @@
// then they should be aligned later in #updateResolvedBoundsHorizontalPosition().
if (!mTmpBounds.isEmpty()) {
resolvedBounds.set(mTmpBounds);
- // Exclude the horizontal decor area.
- resolvedBounds.left = parentAppBounds.left;
}
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
@@ -7752,13 +7753,6 @@
mIsAspectRatioApplied =
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
- // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
- // the container app bounds. Otherwise the entire container bounds are available.
- final boolean fillContainer = resolvedBounds.equals(containingBounds);
- if (!fillContainer) {
- // The horizontal position should not cover insets.
- resolvedBounds.left = containingAppBounds.left;
- }
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
@@ -7825,6 +7819,7 @@
// Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
// if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
+ final boolean fillContainer = resolvedBounds.equals(containingBounds);
final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
final int screenPosY = containerBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
@@ -8066,6 +8061,12 @@
return true;
}
+ private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
+ Rect containingBounds) {
+ return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
+ 0 /* desiredAspectRatio */, false /* fixedOrientationLetterboxed */);
+ }
+
/**
* Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
* made to outBounds.
@@ -8074,17 +8075,19 @@
*/
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
- Rect containingBounds) {
+ Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
final float minAspectRatio = info.getMinAspectRatio();
if (task == null || rootTask == null
- || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
- || (maxAspectRatio == 0 && minAspectRatio == 0)
+ || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
+ && !fixedOrientationLetterboxed)
+ || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
|| isInVrUiMode(getConfiguration())) {
- // We don't enforce aspect ratio if the activity task is in multiwindow unless it
- // is in size-compat mode. We also don't set it if we are in VR mode.
+ // We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
+ // size-compat mode or is letterboxed from fixed orientation. We also don't set it if we
+ // are in VR mode.
return false;
}
@@ -8092,20 +8095,30 @@
final int containingAppHeight = containingAppBounds.height();
final float containingRatio = computeAspectRatio(containingAppBounds);
+ if (desiredAspectRatio < 1) {
+ desiredAspectRatio = containingRatio;
+ }
+
+ if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
+ desiredAspectRatio = maxAspectRatio;
+ } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
+ desiredAspectRatio = minAspectRatio;
+ }
+
int activityWidth = containingAppWidth;
int activityHeight = containingAppHeight;
- if (containingRatio > maxAspectRatio && maxAspectRatio != 0) {
+ if (containingRatio > desiredAspectRatio) {
if (containingAppWidth < containingAppHeight) {
// Width is the shorter side, so we use that to figure-out what the max. height
// should be given the aspect ratio.
- activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f);
+ activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
} else {
// Height is the shorter side, so we use that to figure-out what the max. width
// should be given the aspect ratio.
- activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f);
+ activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
}
- } else if (containingRatio < minAspectRatio) {
+ } else if (containingRatio < desiredAspectRatio) {
boolean adjustWidth;
switch (getRequestedConfigurationOrientation()) {
case ORIENTATION_LANDSCAPE:
@@ -8133,9 +8146,9 @@
break;
}
if (adjustWidth) {
- activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f);
+ activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
} else {
- activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f);
+ activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
}
}
@@ -8159,6 +8172,13 @@
}
outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
+ // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
+ // container app bounds. Otherwise the entire container bounds are available.
+ if (!outBounds.equals(containingBounds)) {
+ // The horizontal position should not cover insets (e.g. display cutout).
+ outBounds.left = containingAppBounds.left;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 395b25d..c9f3356 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2384,6 +2384,11 @@
}
mTransientLaunch = mOptions.getTransientLaunch();
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
+
+ if (inTaskFragment == null) {
+ inTaskFragment = TaskFragment.fromTaskFragmentToken(
+ mOptions.getLaunchTaskFragmentToken(), mService);
+ }
}
mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null;
@@ -2779,7 +2784,8 @@
}
} else {
// Use the child TaskFragment (if any) as the new parent if the activity can be embedded
- final ActivityRecord top = task.topRunningActivity();
+ final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
+ false /* includingEmbeddedTask */);
newParent = top != null ? top.getTaskFragment() : task;
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index ede4c2e..dca0bbd 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1346,7 +1346,8 @@
+ " activityType=" + task.getActivityType()
+ " windowingMode=" + task.getWindowingMode()
+ " isAlwaysOnTopWhenVisible=" + task.isAlwaysOnTopWhenVisible()
- + " intentFlags=" + task.getBaseIntent().getFlags());
+ + " intentFlags=" + task.getBaseIntent().getFlags()
+ + " isEmbedded=" + task.isEmbedded());
}
switch (task.getActivityType()) {
@@ -1392,6 +1393,11 @@
return false;
}
+ // Ignore the task if it is a embedded task
+ if (task.isEmbedded()) {
+ return false;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3516c75..da3f983 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -365,8 +365,26 @@
return false;
}
+ if (matchingCandidate(task)) {
+ return true;
+ }
+
+ // Looking for the embedded tasks (if any)
+ return !task.isLeafTaskFragment() && task.forAllLeafTaskFragments(
+ this::matchingCandidate);
+ }
+
+ boolean matchingCandidate(TaskFragment taskFragment) {
+ final Task task = taskFragment.asTask();
+ if (task == null) {
+ return false;
+ }
+
// Overlays should not be considered as the task's logical top activity.
- final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
+ // Activities of the tasks that embedded from this one should not be used.
+ final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */,
+ false /* includingEmbeddedTask */);
+
if (r == null || r.finishing || r.mUserId != userId
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch root %s", task, r);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7cb8109..69ad59a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1394,14 +1394,6 @@
return mFindRootHelper.findRoot(ignoreRelinquishIdentity, setToBottomIfNone);
}
- ActivityRecord getTopNonFinishingActivity() {
- return getTopNonFinishingActivity(true /* includeOverlays */);
- }
-
- ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
- return getTopActivity(false /*includeFinishing*/, includeOverlays);
- }
-
ActivityRecord topRunningActivityLocked() {
if (getParent() == null) {
return null;
@@ -2976,11 +2968,54 @@
/** Returns the top-most activity that occludes the given one, or {@code null} if none. */
@Nullable
ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
- final ActivityRecord top = getActivity(ActivityRecord::occludesParent,
- true /* traverseTopToBottom */, activity);
+ final ActivityRecord top = getActivity(r -> {
+ if (r == activity) {
+ // Reached the given activity, return the activity to stop searching.
+ return true;
+ }
+
+ if (!r.occludesParent()) {
+ return false;
+ }
+
+ TaskFragment parent = r.getTaskFragment();
+ if (parent == activity.getTaskFragment()) {
+ // Found it. This activity on top of the given activity on the same TaskFragment.
+ return true;
+ }
+ if (isSelfOrNonEmbeddedTask(parent.asTask())) {
+ // Found it. This activity is the direct child of a leaf Task without being
+ // embedded.
+ return true;
+ }
+ // The candidate activity is being embedded. Checking if the bounds of the containing
+ // TaskFragment equals to the outer TaskFragment.
+ TaskFragment grandParent = parent.getParent().asTaskFragment();
+ while (grandParent != null) {
+ if (!parent.getBounds().equals(grandParent.getBounds())) {
+ // Not occluding the grandparent.
+ break;
+ }
+ if (isSelfOrNonEmbeddedTask(grandParent.asTask())) {
+ // Found it. The activity occludes its parent TaskFragment and the parent
+ // TaskFragment also occludes its parent all the way up.
+ return true;
+ }
+ parent = grandParent;
+ grandParent = parent.getParent().asTaskFragment();
+ }
+ return false;
+ });
return top != activity ? top : null;
}
+ private boolean isSelfOrNonEmbeddedTask(Task task) {
+ if (task == this) {
+ return true;
+ }
+ return task != null && !task.isEmbedded();
+ }
+
@Override
public SurfaceControl.Builder makeAnimationLeash() {
return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 255e443..5d679cf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -293,6 +293,13 @@
mRemoteToken = new RemoteToken(this);
}
+ @NonNull
+ static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
+ @NonNull ActivityTaskManagerService service) {
+ if (token == null) return null;
+ return service.mWindowOrganizerController.getTaskFragment(token);
+ }
+
void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
if (mAdjacentTaskFragment == taskFragment) {
return;
@@ -607,17 +614,68 @@
return false;
}
+ ActivityRecord getTopNonFinishingActivity() {
+ return getTopNonFinishingActivity(true /* includeOverlays */);
+ }
+
+ ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
+ return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */);
+ }
+
+ /**
+ * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
+ * the current user.
+ * @param includeOverlays whether the task overlay activity should be included.
+ * @param includingEmbeddedTask whether the activity in a task that being embedded from this
+ * one should be included.
+ * @see #topRunningActivity(boolean, boolean)
+ * @see ActivityRecord#okToShowLocked()
+ */
+ ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
+ boolean includingEmbeddedTask) {
+ // Split into 4 to avoid object creation due to variable capture.
+ if (includeOverlays) {
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> !r.finishing);
+ }
+ return getActivity((r) -> !r.finishing && r.getTask() == this.getTask());
+ }
+
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
+ }
+ return getActivity(
+ (r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask());
+ }
+
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* focusableOnly */);
}
ActivityRecord topRunningActivity(boolean focusableOnly) {
- // Split into 2 to avoid object creation due to variable capture.
+ return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */);
+ }
+
+ /**
+ * Returns the top-most running activity, which the activity is non-finishing and ok to show
+ * to the current user.
+ *
+ * @see ActivityRecord#canBeTopRunning()
+ */
+ ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) {
+ // Split into 4 to avoid object creation due to variable capture.
if (focusableOnly) {
- return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
- } else {
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
+ }
+ return getActivity(
+ (r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask());
+ }
+
+ if (includingEmbeddedTask) {
return getActivity(ActivityRecord::canBeTopRunning);
}
+ return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask());
}
boolean isTopActivityFocusable() {
@@ -1340,6 +1398,8 @@
} else {
prev.schedulePauseTimeout();
+ // Unset readiness since we now need to wait until this pause is complete.
+ mAtmService.getTransitionController().setReady(this, false /* ready */);
return true;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6bfa611..a55fc4e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -657,7 +657,7 @@
ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
for (int i = mParticipants.size() - 1; i >= 0; --i) {
ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
- if (r == null) continue;
+ if (r == null || !r.mVisibleRequested) continue;
// At this point, r is "ready", but if it's not "ALL ready" then it is probably only
// ready due to starting-window.
reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e62a6e4..b5d98a6 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1174,6 +1174,11 @@
taskFragment.removeImmediately();
}
+ @Nullable
+ TaskFragment getTaskFragment(IBinder tfToken) {
+ return mLaunchTaskFragments.get(tfToken);
+ }
+
static class CallerInfo {
final int mPid;
final int mUid;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index c12eb32..e98a4dd 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -64,12 +64,16 @@
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
// Stop watching
appOpsManager.stopWatchingStarted(listener);
@@ -94,7 +98,9 @@
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
- eq(AppOpsManager.MODE_ALLOWED));
+ eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+ eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+ eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener);
// Finish up
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8592166a..f4d1499 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -378,6 +378,11 @@
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
@@ -407,6 +412,11 @@
protected void handleLifecycleAfterAuth(boolean authenticated) {
}
+
+ @Override
+ public boolean wasUserDetected() {
+ return false;
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index 7f5f3c2..bfb0be7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.withSettings;
import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
@@ -61,6 +62,8 @@
private Context mContext;
@Mock
private CoexCoordinator.Callback mCallback;
+ @Mock
+ private CoexCoordinator.ErrorCallback mErrorCallback;
@Before
public void setUp() {
@@ -255,13 +258,16 @@
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+ // For easier reading
+ final CoexCoordinator.Callback faceCallback = mCallback;
+
mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
- mCallback);
- verify(mCallback, never()).sendHapticFeedback();
- verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+ faceCallback);
+ verify(faceCallback, never()).sendHapticFeedback();
+ verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
// CoexCoordinator requests the system to hold onto this AuthenticationClient until
// UDFPS result is known
- verify(mCallback, never()).handleLifecycleAfterAuth();
+ verify(faceCallback, never()).handleLifecycleAfterAuth();
// Reset the mock
CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
@@ -274,6 +280,8 @@
verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
verify(udfpsCallback).handleLifecycleAfterAuth();
+ verify(faceCallback).sendAuthenticationCanceled();
+
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
@@ -281,16 +289,16 @@
if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
verify(udfpsCallback, never()).sendHapticFeedback();
- verify(mCallback).sendHapticFeedback();
- verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
- verify(mCallback).handleLifecycleAfterAuth();
+ verify(faceCallback).sendHapticFeedback();
+ verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
+ verify(faceCallback).handleLifecycleAfterAuth();
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
} else {
assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
- verify(mCallback, never()).sendHapticFeedback();
- verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+ verify(faceCallback, never()).sendHapticFeedback();
+ verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
verify(udfpsCallback).sendHapticFeedback();
verify(udfpsCallback)
@@ -485,4 +493,82 @@
verify(callback).handleLifecycleAfterAuth();
verify(successfulAuths).remove(eq(auth));
}
+
+ @Test
+ public void testBiometricPrompt_FaceError() {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isBiometricPrompt()).thenReturn(true);
+ when(client.wasAuthAttempted()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassEnabled() {
+ testKeyguard_faceAuthOnly(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_faceAuthOnly_errorWhenBypassDisabled() {
+ testKeyguard_faceAuthOnly(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ when(client.isKeyguard()).thenReturn(true);
+ when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(client.wasAuthAttempted()).thenReturn(true);
+ when(client.wasUserDetected()).thenReturn(true);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+ mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ mErrorCallback);
+ verify(mErrorCallback).sendHapticFeedback();
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassEnabled() {
+ testKeyguard_coex_faceError(true /* bypassEnabled */);
+ }
+
+ @Test
+ public void testKeyguard_coex_faceErrorWhenBypassDisabled() {
+ testKeyguard_coex_faceError(false /* bypassEnabled */);
+ }
+
+ private void testKeyguard_coex_faceError(boolean bypassEnabled) {
+ mCoexCoordinator.reset();
+
+ AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+ when(faceClient.isKeyguard()).thenReturn(true);
+ when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+ when(faceClient.wasAuthAttempted()).thenReturn(true);
+ when(faceClient.wasUserDetected()).thenReturn(true);
+
+ AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+ withSettings().extraInterfaces(Udfps.class));
+ when(udfpsClient.isKeyguard()).thenReturn(true);
+ when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+ mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+ mCoexCoordinator.onAuthenticationError(faceClient,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+ if (bypassEnabled) {
+ verify(mErrorCallback).sendHapticFeedback();
+ } else {
+ verify(mErrorCallback, never()).sendHapticFeedback();
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index fa1f4ac..b282cd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -27,6 +27,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
@@ -70,6 +71,7 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
+ private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
private ActivityMetricsLogger mActivityMetricsLogger;
private ActivityMetricsLogger.LaunchingState mLaunchingState;
private ActivityMetricsLaunchObserver mLaunchObserver;
@@ -137,7 +139,7 @@
// messages that are waiting for the lock.
waitHandlerIdle(mAtm.mH);
// AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
- return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
+ return verify(mock, timeout(TIMEOUT_MS));
}
private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
@@ -258,15 +260,40 @@
@Test
public void testOnActivityLaunchWhileSleeping() {
- notifyActivityLaunching(mTopActivity.intent);
- notifyActivityLaunched(START_SUCCESS, mTopActivity);
- doReturn(true).when(mTopActivity.mDisplayContent).isSleeping();
- mTopActivity.setState(ActivityRecord.State.RESUMED, "test");
- mTopActivity.setVisibility(false);
+ notifyActivityLaunching(mTrampolineActivity.intent);
+ notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
+ doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping();
+ mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test");
+ mTrampolineActivity.setVisibility(false);
waitHandlerIdle(mAtm.mH);
// Not cancel immediately because in one of real cases, the keyguard may be going away or
// occluded later, then the activity can be drawn.
- verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity));
+ verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+
+ clearInvocations(mLaunchObserver);
+ mLaunchTopByTrampoline = true;
+ mTopActivity.mVisibleRequested = false;
+ notifyActivityLaunching(mTopActivity.intent);
+ // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
+ // the launch event is still valid.
+ notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
+ // The posted message will acquire wm lock, so the test needs to release the lock to verify.
+ final Throwable error = awaitInWmLock(() -> {
+ try {
+ // Though the aborting target should be eqProto(mTopActivity), use any() to avoid
+ // any changes in proto that may cause failure by different arguments.
+ verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any());
+ } catch (Throwable e) {
+ // Catch any errors including assertion because this runs in another thread.
+ return e;
+ }
+ return null;
+ });
+ // The launch event must be cancelled because the activity keeps invisible.
+ if (error != null) {
+ throw new AssertionError(error);
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index e3c38b0..1b078b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -788,6 +788,19 @@
}
@Test
+ public void testVisibleEmbeddedTask_expectNotVisible() {
+ Task task = createTaskBuilder(".Task")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ doReturn(true).when(task).isEmbedded();
+ mRecentTasks.add(task);
+
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertFalse("embedded task should not be visible recents",
+ mRecentTasks.isVisibleRecentTask(task));
+ }
+
+ @Test
public void testFreezeTaskListOrder_reorderExistingTask() {
// Add some tasks
mRecentTasks.add(mTasks.get(0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d10c006..3bebf6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1986,6 +1986,61 @@
assertTrue(mActivity.areBoundsLetterboxed());
}
+ /**
+ * Tests that all three paths in which aspect ratio logic can be applied yield the same
+ * result, which is that aspect ratio is respected on app bounds. The three paths are
+ * fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode.
+ */
+ @Test
+ public void testAllAspectRatioLogicConsistent() {
+ // Create display that has all stable insets and does not rotate. Make sure that status bar
+ // height is greater than notch height so that stable bounds do not equal app bounds.
+ final int notchHeight = 75;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setNotch(notchHeight)
+ .setStatusBarHeight(notchHeight + 20).setCanRotate(false).build();
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Target min aspect ratio must be larger than parent aspect ratio to be applied.
+ final float targetMinAspectRatio = 3.0f;
+
+ // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Create activity with no fixed orientation and min aspect ratio greater than parent aspect
+ // ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+ // aspect ratio.
+ final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ // Resize display running unresizeable activity to make it enter size compat mode.
+ resizeDisplay(display, 1800, 1000);
+ final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Check that aspect ratio of app bounds is equal to the min aspect ratio.
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(fixedOrientationAppBounds), delta);
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(minAspectRatioAppBounds), delta);
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(sizeCompatAppBounds), delta);
+ }
+
private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
float letterboxHorizontalPositionMultiplier) {
// Set up a display in landscape and ignoring orientation request.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index ce2d748..0e504d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -19,7 +19,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -77,6 +79,7 @@
private int mPosition = POSITION_BOTTOM;
protected final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
+ private int mStatusBarHeight = 0;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -125,6 +128,10 @@
Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
return this;
}
+ Builder setStatusBarHeight(int height) {
+ mStatusBarHeight = height;
+ return this;
+ }
Builder setCanRotate(boolean canRotate) {
mCanRotate = canRotate;
return this;
@@ -158,6 +165,14 @@
doReturn(false).when(displayPolicy).hasStatusBar();
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
+ if (mStatusBarHeight > 0) {
+ doReturn(true).when(displayPolicy).hasStatusBar();
+ doAnswer(invocation -> {
+ Rect inOutInsets = (Rect) invocation.getArgument(0);
+ inOutInsets.top = mStatusBarHeight;
+ return null;
+ }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
+ }
Configuration c = new Configuration();
newDisplay.computeScreenConfiguration(c);
c.windowConfiguration.setWindowingMode(mWindowingMode);
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 628c480..4df8a4b 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -4,13 +4,14 @@
breadley@google.com
fionaxu@google.com
jackyu@google.com
-hallliu@google.com
rgreenwalt@google.com
tgunn@google.com
jminjie@google.com
shuoq@google.com
-refuhoo@google.com
nazaninb@google.com
sarahchin@google.com
-dbright@google.com
xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
index c18ab33..f004824 100644
--- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
@@ -193,6 +193,10 @@
return mDelegateBinder;
}
+ public ISipDelegateStateCallback getStateCallbackBinder() {
+ return mStateBinder;
+ }
+
private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) {
String transactionId = m.getViaBranchParameter();
SipDelegate d = mDelegate;
diff --git a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
index 1f74c09..13ea9973 100644
--- a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.IBinder;
+import android.os.RemoteException;
import android.telephony.ims.DelegateMessageCallback;
import android.telephony.ims.DelegateRequest;
import android.telephony.ims.DelegateStateCallback;
@@ -33,6 +34,7 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -49,10 +51,15 @@
public class SipTransportImplBase {
private static final String LOG_TAG = "SipTransportIB";
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
- mBinderExecutor.execute(() -> binderDiedInternal());
+ // Clean up all binders in this case.
+ mBinderExecutor.execute(() -> binderDiedInternal(null));
+ }
+ @Override
+ public void binderDied(IBinder who) {
+ mBinderExecutor.execute(() -> binderDiedInternal(who));
}
};
@@ -142,6 +149,7 @@
ISipDelegateStateCallback cb, ISipDelegateMessageCallback mc) {
SipDelegateAidlWrapper wrapper = new SipDelegateAidlWrapper(mBinderExecutor, cb, mc);
mDelegates.add(wrapper);
+ linkDeathRecipient(wrapper);
createSipDelegate(subId, r, wrapper, wrapper);
}
@@ -155,6 +163,7 @@
}
if (result != null) {
+ unlinkDeathRecipient(result);
mDelegates.remove(result);
destroySipDelegate(result.getDelegate(), reason);
} else {
@@ -163,12 +172,37 @@
}
}
- private void binderDiedInternal() {
- for (SipDelegateAidlWrapper w : mDelegates) {
- destroySipDelegate(w.getDelegate(),
- SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ private void linkDeathRecipient(SipDelegateAidlWrapper w) {
+ try {
+ w.getStateCallbackBinder().asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "linkDeathRecipient, remote process already died, cleaning up.");
+ mDeathRecipient.binderDied(w.getStateCallbackBinder().asBinder());
}
- mDelegates.clear();
+ }
+
+ private void unlinkDeathRecipient(SipDelegateAidlWrapper w) {
+ try {
+ w.getStateCallbackBinder().asBinder().unlinkToDeath(mDeathRecipient, 0);
+ } catch (NoSuchElementException e) {
+ // Ignore this case.
+ }
+ }
+
+ private void binderDiedInternal(IBinder who) {
+ for (SipDelegateAidlWrapper w : mDelegates) {
+ // If the binder itself was not given from the platform, just clean up all binders.
+ if (who == null || w.getStateCallbackBinder().asBinder().equals(who)) {
+ Log.w(LOG_TAG, "Binder death detected for " + w + ", calling destroy and "
+ + "removing.");
+ mDelegates.remove(w);
+ destroySipDelegate(w.getDelegate(),
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ return;
+ }
+ }
+ Log.w(LOG_TAG, "Binder death detected for IBinder " + who + ", but couldn't find matching "
+ + "SipDelegate");
}
/**