Merge "Fix deadlock between WMGlobal and WMS" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cea16d6..e6ee975 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -318,7 +318,8 @@
private final List<JobRestriction> mJobRestrictions;
@GuardedBy("mLock")
- private final BatteryStateTracker mBatteryStateTracker;
+ @VisibleForTesting
+ final BatteryStateTracker mBatteryStateTracker;
@GuardedBy("mLock")
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
@@ -4261,20 +4262,34 @@
.sendToTarget();
}
- private final class BatteryStateTracker extends BroadcastReceiver {
- /**
- * Track whether we're "charging", where charging means that we're ready to commit to
- * doing work.
- */
- private boolean mCharging;
+ @VisibleForTesting
+ final class BatteryStateTracker extends BroadcastReceiver
+ implements BatteryManagerInternal.ChargingPolicyChangeListener {
+ private final BatteryManagerInternal mBatteryManagerInternal;
+
+ /** Last reported battery level. */
+ private int mBatteryLevel;
/** Keep track of whether the battery is charged enough that we want to do work. */
private boolean mBatteryNotLow;
+ /**
+ * Charging status based on {@link BatteryManager#ACTION_CHARGING} and
+ * {@link BatteryManager#ACTION_DISCHARGING}.
+ */
+ private boolean mCharging;
+ /**
+ * The most recently acquired value of
+ * {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ */
+ private int mChargingPolicy;
+ /** Track whether there is power connected. It doesn't mean the device is charging. */
+ private boolean mPowerConnected;
/** Sequence number of last broadcast. */
private int mLastBatterySeq = -1;
private BroadcastReceiver mMonitor;
BatteryStateTracker() {
+ mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
}
public void startTracking() {
@@ -4286,13 +4301,18 @@
// Charging/not charging.
filter.addAction(BatteryManager.ACTION_CHARGING);
filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
getTestableContext().registerReceiver(this, filter);
+ mBatteryManagerInternal.registerChargingPolicyChangeListener(this);
+
// Initialise tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
- mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ mBatteryNotLow = !mBatteryManagerInternal.getBatteryLevelLow();
+ mCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mChargingPolicy = mBatteryManagerInternal.getChargingPolicy();
}
public void setMonitorBatteryLocked(boolean enabled) {
@@ -4315,7 +4335,7 @@
}
public boolean isCharging() {
- return mCharging;
+ return isConsideredCharging();
}
public boolean isBatteryNotLow() {
@@ -4326,17 +4346,42 @@
return mMonitor != null;
}
+ public boolean isPowerConnected() {
+ return mPowerConnected;
+ }
+
public int getSeq() {
return mLastBatterySeq;
}
@Override
+ public void onChargingPolicyChanged(int newPolicy) {
+ synchronized (mLock) {
+ if (mChargingPolicy == newPolicy) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Charging policy changed from " + mChargingPolicy + " to " + newPolicy);
+ }
+
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mChargingPolicy = newPolicy;
+
+ if (isConsideredCharging() != wasConsideredCharging) {
+ for (int c = mControllers.size() - 1; c >= 0; --c) {
+ mControllers.get(c).onBatteryStateChangedLocked();
+ }
+ }
+ }
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
onReceiveInternal(intent);
}
- @VisibleForTesting
- public void onReceiveInternal(Intent intent) {
+ private void onReceiveInternal(Intent intent) {
synchronized (mLock) {
final String action = intent.getAction();
boolean changed = false;
@@ -4356,21 +4401,49 @@
mBatteryNotLow = true;
changed = true;
}
+ } else if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Battery level changed @ "
+ + sElapsedRealtimeClock.millis());
+ }
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ changed = isConsideredCharging() != wasConsideredCharging;
+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (mPowerConnected) {
+ return;
+ }
+ mPowerConnected = true;
+ changed = true;
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (!mPowerConnected) {
+ return;
+ }
+ mPowerConnected = false;
+ changed = true;
} else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
}
if (!mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = true;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
}
if (mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = false;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
}
mLastBatterySeq =
@@ -4382,6 +4455,30 @@
}
}
}
+
+ private boolean isConsideredCharging() {
+ if (mCharging) {
+ return true;
+ }
+ // BatteryService (or Health HAL or whatever central location makes sense)
+ // should ideally hold this logic so that everyone has a consistent
+ // idea of when the device is charging (or an otherwise stable charging/plugged state).
+ // TODO(304512874): move this determination to BatteryService
+ if (!mPowerConnected) {
+ return false;
+ }
+
+ if (mChargingPolicy == Integer.MIN_VALUE) {
+ // Property not supported on this device.
+ return false;
+ }
+ // Adaptive charging policies don't expose their target battery level, but 80% is a
+ // commonly used threshold for battery health, so assume that's what's being used by
+ // the policies and use 70%+ as the threshold here for charging in case some
+ // implementations choose to discharge the device slightly before recharging back up
+ // to the target level.
+ return mBatteryLevel >= 70 && BatteryManager.isAdaptiveChargingPolicy(mChargingPolicy);
+ }
}
final class LocalService implements JobSchedulerInternal {
@@ -5450,6 +5547,13 @@
}
}
+ /** Return {@code true} if the device is connected to power. */
+ public boolean isPowerConnected() {
+ synchronized (mLock) {
+ return mBatteryStateTracker.isPowerConnected();
+ }
+ }
+
int getStorageSeq() {
synchronized (mLock) {
return mStorageController.getTracker().getSeq();
@@ -5778,8 +5882,14 @@
mQuotaTracker.dump(pw);
pw.println();
+ pw.print("Power connected: ");
+ pw.println(mBatteryStateTracker.isPowerConnected());
pw.print("Battery charging: ");
- pw.println(mBatteryStateTracker.isCharging());
+ pw.println(mBatteryStateTracker.mCharging);
+ pw.print("Considered charging: ");
+ pw.println(mBatteryStateTracker.isConsideredCharging());
+ pw.print("Battery level: ");
+ pw.println(mBatteryStateTracker.mBatteryLevel);
pw.print("Battery not low: ");
pw.println(mBatteryStateTracker.isBatteryNotLow());
if (mBatteryStateTracker.isMonitoring()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index ddbc2ec..e9f9b14 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -20,12 +20,6 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -36,7 +30,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -60,8 +53,6 @@
@GuardedBy("mLock")
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
- private final PowerTracker mPowerTracker;
-
private final FlexibilityController mFlexibilityController;
/**
* Helper set to avoid too much GC churn from frequent calls to
@@ -77,16 +68,10 @@
public BatteryController(JobSchedulerService service,
FlexibilityController flexibilityController) {
super(service);
- mPowerTracker = new PowerTracker();
mFlexibilityController = flexibilityController;
}
@Override
- public void startTrackingLocked() {
- mPowerTracker.startTracking();
- }
-
- @Override
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasPowerConstraint()) {
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -95,7 +80,7 @@
if (taskStatus.hasChargingConstraint()) {
if (hasTopExemptionLocked(taskStatus)) {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
- mPowerTracker.isPowerConnected());
+ mService.isPowerConnected());
} else {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
mService.isBatteryCharging() && mService.isBatteryNotLow());
@@ -178,7 +163,7 @@
@GuardedBy("mLock")
private void maybeReportNewChargingStateLocked() {
- final boolean powerConnected = mPowerTracker.isPowerConnected();
+ final boolean powerConnected = mService.isPowerConnected();
final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
final boolean batteryNotLow = mService.isBatteryNotLow();
if (DEBUG) {
@@ -239,62 +224,6 @@
mChangedJobs.clear();
}
- private final class PowerTracker extends BroadcastReceiver {
- /**
- * Track whether there is power connected. It doesn't mean the device is charging.
- * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
- * charging.
- */
- private boolean mPowerConnected;
-
- PowerTracker() {
- }
-
- void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- mContext.registerReceiver(this, filter);
-
- // Initialize tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
- }
-
- boolean isPowerConnected() {
- return mPowerConnected;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- final String action = intent.getAction();
-
- if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
- }
- if (mPowerConnected) {
- return;
- }
- mPowerConnected = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
- }
- if (!mPowerConnected) {
- return;
- }
- mPowerConnected = false;
- }
-
- maybeReportNewChargingStateLocked();
- }
- }
- }
-
@VisibleForTesting
ArraySet<JobStatus> getTrackedJobs() {
return mTrackedTasks;
@@ -308,7 +237,6 @@
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
- pw.println("Power connected: " + mPowerTracker.isPowerConnected());
pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
pw.println("Not low: " + mService.isBatteryNotLow());
diff --git a/api/Android.bp b/api/Android.bp
index 27c372a..a148cbd 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -200,7 +200,7 @@
out: ["current.srcjar"],
cmd: "$(location merge_zips) $(out) $(in)",
srcs: [
- ":api-stubs-docs-non-updatable",
+ ":api-stubs-docs-non-updatable{.exportable}",
":all-modules-public-stubs-source",
],
visibility: ["//visibility:private"], // Used by make module in //development, mind
diff --git a/core/api/current.txt b/core/api/current.txt
index 4351143..c896d5c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12410,7 +12410,7 @@
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
- method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -12424,6 +12424,12 @@
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ @FlaggedApi("android.content.pm.archiving") public static class LauncherApps.ArchiveCompatibilityParams {
+ ctor public LauncherApps.ArchiveCompatibilityParams();
+ method public void setEnableIconOverlay(boolean);
+ method public void setEnableUnarchivalConfirmation(boolean);
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -16519,8 +16525,8 @@
field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
}
@@ -17997,7 +18003,7 @@
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -26626,6 +26632,8 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+ field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONESHOT = 3; // 0x3
+ field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONEWAY = 2; // 0x2
field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
}
@@ -27299,6 +27307,24 @@
field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1
field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2
field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON = "video_unavailable_reason";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+ field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
field public static final int SIGNAL_STRENGTH_LOST = 1; // 0x1
field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
@@ -27414,6 +27440,7 @@
method public void notifyTuned(@NonNull android.net.Uri);
method public void notifyTvMessage(int, @NonNull android.os.Bundle);
method public void notifyVideoAvailable();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
method public void notifyVideoUnavailable(int);
method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer);
method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27450,8 +27477,10 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void onTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
method public void onTvMessage(int, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
+ method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void sendTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
method public void setOverlayViewEnabled(boolean);
}
@@ -27582,6 +27611,7 @@
method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
method public void setTvMessageEnabled(int, boolean);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setVideoFrozen(boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
@@ -27625,6 +27655,7 @@
method public void onTuned(@NonNull String, @NonNull android.net.Uri);
method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle);
method public void onVideoAvailable(String);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(@NonNull String, boolean);
method public void onVideoSizeChanged(String, int, int);
method public void onVideoUnavailable(String, int);
}
@@ -27635,6 +27666,27 @@
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
+ method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+ method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+ method public void unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+ field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.ad.action.APP_LINK_COMMAND";
+ field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+ field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+ field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+ field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+ field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+ field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+ field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+ field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+ field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+ field public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+ field public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+ field public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+ field public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+ field public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+ field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+ field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+ field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
}
public abstract static class TvAdManager.TvAdServiceCallback {
@@ -27646,6 +27698,7 @@
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
ctor public TvAdService();
+ method public void onAppLinkCommand(@NonNull android.os.Bundle);
method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
@@ -27654,17 +27707,26 @@
public abstract static class TvAdService.Session implements android.view.KeyEvent.Callback {
ctor public TvAdService.Session(@NonNull android.content.Context);
+ method public boolean isMediaViewEnabled();
method @CallSuper public void layoutSurface(int, int, int, int);
+ method @Nullable public android.view.View onCreateMediaView();
method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, @Nullable android.view.KeyEvent);
method public boolean onKeyUp(int, @Nullable android.view.KeyEvent);
+ method public void onMediaViewSizeChanged(@Px int, @Px int);
method public abstract void onRelease();
+ method public void onResetAdService();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+ method public void onStartAdService();
+ method public void onStopAdService();
method public void onSurfaceChanged(int, int, int);
method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+ method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+ method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
+ method @CallSuper public void setMediaViewEnabled(boolean);
}
@FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdServiceInfo implements android.os.Parcelable {
@@ -27681,12 +27743,26 @@
ctor public TvAdView(@NonNull android.content.Context);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+ method public void clearOnUnhandledInputEventListener();
+ method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
+ method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
method public void onMeasure(int, int);
+ method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
method public void onVisibilityChanged(@NonNull android.view.View, int);
method public void prepareAdService(@NonNull String, @NonNull String);
+ method public void reset();
+ method public void resetAdService();
+ method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+ method public boolean setTvView(@Nullable android.media.tv.TvView);
+ method public void startAdService();
+ method public void stopAdService();
+ }
+
+ public static interface TvAdView.OnUnhandledInputEventListener {
+ method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
}
}
@@ -27799,6 +27875,7 @@
method public void onAdResponse(@NonNull android.media.tv.AdResponse);
method public void onAvailableSpeeds(@NonNull float[]);
method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
method public void onContentAllowed();
method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
method public void onCreateBiInteractiveAppRequest(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -27848,11 +27925,13 @@
method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void onVideoAvailable();
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(boolean);
method public void onVideoUnavailable(int);
method @CallSuper public void removeBroadcastInfo(int);
method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
method @CallSuper public void requestAvailableSpeeds();
method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestCertificate(@NonNull String, int);
method @CallSuper public void requestCurrentChannelLcn();
method @CallSuper public void requestCurrentChannelUri();
method @CallSuper public void requestCurrentTvInputId();
@@ -27861,6 +27940,7 @@
method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
method @CallSuper public void requestStopRecording(@NonNull String);
method @CallSuper public void requestStreamVolume();
@@ -27910,6 +27990,7 @@
method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
method public void notifyTimeShiftStatusChanged(@NonNull String, int);
method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
@@ -27920,6 +28001,7 @@
method public void reset();
method public void resetInteractiveApp();
method public void sendAvailableSpeeds(@NonNull float[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
method public void sendCurrentChannelLcn(int);
method public void sendCurrentChannelUri(@Nullable android.net.Uri);
method public void sendCurrentTvInputId(@Nullable String);
@@ -27954,6 +28036,7 @@
method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
method public void onRequestAvailableSpeeds(@NonNull String);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestCertificate(@NonNull String, @NonNull String, int);
method public void onRequestCurrentChannelLcn(@NonNull String);
method public void onRequestCurrentChannelUri(@NonNull String);
method public void onRequestCurrentTvInputId(@NonNull String);
@@ -27962,6 +28045,7 @@
method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
method public void onRequestStopRecording(@NonNull String, @NonNull String);
method public void onRequestStreamVolume(@NonNull String);
@@ -47219,7 +47303,7 @@
method public int getLineForOffset(int);
method public int getLineForVertical(int);
method public float getLineLeft(int);
- method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
+ method @FlaggedApi("com.android.text.flags.letter_spacing_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
method public float getLineMax(int);
method public float getLineRight(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47270,7 +47354,7 @@
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
- field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+ field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a6db0c0..515d017 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11173,6 +11173,7 @@
method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
+ method @FlaggedApi("android.os.storage_lifetime_api") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getInternalStorageRemainingLifetime();
method public static boolean hasIsolatedStorage();
method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
@@ -14756,6 +14757,7 @@
method public boolean areUiccApplicationsEnabled();
method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
method public int getProfileClass();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public int getTransferStatus();
method public boolean isGroupDisabled();
}
@@ -14778,6 +14780,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setTransferStatus(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -14787,6 +14790,9 @@
field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_CONVERTED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1; // 0x1
field @NonNull public static final android.net.Uri VT_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -15652,7 +15658,9 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isPsimConversionSupported(int);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setPsimConversionSupportedCarriers(@NonNull java.util.Set<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0dbce97..5074ed7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -4588,19 +4588,19 @@
*/
private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
@UidState int toUidState, @OpFlags int flags) {
- NoteOpEvent lastAccessEvent = null;
+ NoteOpEvent lastRejectEvent = null;
for (AttributedOpEntry attributionEntry : mAttributedOpEntries.values()) {
- NoteOpEvent lastAttributionAccessEvent = attributionEntry.getLastRejectEvent(
+ NoteOpEvent lastAttributionRejectEvent = attributionEntry.getLastRejectEvent(
fromUidState, toUidState, flags);
- if (lastAccessEvent == null || (lastAttributionAccessEvent != null
- && lastAttributionAccessEvent.getNoteTime()
- > lastAccessEvent.getNoteTime())) {
- lastAccessEvent = lastAttributionAccessEvent;
+ if (lastRejectEvent == null || (lastAttributionRejectEvent != null
+ && lastAttributionRejectEvent.getNoteTime()
+ > lastRejectEvent.getNoteTime())) {
+ lastRejectEvent = lastAttributionRejectEvent;
}
}
- return lastAccessEvent;
+ return lastRejectEvent;
}
/**
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index ea31ef3..112c5fd 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -26,6 +26,8 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.server.backup.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
@@ -56,7 +58,7 @@
*
* @hide
*/
- public static final int DATA_TYPES_ALLOWED = 15;
+ public static final int DATA_TYPES_ALLOWED = 150;
/**
* Denotes that the annotated element identifies a data type as required by the logging methods
@@ -299,7 +301,7 @@
}
if (!mResults.containsKey(dataType)) {
- if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+ if (mResults.keySet().size() == getDataTypesAllowed()) {
// This is a new data type and we're already at capacity.
Slog.d(TAG, "Logger is full, ignoring new data type");
return null;
@@ -315,6 +317,14 @@
return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
}
+ private int getDataTypesAllowed(){
+ if (Flags.enableIncreaseDatatypesForAgentLogging()) {
+ return DATA_TYPES_ALLOWED;
+ } else {
+ return 15;
+ }
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 084cba3..822f02f 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -19,6 +19,9 @@
namespace: "app_widgets"
description: "Move state file IO to non-critical path"
bug: "312949280"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983..7c264f6 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1802,25 +1802,16 @@
}
/**
- * Enable or disable different archive compatibility options of the launcher.
+ * Disable different archive compatibility options of the launcher for the caller of this
+ * method.
*
- * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
- * that a certain app is archived. True by default.
- * Launchers might want to disable this operation if they want to provide custom user experience
- * to differentiate archived apps.
- * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
- * they click an archived app, which explains that the app will be downloaded and restored in
- * the background. True by default.
- * Launchers might want to disable this operation if they provide sufficient, alternative user
- * guidance to highlight that an unarchival is starting and ongoing once an archived app is
- * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ * @see ArchiveCompatibilityParams for individual options.
*/
@FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
- boolean enableUnarchivalConfirmation) {
+ public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) {
try {
- mService.setArchiveCompatibilityOptions(enableIconOverlay,
- enableUnarchivalConfirmation);
+ mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(),
+ params.isEnableUnarchivalConfirmation());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1982,6 +1973,50 @@
}
};
+ /**
+ * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public static class ArchiveCompatibilityParams {
+ private boolean mEnableIconOverlay = true;
+
+ private boolean mEnableUnarchivalConfirmation = true;
+
+ /** @hide */
+ public boolean isEnableIconOverlay() {
+ return mEnableIconOverlay;
+ }
+
+ /** @hide */
+ public boolean isEnableUnarchivalConfirmation() {
+ return mEnableUnarchivalConfirmation;
+ }
+
+ /**
+ * If true, provides a cloud overlay for archived apps to ensure users are aware that a
+ * certain app is archived. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they want to provide custom user
+ * experience to differentiate archived apps.
+ */
+ public void setEnableIconOverlay(boolean enableIconOverlay) {
+ this.mEnableIconOverlay = enableIconOverlay;
+ }
+
+ /**
+ * If true, the user is shown a confirmation dialog when they click an archived app, which
+ * explains that the app will be downloaded and restored in the background. True by default.
+ *
+ * <p> Launchers might want to disable this operation if they provide sufficient,
+ * alternative user guidance to highlight that an unarchival is starting and ongoing once an
+ * archived app is tapped. E.g., this could be achieved by showing the unarchival progress
+ * around the icon.
+ */
+ public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) {
+ this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation;
+ }
+ }
+
private static class CallbackMessageHandler extends Handler {
private static final int MSG_ADDED = 1;
private static final int MSG_REMOVED = 2;
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 4990a27..74ce62c 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -145,6 +145,11 @@
private final boolean mUpdatableSystem;
/**
+ * Name of the emergency installer for the designated system app.
+ */
+ private final @Nullable String mEmergencyInstaller;
+
+ /**
* Archival install info.
*/
private final @Nullable ArchivedPackageParcel mArchivedPackage;
@@ -159,7 +164,8 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+ String emergencyInstaller) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -194,6 +200,7 @@
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
mUpdatableSystem = updatableSystem;
+ mEmergencyInstaller = emergencyInstaller;
mArchivedPackage = null;
}
@@ -232,6 +239,7 @@
mHasDeviceAdminReceiver = false;
mIsSdkLibrary = false;
mUpdatableSystem = true;
+ mEmergencyInstaller = null;
mArchivedPackage = archivedPackage;
}
@@ -550,6 +558,14 @@
}
/**
+ * Name of the emergency installer for the designated system app.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getEmergencyInstaller() {
+ return mEmergencyInstaller;
+ }
+
+ /**
* Archival install info.
*/
@DataClass.Generated.Member
@@ -558,10 +574,10 @@
}
@DataClass.Generated(
- time = 1699587291575L,
+ time = 1706896661616L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 69f9a7d..ffb69c0 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -435,6 +435,7 @@
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isSplitRequired", false);
String configForSplit = parser.getAttributeValue(null, "configForSplit");
+ String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
@@ -644,7 +645,7 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, updatableSystem));
+ hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 1ca11e6..ec46d2f 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -55,3 +55,10 @@
description: "Enables Credential Manager to work with the Biometric Authenticate API"
bug: "323211850"
}
+
+flag {
+ namespace: "wear_frameworks"
+ name: "wear_credential_manager_enabled"
+ description: "Enables Credential Manager on Wear Platform"
+ bug: "301168341"
+}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 4c95e02..a968c6f 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -62,55 +62,55 @@
@SystemApi
public final class ProgramSelector implements Parcelable {
/** Invalid program type.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_INVALID = 0;
/** Analog AM radio (with or without RDS).
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_AM = 1;
/** analog FM radio (with or without RDS).
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link IdentifierType} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_FM = 2;
/** AM HD Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_AM_HD = 3;
/** FM HD Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_FM_HD = 4;
/** Digital audio broadcasting.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_DAB = 5;
/** Digital Radio Mondiale.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_DRMO = 6;
/** SiriusXM Satellite Radio.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_SXM = 7;
/** Vendor-specific, not synced across devices.
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
*/
@Deprecated
public static final int PROGRAM_TYPE_VENDOR_START = 1000;
- /** @deprecated use {@link ProgramIdentifier} instead */
+ /** @deprecated use {@link Identifier} instead */
@Deprecated
public static final int PROGRAM_TYPE_VENDOR_END = 1999;
/**
- * @deprecated use {@link ProgramIdentifier} instead
+ * @deprecated use {@link Identifier} instead
* @removed mistakenly exposed previously
*/
@Deprecated
@@ -271,7 +271,7 @@
*/
public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
/**
- * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+ * @see #IDENTIFIER_TYPE_DAB_SID_EXT
*
* @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
*/
@@ -381,7 +381,7 @@
*/
public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
/**
- * @see {@link IDENTIFIER_TYPE_VENDOR_START}
+ * @see #IDENTIFIER_TYPE_VENDOR_START
*/
public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
/**
@@ -771,7 +771,7 @@
* Returns whether this Identifier's type is considered a category when filtering
* ProgramLists for category entries.
*
- * @see {@link ProgramList.Filter#areCategoriesIncluded()}
+ * @see ProgramList.Filter#areCategoriesIncluded
* @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
* vendor-specified type). True otherwise.
*/
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 41f21ef..61cf8901 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -311,7 +311,7 @@
}
/** Unique module identifier provided by the native service.
- * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+ * For use with {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
* @return the radio module unique identifier.
*/
public int getId() {
@@ -1561,7 +1561,7 @@
/** Main channel expressed in units according to band type.
* Currently all defined band types express channels as frequency in kHz
* @return the program channel
- * @deprecated Use {@link getSelector()} instead.
+ * @deprecated Use {@link ProgramInfo#getSelector} instead.
*/
@Deprecated
public int getChannel() {
@@ -1575,7 +1575,7 @@
/** Sub channel ID. E.g 1 for HD radio HD1
* @return the program sub channel
- * @deprecated Use {@link getSelector()} instead.
+ * @deprecated Use {@link ProgramInfo#getSelector} instead.
*/
@Deprecated
public int getSubChannel() {
@@ -1604,7 +1604,7 @@
/** {@code true} if the received program is digital (e.g HD radio)
* @return {@code true} if digital, {@code false} otherwise.
- * @deprecated Use {@link getLogicallyTunedTo()} instead.
+ * @deprecated Use {@link ProgramInfo#getLogicallyTunedTo()} instead.
*/
@Deprecated
public boolean isDigital() {
@@ -1913,7 +1913,8 @@
* Removes previously registered announcement listener.
*
* @param listener announcement listener, previously registered with
- * {@link addAnnouncementListener}
+ * {@link #addAnnouncementListener(Executor, Set, Announcement.OnListUpdatedListener)}
+ * or {@link #addAnnouncementListener(Set, Announcement.OnListUpdatedListener)}
*/
@RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index da6b9c2..67381ec 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -593,7 +593,7 @@
* Helper for getting the String key used by {@link RadioMetadata} from the
* corrsponding native integer key.
*
- * @param editorKey The key used by the editor
+ * @param nativeKey The key used by the editor
* @return the key used by this class or null if no mapping exists
* @hide
*/
@@ -743,11 +743,11 @@
* Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
* METADATA_KEYs defined in this class are used they may only be one of the following:
* <ul>
- * <li>{@link #MEADATA_KEY_CLOCK}</li>
+ * <li>{@link #METADATA_KEY_CLOCK}</li>
* </ul>
*
* @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
- * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
+ * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes.
* @return the same Builder instance.
*/
public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index b9bb059..f3efd89 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -238,6 +238,16 @@
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ /**
+ * Returns true if the policy is some type of adaptive charging policy.
+ * @hide
+ */
+ public static boolean isAdaptiveChargingPolicy(int policy) {
+ return policy == CHARGING_POLICY_ADAPTIVE_AC
+ || policy == CHARGING_POLICY_ADAPTIVE_AON
+ || policy == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ }
+
// values for "battery part status" property
/**
* Battery part status is not supported.
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 9bad0de..0ec8729 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+
/**
* Battery manager local system service interface.
*
@@ -84,6 +86,26 @@
*/
public abstract boolean getBatteryLevelLow();
+ public interface ChargingPolicyChangeListener {
+ void onChargingPolicyChanged(int newPolicy);
+ }
+
+ /**
+ * Register a listener for changes to {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * The charging policy can't be added to the BATTERY_CHANGED intent because it requires
+ * the BATTERY_STATS permission.
+ */
+ public abstract void registerChargingPolicyChangeListener(
+ @NonNull ChargingPolicyChangeListener chargingPolicyChangeListener);
+
+ /**
+ * Returns the value of {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * This will return {@link Integer#MIN_VALUE} if the device does not support the property.
+ *
+ * @see BatteryManager#getIntProperty(int)
+ */
+ public abstract int getChargingPolicy();
+
/**
* Returns a non-zero value if an unsupported charger is attached.
*
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 82518bf..6c728a4 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -114,3 +114,11 @@
is_fixed_read_only: true
bug: "309792384"
}
+
+flag {
+ name: "storage_lifetime_api"
+ namespace: "phoenix"
+ description: "Feature flag for adding storage component health APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 54ed73c..1ab48a2 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -175,4 +175,12 @@
void setCloudMediaProvider(in String authority) = 96;
String getCloudMediaProvider() = 97;
long getInternalStorageBlockDeviceSize() = 98;
-}
\ No newline at end of file
+ /**
+ * Returns the remaining lifetime of the internal storage device, as an
+ * integer percentage. For example, 90 indicates that 90% of the storage
+ * device's useful lifetime remains. If no information is available, -1
+ * is returned.
+ */
+ @EnforcePermission("READ_PRIVILEGED_PHONE_STATE")
+ int getInternalStorageRemainingLifetime() = 99;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3a57e84..5a09541 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,7 @@
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,6 +59,7 @@
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Flags;
import android.os.Handler;
import android.os.IInstalld;
import android.os.IVold;
@@ -2939,4 +2941,24 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int CRYPT_TYPE_DEFAULT = 1;
+
+ /**
+ * Returns the remaining lifetime of the internal storage device, as an integer percentage. For
+ * example, 90 indicates that 90% of the storage device's useful lifetime remains. If no
+ * information is available, -1 is returned.
+ *
+ * @return Percentage of the remaining useful lifetime of the internal storage device.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_STORAGE_LIFETIME_API)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getInternalStorageRemainingLifetime() {
+ try {
+ return mStorageManager.getInternalStorageRemainingLifetime();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2841dc0..658cec8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4943,6 +4943,13 @@
public static final String COLUMN_IS_NTN = "is_ntn";
/**
+ * TelephonyProvider column name for transferred status
+ *
+ * @hide
+ */
+ public static final String COLUMN_TRANSFER_STATUS = "transfer_status";
+
+ /**
* TelephonyProvider column name to indicate the service capability bitmasks.
*
* @hide
@@ -5021,7 +5028,8 @@
COLUMN_SATELLITE_ENABLED,
COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
COLUMN_IS_NTN,
- COLUMN_SERVICE_CAPABILITIES
+ COLUMN_SERVICE_CAPABILITIES,
+ COLUMN_TRANSFER_STATUS
);
/**
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1ea80f1..8ddb42d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -172,7 +172,7 @@
/**
* Value for justification mode indicating the text is justified by stretching letter spacing.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
@@ -1831,7 +1831,7 @@
* @return the number of cluster count in the line.
*/
@IntRange(from = 0)
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
boolean includeTrailingWhitespace) {
final int start = getLineStart(line);
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 224e5d8..bde9c77 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -305,7 +305,7 @@
}
mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
mAddedLetterSpacingInPx = 0;
- } else { // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+ } else { // justificationMode == Layout.JUSTIFICATION_MODE_LETTER_SPACING
LineInfo lineInfo = new LineInfo();
float width = Math.abs(measure(end, false, null, null, lineInfo));
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a49aee1..f37c4c2a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -77,7 +77,7 @@
}
flag {
- name: "inter_character_justification"
+ name: "letter_spacing_justification"
namespace: "text"
description: "A feature flag that implement inter character justification."
bug: "283193133"
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 83acc47..d433ca3 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -227,6 +227,9 @@
private String requiredAccountType;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
+ private String mEmergencyInstaller;
+ @Nullable
+ @DataClass.ParcelWith(ForInternedString.class)
private String overlayTarget;
@Nullable
@DataClass.ParcelWith(ForInternedString.class)
@@ -1275,6 +1278,12 @@
return restrictedAccountType;
}
+ @Nullable
+ @Override
+ public String getEmergencyInstaller() {
+ return mEmergencyInstaller;
+ }
+
@Override
public int getRoundIconResourceId() {
return roundIconRes;
@@ -2336,6 +2345,12 @@
}
@Override
+ public PackageImpl setEmergencyInstaller(@Nullable String emergencyInstaller) {
+ this.mEmergencyInstaller = emergencyInstaller;
+ return this;
+ }
+
+ @Override
public PackageImpl setRoundIconResourceId(int value) {
roundIconRes = value;
return this;
@@ -3105,6 +3120,7 @@
dest.writeString(this.mBaseApkPath);
dest.writeString(this.restrictedAccountType);
dest.writeString(this.requiredAccountType);
+ dest.writeString(this.mEmergencyInstaller);
sForInternedString.parcel(this.overlayTarget, dest, flags);
dest.writeString(this.overlayTargetOverlayableName);
dest.writeString(this.overlayCategory);
@@ -3255,6 +3271,7 @@
this.mBaseApkPath = in.readString();
this.restrictedAccountType = in.readString();
this.requiredAccountType = in.readString();
+ this.mEmergencyInstaller = in.readString();
this.overlayTarget = sForInternedString.unparcel(in);
this.overlayTargetOverlayableName = in.readString();
this.overlayCategory = in.readString();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 7ef0b48..66cfb69 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -75,6 +75,11 @@
ParsedPackage setUpdatableSystem(boolean value);
+ /**
+ * Sets a system app that is allowed to update another system app
+ */
+ ParsedPackage setEmergencyInstaller(String emergencyInstaller);
+
ParsedPackage markNotActivitiesAsNotExportedIfSingleUser();
ParsedPackage setOdm(boolean odm);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 6c09b7c..ef106e0 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -347,6 +347,11 @@
ParsingPackage setUpdatableSystem(boolean value);
+ /**
+ * Sets a system app that is allowed to update another system app
+ */
+ ParsingPackage setEmergencyInstaller(String emergencyInstaller);
+
ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index f483597..e0fdbc6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -952,6 +952,8 @@
final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
"updatableSystem", true);
+ final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,
+ "emergencyInstaller");
pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
R.styleable.AndroidManifest_installLocation, sa))
@@ -959,7 +961,8 @@
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
- .setUpdatableSystem(updatableSystem);
+ .setUpdatableSystem(updatableSystem)
+ .setEmergencyInstaller(emergencyInstaller);
boolean foundApp = false;
final int depth = parser.getDepth();
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index adb0c69..096f246 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -273,6 +273,13 @@
String getRestrictedAccountType();
/**
+ * @see R.styleable#AndroidManifestApplication_emergencyInstaller
+ * @hide
+ */
+ @Nullable
+ String getEmergencyInstaller();
+
+ /**
* @see ApplicationInfo#roundIconRes
* @see R.styleable#AndroidManifestApplication_roundIcon
*/
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e325a5..58166bf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -667,8 +667,9 @@
}
}
-static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn, jlong bounding_capabilities) {
for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if ((1LL << i) & bounding_capabilities) continue;
if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
@@ -680,6 +681,27 @@
}
}
+static bool MatchGid(JNIEnv* env, jintArray gids, jint gid, jint gid_to_find) {
+ if (gid == gid_to_find) return true;
+
+ if (gids == nullptr) return false;
+
+ jsize gids_num = env->GetArrayLength(gids);
+ ScopedIntArrayRO native_gid_proxy(env, gids);
+
+ if (native_gid_proxy.get() == nullptr) {
+ RuntimeAbort(env, __LINE__, "Bad gids array");
+ }
+
+ for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
+ if (native_gid_proxy[gids_index] == gid_to_find) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
@@ -1875,9 +1897,9 @@
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jlong permitted_capabilities,
- jlong effective_capabilities, jint mount_external,
- jstring managed_se_info, jstring managed_nice_name,
- bool is_system_server, bool is_child_zygote,
+ jlong effective_capabilities, jlong bounding_capabilities,
+ jint mount_external, jstring managed_se_info,
+ jstring managed_nice_name, bool is_system_server, bool is_child_zygote,
jstring managed_instruction_set, jstring managed_app_data_dir,
bool is_top_app, jobjectArray pkg_data_info_list,
jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
@@ -1891,6 +1913,9 @@
auto instruction_set = extract_fn(managed_instruction_set);
auto app_data_dir = extract_fn(managed_app_data_dir);
+ // Permit bounding capabilities
+ permitted_capabilities |= bounding_capabilities;
+
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
EnableKeepCapabilities(fail_fn);
@@ -1898,7 +1923,7 @@
SetInheritable(permitted_capabilities, fail_fn);
- DropCapabilitiesBoundingSet(fail_fn);
+ DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);
bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
android::NativeBridgeAvailable() &&
@@ -2165,6 +2190,23 @@
return capdata[0].effective | (static_cast<uint64_t>(capdata[1].effective) << 32);
}
+static jlong CalculateBoundingCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids) {
+ jlong capabilities = 0;
+
+ /*
+ * Grant CAP_SYS_NICE to CapInh/CapPrm/CapBnd for processes that can spawn
+ * VMs. This enables processes to execve on binaries with elevated
+ * capabilities if its file capability bits are set. This does not grant
+ * capability to the parent process(that spawns the VM) as the effective
+ * bits are not set.
+ */
+ if (MatchGid(env, gids, gid, AID_VIRTUALMACHINE)) {
+ capabilities |= (1LL << CAP_SYS_NICE);
+ }
+
+ return capabilities;
+}
+
static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids,
bool is_child_zygote) {
jlong capabilities = 0;
@@ -2198,26 +2240,7 @@
* Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
*/
- bool gid_wakelock_found = false;
- if (gid == AID_WAKELOCK) {
- gid_wakelock_found = true;
- } else if (gids != nullptr) {
- jsize gids_num = env->GetArrayLength(gids);
- ScopedIntArrayRO native_gid_proxy(env, gids);
-
- if (native_gid_proxy.get() == nullptr) {
- RuntimeAbort(env, __LINE__, "Bad gids array");
- }
-
- for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
- if (native_gid_proxy[gids_index] == AID_WAKELOCK) {
- gid_wakelock_found = true;
- break;
- }
- }
- }
-
- if (gid_wakelock_found) {
+ if (MatchGid(env, gids, gid, AID_WAKELOCK)) {
capabilities |= (1LL << CAP_BLOCK_SUSPEND);
}
@@ -2494,6 +2517,7 @@
jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
zygote::ZygoteFailure(env, "zygote", nice_name,
@@ -2532,10 +2556,11 @@
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
return pid;
}
@@ -2568,7 +2593,7 @@
// System server prcoess does not need data isolation so no need to
// know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
- effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+ effective_capabilities, 0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
/* pkg_data_info_list */ nullptr,
/* allowlisted_data_info_list */ nullptr, false, false, false);
@@ -2725,12 +2750,14 @@
jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
- mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
- instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
- allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
- mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+ bounding_capabilities, mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+ mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+ mount_sysprop_overrides == JNI_TRUE);
}
/**
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 381580b..c65794e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -19,6 +19,7 @@
per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
+per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
# Biometrics
jaggies@google.com
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index ddedca2..50ff7c9 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -79,16 +79,16 @@
android:layout_height="1dp"
style="@style/AutofillHalfSheetDivider" />
- <com.android.internal.widget.ButtonBarLayout
+ <com.android.server.autofill.ui.BottomSheetButtonBarLayout
+ android:id="@+id/autofill_save_button_bar"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:layout_gravity="end"
android:clipToPadding="false"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
android:orientation="horizontal"
- android:gravity="center_vertical"
android:layout_marginStart="@dimen/autofill_save_outer_margin"
android:layout_marginEnd="@dimen/autofill_save_outer_margin"
>
@@ -100,12 +100,15 @@
android:paddingEnd="12dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
android:minWidth="0dp"
style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
<Space
+ android:id="@+id/autofill_button_bar_spacer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
@@ -116,12 +119,14 @@
android:id="@+id/autofill_save_yes"
android:layout_width="wrap_content"
android:layout_height="40dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
android:minWidth="0dp"
style="@style/AutofillHalfSheetTonalButton"
android:text="@string/autofill_save_yes">
</Button>
- </com.android.internal.widget.ButtonBarLayout>
+ </com.android.server.autofill.ui.BottomSheetButtonBarLayout>
</com.android.server.autofill.ui.BottomSheetLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 65c4d9f..d910940 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1608,6 +1608,10 @@
This is a private attribute, used without android: namespace. -->
<attr name="updatableSystem" format="boolean" />
+ <!-- Allows each installer in the system image to designate another app in the system image to
+ update the installer. -->
+ <attr name="emergencyInstaller" format="string" />
+
<!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags
together. -->
<attr name="foregroundServiceType">
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 96c4bf4..0acccee 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -887,6 +887,8 @@
<dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
<dimen name="autofill_save_button_bar_padding">16dp</dimen>
<dimen name="autofill_dialog_corner_radius">24dp</dimen>
+ <dimen name="autofill_button_bar_spacer_width">12dp</dimen>
+ <dimen name="autofill_button_bar_spacer_height">4dp</dimen>
<!-- How much extra space should be left around the autofill dialog -->
<dimen name="autofill_dialog_offset">72dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4cf37df..58427b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3728,6 +3728,7 @@
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_yes" />
+ <java-symbol type="id" name="autofill_button_bar_spacer" />
<java-symbol type="id" name="autofill_service_icon" />
<java-symbol type="id" name="autofill_dialog_picker"/>
<java-symbol type="id" name="autofill_dialog_header"/>
@@ -3774,6 +3775,8 @@
<java-symbol type="dimen" name="autofill_dialog_max_width" />
<java-symbol type="dimen" name="autofill_dialog_offset"/>
<java-symbol type="dimen" name="autofill_save_outer_margin"/>
+ <java-symbol type="dimen" name="autofill_button_bar_spacer_width"/>
+ <java-symbol type="dimen" name="autofill_button_bar_spacer_height"/>
<java-symbol type="bool" name="autofill_dialog_horizontal_space_included"/>
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 6e1c580..0aefef2 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -26,10 +26,14 @@
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,7 +47,9 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupRestoreEventLoggerTest {
- private static final int DATA_TYPES_ALLOWED = 15;
+ private static final int DATA_TYPES_ALLOWED_AFTER_FLAG = 150;
+
+ private static final int DATA_TYPES_ALLOWED_BEFORE_FLAG = 15;
private static final String DATA_TYPE_1 = "data_type_1";
private static final String DATA_TYPE_2 = "data_type_2";
@@ -55,6 +61,9 @@
private BackupRestoreEventLogger mLogger;
private MessageDigest mHashDigest;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -83,10 +92,11 @@
}
@Test
- public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ public void testBackupLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
mLogger = new BackupRestoreEventLogger(BACKUP);
- for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
String dataType = DATA_TYPE_1 + i;
mLogger.logItemsBackedUp(dataType, /* count */ 5);
mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
@@ -103,10 +113,53 @@
}
@Test
- public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ public void testRestoreLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
mLogger = new BackupRestoreEventLogger(RESTORE);
- for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ mLogger.logItemsRestored(dataType, /* count */ 5);
+ mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
+ }
+
+ mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testBackupLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ mLogger.logItemsBackedUp(dataType, /* count */ 5);
+ mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logBackupMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
+ }
+
+ mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testRestoreLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
String dataType = DATA_TYPE_1 + i;
mLogger.logItemsRestored(dataType, /* count */ 5);
mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 36bb8e5..548b8ec 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -104,7 +104,9 @@
private void createComplexDatabase() {
mDatabase.beginTransaction();
try {
- mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+ // Column "l" is used to test the long variants. The underlying sqlite type is int,
+ // which is the same as a java long.
+ mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text, l int);");
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
@@ -115,7 +117,7 @@
* A three-value insert for the complex database.
*/
private String createComplexInsert() {
- return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+ return "INSERT INTO t1 (i, d, t, l) VALUES (?1, ?2, ?3, ?4)";
}
/**
@@ -209,22 +211,25 @@
mDatabase.beginTransaction();
try {
try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < 9; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
+ for (int row = 0; row < 9; row++) {
+ int vi = row * 3;
+ double vd = row * 2.5;
+ String vt = String.format("text%02dvalue", row);
+ long vl = Long.MAX_VALUE - row;
s.bindInt(1, vi);
s.bindDouble(2, vd);
s.bindText(3, vt);
+ s.bindLong(4, vl);
boolean r = s.step();
// No row is returned by this query.
assertFalse(r);
s.reset();
}
- // The last row has a null double and a null text.
+ // The last row has a null double, null text, and null long.
s.bindInt(1, 20);
s.bindNull(2);
s.bindNull(3);
+ s.bindNull(4);
assertFalse(s.step());
s.reset();
}
@@ -248,19 +253,31 @@
mDatabase.endTransaction();
}
- // Verify that the element created with i == 3 is correct.
+ // Verify that the element created with row == 3 is correct.
mDatabase.beginTransactionReadOnly();
try {
- final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
+ final String query = "SELECT i, d, t, l FROM t1 WHERE t = 'text03value'";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnCount());
+ assertEquals(4, s.getResultColumnCount());
int vi = s.getColumnInt(0);
double vd = s.getColumnDouble(1);
String vt = s.getColumnText(2);
- assertEquals(3 * 3, vi);
- assertEquals(2.5 * 3, vd, 0.1);
+ long vl = s.getColumnLong(3);
+ // The query extracted the third generated row.
+ final int row = 3;
+ assertEquals(3 * row, vi);
+ assertEquals(2.5 * row, vd, 0.1);
assertEquals("text03value", vt);
+ assertEquals(Long.MAX_VALUE - row, vl);
+
+ // Verify the column types. Remember that sqlite integers are the same as Java
+ // long, so the integer and long columns have type INTEGER.
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_FLOAT, s.getColumnType(1));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(2));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(3));
+
// No more rows.
assertFalse(s.step());
}
@@ -268,15 +285,24 @@
mDatabase.endTransaction();
}
+ // Verify that null columns are returned properly.
mDatabase.beginTransactionReadOnly();
try {
- final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
+ final String query = "SELECT i, d, t, l FROM t1 WHERE i == 20";
try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
assertTrue(s.step());
- assertEquals(3, s.getResultColumnCount());
+ assertEquals(4, s.getResultColumnCount());
assertEquals(20, s.getColumnInt(0));
assertEquals(0.0, s.getColumnDouble(1), 0.01);
assertEquals(null, s.getColumnText(2));
+ assertEquals(0, s.getColumnLong(3));
+
+ // Verify the column types.
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(1));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(2));
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(3));
+
// No more rows.
assertFalse(s.step());
}
@@ -495,6 +521,8 @@
// Fetch the entire reference array.
s.bindInt(1, 1);
assertTrue(s.step());
+ assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_BLOB, s.getColumnType(0));
+
byte[] a = s.getColumnBlob(0);
assertTrue(Arrays.equals(src, a));
s.reset();
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
index a525615..c18f35f 100644
--- a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -126,4 +126,4 @@
TextLine.recycle(tl)
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 27869bb..71980c1 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -21,7 +21,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -40,7 +40,7 @@
@JvmField
val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
@Test
fun calculateRunFlagTest() {
// Only one Bidi run
@@ -84,7 +84,7 @@
.isEqualTo(LEFT_EDGE)
}
- @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
@Test
fun resolveRunFlagForSubSequenceTest() {
val runStart = 5
@@ -221,4 +221,4 @@
MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
.isEqualTo(MIDDLE_OF_LINE)
}
-}
\ No newline at end of file
+}
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 4a9dd2f..3b1867c 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -21,12 +21,36 @@
Example usage:
- <enhanced-confirmation-trusted-installer
+ <enhanced-confirmation-trusted-package
package="com.example.app"
- signature="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+ sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-This indicates that "com.example.app" should be exempt from ECM, and that, if "com.example.app" is
-an installer, all packages installed via "com.example.app" will also be exempt from ECM.
+ ...
+
+ <enhanced-confirmation-trusted-installer
+ package="com.example.installer"
+ sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+ ...
+
+The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
+should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+
+The "enhanced-confirmation-trusted-installer" entry shown above indicates that
+"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
+packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
+For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
+restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+
+In either case:
+
+- The "package" XML attribute refers to the app's package name.
+- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
+
+For any entry to successfully apply to a package, both XML attributes must be present, and must
+match the package. That is, the package name must match the "package" attribute, and the app must be
+signed by the signing certificate identified by the "sha256-cert-digest" attribute..
-->
<config></config>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ae61a2d..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,7 +17,7 @@
package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -293,7 +293,7 @@
* {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
* {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
@@ -324,7 +324,7 @@
* {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
* {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
// These flags are always set on a new/reset paint, even if flags 0 is passed.
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 9707126..0e9f29d 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,7 +17,7 @@
package android.graphics.text;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -183,7 +183,7 @@
/**
* Value for justification mode indicating the text is justified by stretching letter spacing.
*/
- @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 621c453..0aa8959 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -919,6 +919,9 @@
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
+ } else if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
}
}
@@ -1403,6 +1406,13 @@
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
+
+ if (!mBubbleData.hasBubbles()) {
+ // if there are no bubbles, don't create the stack or layer views. they will be created
+ // later when the first bubble is added.
+ return;
+ }
+
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
@@ -1732,6 +1742,10 @@
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
mLayerView.removeBubble(removedBubble);
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b95d258..62f2726 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -273,6 +273,9 @@
if (endAction != null) {
endAction.run();
}
+ if (mBubbleData.getBubbles().isEmpty()) {
+ mBubbleController.onAllBubblesAnimatedOut();
+ }
};
if (mDragController != null && mDragController.isStuckToDismiss()) {
mAnimationHelper.animateDismiss(runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 52a06e0..9f73f1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1891,6 +1891,9 @@
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removeContentOverlay: %s, state=%s, surface=%s",
+ mTaskInfo, mPipTransitionState, surface);
if (mPipOverlay != null) {
if (mPipOverlay != surface) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 0000000..2ff4d90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
+natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index b3e8bd9..fa331af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -196,14 +196,18 @@
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
+ final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
final String handler = entry.getKey();
final int handlerId = entry.getValue();
- final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+
+ final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING);
os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
- os.end(token);
+ os.end(mappingEntryToken);
+
}
+ os.end(mappingsToken);
ctx.flush();
});
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 6ebfc63..ac75c07 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,9 +41,9 @@
#endif // __ANDROID__
}
-inline bool inter_character_justification() {
+inline bool letter_spacing_justification() {
#ifdef __ANDROID__
- return com_android_text_flags_inter_character_justification();
+ return com_android_text_flags_letter_spacing_justification();
#else
return true;
#endif // __ANDROID__
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5613369..d66d7f8 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,7 +72,7 @@
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
- const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
? paint->getRunFlag()
: minikin::RunFlag::NONE;
@@ -105,7 +105,7 @@
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
- const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
? paint->getRunFlag()
: minikin::RunFlag::NONE;
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 09f40e8..970bfda 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -195,7 +195,7 @@
* <li>SBAS: 120-151, 183-192</li>
* <li>GLONASS: One of: OSN, or FCN+100
* <ul>
- * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li>
+ * <li>1-25 as the orbital slot number (OSN) (preferred, if known)</li>
* <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
* i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
* </ul></li>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 69708ec..e9ba779 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5166,13 +5166,12 @@
* dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
* the request was successful but the dispatch of focus change was delayed due to a fade
* operation.
- * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
- * other active {@link AudioFocusInfo} are {@code null}.
* @hide
*/
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @FocusRequestResult
public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
@NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
@Nullable FadeManagerConfiguration transientFadeMgrConfig) {
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e8adfaf..48ca4bf 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -50,6 +50,14 @@
private static final String TAG = "AudioProductStrategy";
+ /**
+ * The audio flags that will affect product strategy selection.
+ */
+ private static final int AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION =
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED
+ | AudioAttributes.FLAG_SCO
+ | AudioAttributes.FLAG_BEACON;
+
private final AudioAttributesGroup[] mAudioAttributesGroups;
private final String mName;
/**
@@ -438,8 +446,8 @@
|| (attr.getSystemUsage() == refAttr.getSystemUsage()))
&& ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
|| (attr.getContentType() == refAttr.getContentType()))
- && ((refAttr.getAllFlags() == 0)
- || (attr.getAllFlags() != 0
+ && (((refAttr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) == 0)
+ || ((attr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) != 0
&& (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
&& ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
}
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 694756c..eb980a3 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -16,9 +16,11 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.media.tv.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -51,14 +53,14 @@
/**
* Request option: one-way
* <p> With this option, no response is expected after sending the request.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public static final int REQUEST_OPTION_ONEWAY = 2;
/**
* Request option: one-shot
* <p> With this option, only one response will be given per request.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public static final int REQUEST_OPTION_ONESHOT = 3;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 672f58b..aed3e60e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -556,85 +556,85 @@
/**
* Informs the application that the session has been tuned to the given channel.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_CHANNEL_URI
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TUNED = "tuned";
/**
* Sends the type and ID of a selected track. This is used to inform the application that a
* specific track is selected.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_TRACK_TYPE
* @see SESSION_DATA_KEY_TRACK_ID
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
/**
* Sends the list of all audio/video/subtitle tracks.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_TRACKS
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
/**
* Informs the application that the video is now available for watching.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
/**
* Informs the application that the video became unavailable for some reason.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
/**
* Notifies response for broadcast info.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
"broadcast_info_response";
/**
* Notifies response for advertisement.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_RESPONSE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
/**
* Notifies the advertisement buffer is consumed.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_BUFFER
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
/**
* Sends the TV message.
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see TvInputService.Session#notifyTvMessage(int, Bundle)
* @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
@@ -657,9 +657,9 @@
*
* <p> Type: android.net.Uri
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
/**
@@ -671,9 +671,9 @@
* <p> Type: Integer
*
* @see TvTrackInfo#getType()
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
/**
@@ -682,9 +682,9 @@
* <p> Type: String
*
* @see TvTrackInfo#getId()
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
/**
@@ -692,9 +692,9 @@
*
* <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TRACKS = "tracks";
/**
@@ -704,9 +704,9 @@
*
* <p> Type: Integer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
"video_unavailable_reason";
@@ -715,9 +715,9 @@
*
* <p> Type: android.media.tv.BroadcastInfoResponse
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
/**
@@ -725,9 +725,9 @@
*
* <p> Type: android.media.tv.AdResponse
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
/**
@@ -735,9 +735,9 @@
*
* <p> Type: android.media.tv.AdBuffer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
/**
@@ -747,9 +747,9 @@
*
* <p> Type: Integer
*
- * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
- * @hide
+ * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6b03041..6658918 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -768,12 +768,12 @@
/**
* Informs the application that the video freeze state has been updated.
*
- * When {@code true}, the video is frozen on the last frame but audio playback remains
+ * <p>When {@code true}, the video is frozen on the last frame but audio playback remains
* active.
*
* @param isFrozen Whether or not the video is frozen
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void notifyVideoFreezeUpdated(boolean isFrozen) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@@ -1262,7 +1262,7 @@
}
/**
- * Notifies data related to this session to corresponding linked
+ * Sends data related to this session to corresponding linked
* {@link android.media.tv.ad.TvAdService} object via TvAdView.
*
* <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
@@ -1272,21 +1272,21 @@
*
* @param type data type
* @param data the related data values
- * @hide
*/
- public void notifyTvInputSessionData(
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+ public void sendTvInputSessionData(
@NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+ if (DEBUG) Log.d(TAG, "sendTvInputSessionData");
if (mSessionCallback != null) {
mSessionCallback.onTvInputSessionData(type, data);
}
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTvInputSessionData", e);
+ Log.w(TAG, "error in sendTvInputSessionData", e);
}
}
});
@@ -1441,10 +1441,10 @@
*
* @param type the type of the data
* @param data a bundle contains the data received
- * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.ad.TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see android.media.tv.ad.TvAdView#setTvView(TvView)
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
public void onTvAdSessionData(
@NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ffc121e..e604cb7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -683,13 +683,15 @@
* Sets whether or not the video is frozen. While the video is frozen, audio playback will
* continue.
*
- * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+ * <p>This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
* received with the command to freeze the video.
*
- * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+ * <p>This will freeze the video to the last frame when the state is set to {@code true}.
+ *
+ * @see TvView.TvInputCallback#setVideoFrozen(boolean)
* @param isFrozen whether or not the video is frozen.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void setVideoFrozen(boolean isFrozen) {
if (mSession != null) {
mSession.setVideoFrozen(isFrozen);
@@ -1325,6 +1327,16 @@
public void onTvMessage(@NonNull String inputId,
@TvInputManager.TvMessageType int type, @NonNull Bundle data) {
}
+
+ /**
+ * This is called when the video freeze status is updated.
+ *
+ * @see #setVideoFrozen(boolean)
+ * @param inputId The ID of the TV input bound to this view.
+ * @param isFrozen Whether or not the video is currently frozen on the las
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onVideoFreezeUpdated(@NonNull String inputId, boolean isFrozen) {}
}
/**
@@ -1753,5 +1765,19 @@
mCallback.onTvMessage(mInputId, type, data);
}
}
+
+ @Override
+ public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoFreezeUpdated(isFrozen=" + isFrozen + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onVideoFreezeUpdated - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onVideoFreezeUpdated(mInputId, isFrozen);
+ }
+ }
}
}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index f373bed..59b10c6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -68,7 +68,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
@@ -77,7 +76,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
@@ -86,7 +84,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
@@ -95,7 +92,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
@@ -104,7 +100,6 @@
* <p>Type: String
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String APP_LINK_KEY_BACK_URI = "back_uri";
@@ -112,7 +107,6 @@
* Broadcast intent action to send app command to TV app.
*
* @see #sendAppLinkCommand(String, Bundle)
- * @hide
*/
public static final String ACTION_APP_LINK_COMMAND =
"android.media.tv.ad.action.APP_LINK_COMMAND";
@@ -123,7 +117,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
@@ -134,7 +127,6 @@
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
* @see TvAdServiceInfo#getId()
- * @hide
*/
public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
@@ -144,7 +136,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
@@ -155,7 +146,6 @@
*
* @see #sendAppLinkCommand(String, Bundle)
* @see #ACTION_APP_LINK_COMMAND
- * @hide
*/
public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
@@ -171,36 +161,32 @@
/**
* Sends an advertisement request to be processed by the related TV input.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_REQUEST
- * @hide
*/
public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
/**
* Notifies the advertisement buffer is ready.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_AD_BUFFER
- * @hide
*/
public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
/**
* Sends request for broadcast info.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
- * @hide
*/
public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
/**
* Removes request for broadcast info.
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
* @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
- * @hide
*/
public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
"remove_broadcast_info_request";
@@ -220,8 +206,7 @@
*
* <p> Type: android.media.tv.AdRequest
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
@@ -230,8 +215,7 @@
*
* <p> Type: android.media.tv.AdBuffer
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
@@ -240,8 +224,7 @@
*
* <p> Type: android.media.tv.BroadcastInfoRequest
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
@@ -250,8 +233,7 @@
*
* <p> Type: Integer
*
- * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
- * @hide
+ * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
*/
public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
@@ -494,8 +476,17 @@
*
* @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
* in {@link TvAdServiceInfo#getId()}.
- * @param command The command to be sent.
- * @hide
+ * @param command The command to be sent. The command is a bundle with the following keys:
+ * <ul>
+ * <li>{@link #APP_LINK_KEY_PACKAGE_NAME}: The package name of the app to be
+ * launched.
+ * <li>{@link #APP_LINK_KEY_CLASS_NAME}: The class name of the app to be
+ * launched.
+ * <li>{@link #APP_LINK_KEY_COMMAND_TYPE}: The command type.
+ * <li>{@link #APP_LINK_KEY_SERVICE_ID}: The ID of the TV AD service.
+ * <li>{@link #APP_LINK_KEY_BACK_URI}: The URI to be used to return to the
+ * previous app.
+ * </ul>
*/
public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
try {
@@ -510,7 +501,6 @@
*
* @param callback A callback used to monitor status of the TV AD services.
* @param executor A {@link Executor} that the status change will be delivered to.
- * @hide
*/
public void registerCallback(
@CallbackExecutor @NonNull Executor executor,
@@ -526,7 +516,6 @@
* Unregisters the existing {@link TvAdServiceCallback}.
*
* @param callback The existing callback to remove.
- * @hide
*/
public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 953b5cf..6c8a8fd 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -136,7 +136,6 @@
* Called when app link command is received.
*
* @see TvAdManager#sendAppLinkCommand(String, Bundle)
- * @hide
*/
public void onAppLinkCommand(@NonNull Bundle command) {
}
@@ -199,7 +198,6 @@
*
* @param enable {@code true} if you want to enable the media view. {@code false}
* otherwise.
- * @hide
*/
@CallSuper
public void setMediaViewEnabled(final boolean enable) {
@@ -225,7 +223,6 @@
* Returns {@code true} if media view is enabled, {@code false} otherwise.
*
* @see #setMediaViewEnabled(boolean)
- * @hide
*/
public boolean isMediaViewEnabled() {
return mMediaViewEnabled;
@@ -253,21 +250,18 @@
/**
* Starts TvAdService session.
- * @hide
*/
public void onStartAdService() {
}
/**
* Stops TvAdService session.
- * @hide
*/
public void onStopAdService() {
}
/**
* Resets TvAdService session.
- * @hide
*/
public void onResetAdService() {
}
@@ -618,9 +612,8 @@
*
* @param type the type of the data
* @param data a bundle contains the data received
- * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+ * @see android.media.tv.TvInputService.Session#sendTvInputSessionData(String, Bundle)
* @see android.media.tv.ad.TvAdView#setTvView(TvView)
- * @hide
*/
public void onTvInputSessionData(
@NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
@@ -636,7 +629,6 @@
*
* @param width The width of the media view, in pixels.
* @param height The height of the media view, in pixels.
- * @hide
*/
public void onMediaViewSizeChanged(@Px int width, @Px int height) {
}
@@ -646,7 +638,6 @@
* implementation can override this method and return its own view.
*
* @return a view attached to the media window. {@code null} if no media view is created.
- * @hide
*/
@Nullable
public View onCreateMediaView() {
@@ -654,27 +645,26 @@
}
/**
- * Notifies data related to this session to corresponding linked
+ * Sends data related to this session to corresponding linked
* {@link android.media.tv.TvInputService} object via TvView.
*
* @param type data type
* @param data the related data values
* @see TvAdView#setTvView(TvView)
- * @hide
*/
- public void notifyTvAdSessionData(
+ public void sendTvAdSessionData(
@NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+ if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
if (mSessionCallback != null) {
mSessionCallback.onTvAdSessionData(type, data);
}
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTvAdSessionData", e);
+ Log.w(TAG, "error in sendTvAdSessionData", e);
}
}
});
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index be88506..ee01468 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -160,7 +160,6 @@
* @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
* to unlink the TvView.
* @return {@code true} if it's linked successfully; {@code false} otherwise.
- * @hide
*/
public boolean setTvView(@Nullable TvView tvView) {
if (tvView == null) {
@@ -259,7 +258,6 @@
* Resets this TvAdView to release its resources.
*
* <p>It can be reused by call {@link #prepareAdService(String, String)}.
- * @hide
*/
public void reset() {
if (DEBUG) Log.d(TAG, "reset()");
@@ -362,7 +360,6 @@
*
* @param event The input event.
* @return {@code true} if the event was handled by the view, {@code false} otherwise.
- * @hide
*/
public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
if (mOnUnhandledInputEventListener != null) {
@@ -381,7 +378,6 @@
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to be
* handled by the next receiver, return {@code false}.
- * @hide
*/
public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
return false;
@@ -392,7 +388,6 @@
* by the TV AD service.
*
* @param listener The callback to be invoked when the unhandled input event is received.
- * @hide
*/
public void setOnUnhandledInputEventListener(
@NonNull @CallbackExecutor Executor executor,
@@ -407,7 +402,6 @@
*
* @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
* @see #clearOnUnhandledInputEventListener()
- * @hide
*/
@Nullable
public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
@@ -416,7 +410,6 @@
/**
* Clears the {@link OnUnhandledInputEventListener}.
- * @hide
*/
public void clearOnUnhandledInputEventListener() {
mOnUnhandledInputEventListener = null;
@@ -453,7 +446,6 @@
/**
* Starts the AD service.
- * @hide
*/
public void startAdService() {
if (DEBUG) {
@@ -466,7 +458,6 @@
/**
* Stops the AD service.
- * @hide
*/
public void stopAdService() {
if (DEBUG) {
@@ -481,7 +472,6 @@
* Resets the AD service.
*
* <p>This releases the resources of the corresponding {@link TvAdService.Session}.
- * @hide
*/
public void resetAdService() {
if (DEBUG) {
@@ -622,7 +612,6 @@
/**
* Interface definition for a callback to be invoked when the unhandled input event is received.
- * @hide
*/
public interface OnUnhandledInputEventListener {
/**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 498eec6..7cf32ec 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -2634,8 +2634,8 @@
/**
* This is called when
- * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
- * called.
+ * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+ * is called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
* @param signingId the ID to identify the request.
@@ -2644,7 +2644,6 @@
* @param host The host of the SSL CLient Authentication Server
* @param port The port of the SSL Client Authentication Server
* @param data the original bytes to be signed.
- * @hide
*/
public void onRequestSigning(
Session session, String signingId, String algorithm, String host,
@@ -2657,7 +2656,6 @@
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
- * @hide
*/
public void onRequestCertificate(Session session, String host, int port) {
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 6b0620c..eba26d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -742,8 +742,8 @@
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
* @param cert the SSL certificate received.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
}
@@ -896,13 +896,13 @@
}
/**
- * Called when video becomes frozen or unfrozen. Audio playback will continue while
- * video will be frozen to the last frame if {@code true}.
+ * Called when video becomes frozen or unfrozen. Audio playback will continue while video
+ * will be frozen to the last frame if {@code true}.
+ *
* @param isFrozen Whether or not the video is frozen.
- * @hide
*/
- public void onVideoFreezeUpdated(boolean isFrozen) {
- }
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onVideoFreezeUpdated(boolean isFrozen) {}
/**
* Called when content is allowed.
@@ -1666,9 +1666,9 @@
* @see #onSigningResult(String, byte[])
* @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
* @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
- * @hide
*/
@CallSuper
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@NonNull String host, int port, @NonNull byte[] data) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1695,8 +1695,9 @@
*
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
- * @hide
*/
+ @CallSuper
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void requestCertificate(@NonNull String host, int port) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 584ea84..29a3b98 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -723,12 +723,12 @@
}
/**
- * Alerts the TV Interactive app that the video freeze state has been updated.
- * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true},
+ * the video is frozen on the last frame while audio playback continues.
*
* @param isFrozen Whether the video is frozen.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void notifyVideoFreezeUpdated(boolean isFrozen) {
if (DEBUG) {
Log.d(TAG, "notifyVideoFreezeUpdated");
@@ -760,12 +760,12 @@
}
/**
- * Send the requested SSL certificate to the TV Interactive App
+ * Sends the requested SSL certificate to the TV Interactive App
* @param host the host name of the SSL authentication server.
* @param port the port of the SSL authentication server. E.g., 443
* @param cert the SSL certificate requested
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
if (DEBUG) {
Log.d(TAG, "sendCertificate");
@@ -1390,6 +1390,37 @@
}
/**
+ * This is called when
+ * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+ * is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param signingId the ID to identify the request.
+ * @param algorithm the standard name of the signature algorithm requested, such as
+ * MD5withRSA, SHA256withDSA, etc.
+ * @param host The hostname of the SSL authentication server.
+ * @param port The port of the SSL authentication server.
+ * @param data the original bytes to be signed.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
+ @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) {
+ }
+
+ /**
+ * This is called when
+ * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param host The hostname of the SSL authentication server.
+ * @param port The port of the SSL authentication server.
+ */
+ @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+ public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host,
+ int port) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String,
* TvRecordingInfo)} is called.
*
@@ -1957,5 +1988,34 @@
mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
}
}
+
+ @Override
+ public void onRequestSigning(
+ Session session, String id, String algorithm, String host, int port, byte[] data) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSigning");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSigning - session not created");
+ return;
+ }
+ if (mCallback != null && Flags.tiafVApis()) {
+ mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data);
+ }
+ }
+
+ @Override
+ public void onRequestCertificate(Session session, String host, int port) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCertificate");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestCertificate - session not created");
+ return;
+ }
+ if (mCallback != null && Flags.tiafVApis()) {
+ mCallback.onRequestCertificate(mIAppServiceId, host, port);
+ }
+ }
}
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 28cf250..845a8f9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -232,13 +232,13 @@
method public final void sendResponseApdu(byte[]);
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 7cd2533..89b0322 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,11 +244,11 @@
public static final String KEY_DATA = "data";
/**
- * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+ * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
* polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+ public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
/**
* POLLING_LOOP_TYPE_A is the value associated with the key
@@ -299,33 +299,33 @@
public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
/**
- * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+ public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
- * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+ * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+ public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
- * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+ * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
* the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
* when the frame type isn't recognized.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+ public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
/**
* @hide
*/
- public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+ public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
"android.nfc.cardemulation.POLLING_FRAMES";
/**
@@ -405,7 +405,7 @@
break;
case MSG_POLLING_LOOP:
ArrayList<Bundle> frames =
- msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+ msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
Bundle.class);
processPollingFrames(frames);
break;
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index fdda9ea..910ff96 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index c7c2fda..4bf0e99 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,7 +17,7 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="@dimen/dropdown_touch_target_min_width"
+ android:minHeight="@dimen/dropdown_touch_target_min_height"
android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 53852cb..b47a4dc 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -26,6 +26,6 @@
<dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
- <integer name="autofill_max_visible_datasets">3</integer>
- <dimen name="dropdown_touch_target_min_width">48dp</dimen>
+ <integer name="autofill_max_visible_datasets">5</integer>
+ <dimen name="dropdown_touch_target_min_height">48dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index f496c1f..2628f09 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -318,7 +318,7 @@
maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
- (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+ (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount - 1)
var i = 0
var datasetAdded = false
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index 3ed0c9c..b2812d3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -31,7 +31,7 @@
@Composable
fun AccountRow(
primaryText: String,
- secondaryText: String,
+ secondaryText: String? = null,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
@@ -42,14 +42,16 @@
maxLines = 1,
style = MaterialTheme.typography.title2
)
- Text(
- text = secondaryText,
- modifier = Modifier.padding(top = 7.dp),
- color = Color(0xFFCAC5BC),
- overflow = TextOverflow.Ellipsis,
- maxLines = 2,
- style = MaterialTheme.typography.body1,
- )
+ if (secondaryText != null) {
+ Text(
+ text = secondaryText,
+ modifier = Modifier.padding(top = 7.dp),
+ color = Color(0xFFCAC5BC),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
+ style = MaterialTheme.typography.body1,
+ )
+ }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index 2878b0b..92d8a39 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -18,44 +18,119 @@
package com.android.credentialmanager.ui.screens.single.passkey
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.Column
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
- name: String,
- email: String,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+ screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
+ viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
+ navController: NavHostController = rememberNavController(),
+) {
+ viewModel.initialize(credentialSelectorUiState.entry)
+
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ when (val state = uiState) {
+ UiState.CredentialScreen -> {
+ SinglePasskeyScreen(
+ credentialSelectorUiState.entry,
+ screenIcon,
+ columnState,
+ modifier,
+ viewModel
+ )
+ }
+
+ is UiState.CredentialSelected -> {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
+ }
+
+ SideEffect {
+ state.intentSenderRequest?.let {
+ launcher.launch(it)
+ }
+ }
+ }
+
+ UiState.Cancel -> {
+ // TODO(b/322797032) add valid navigation path here for going back
+ navController.popBackStack()
+ }
+ }
+}
+
+@OptIn(ExperimentalHorologistApi::class)
+@Composable
+fun SinglePasskeyScreen(
+ entry: CredentialEntryInfo,
+ screenIcon: Drawable?,
+ columnState: ScalingLazyColumnState,
+ modifier: Modifier = Modifier,
+ viewModel: SinglePasskeyScreenViewModel,
) {
SingleAccountScreen(
headerContent = {
SignInHeader(
- icon = null,
+ icon = screenIcon,
title = stringResource(R.string.use_passkey_title),
)
},
accountContent = {
- AccountRow(
- primaryText = name,
- secondaryText = email,
- modifier = Modifier.padding(top = 10.dp),
- )
+ if (entry.displayName != null) {
+ AccountRow(
+ primaryText = checkNotNull(entry.displayName),
+ secondaryText = entry.userName,
+ modifier = Modifier.padding(top = 10.dp),
+ )
+ } else {
+ AccountRow(
+ primaryText = entry.userName,
+ modifier = Modifier.padding(top = 10.dp),
+ )
+ }
},
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
+ Column {
+ ContinueChip(viewModel::onContinueClick)
+ SignInOptionsChip(viewModel::onSignInOptionsClick)
+ DismissChip(viewModel::onDismissClick)
+ }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
new file mode 100644
index 0000000..35c39f6
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.credentialmanager.ui.screens.single.passkey
+
+import android.content.Intent
+import android.credentials.selection.UserSelectionDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.ktx.getIntentSenderRequest
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import dagger.hilt.android.lifecycle.HiltViewModel
+import com.android.credentialmanager.ui.screens.single.UiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+@HiltViewModel
+class SinglePasskeyScreenViewModel @Inject constructor(
+ private val credentialManagerClient: CredentialManagerClient,
+) : ViewModel() {
+
+ private val _uiState =
+ MutableStateFlow<UiState>(UiState.CredentialScreen)
+ val uiState: StateFlow<UiState> = _uiState
+
+ private lateinit var requestGet: Request.Get
+ private lateinit var entryInfo: CredentialEntryInfo
+
+
+ @MainThread
+ fun initialize(entry: CredentialEntryInfo) {
+ this.entryInfo = entry
+ }
+
+ fun onDismissClick() {
+ _uiState.value = UiState.Cancel
+ }
+
+ fun onContinueClick() {
+ _uiState.value = UiState.CredentialSelected(
+ intentSenderRequest = entryInfo.getIntentSenderRequest()
+ )
+ }
+
+ fun onSignInOptionsClick() {
+ }
+
+ fun onPasskeyInfoRetrieved(
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ ) {
+ val userSelectionDialogResult = UserSelectionDialogResult(
+ requestGet.token,
+ entryInfo.providerId,
+ entryInfo.entryKey,
+ entryInfo.entrySubkey,
+ if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+ )
+ credentialManagerClient.sendResult(userSelectionDialogResult)
+ }
+}
+
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index b2b7b61..5f5f1d5 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -6,7 +6,6 @@
edgarwang@google.com
evanlaird@google.com
juliacr@google.com
-yantingyang@google.com
ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 4b5a9bc..b55dd1b 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -24,4 +24,8 @@
</style>
<style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
+ <style name="Theme.SpaLib.BottomSheetDialog" parent="Theme.SpaLib">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index b34c310..e704505 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -18,7 +18,6 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
@@ -95,56 +94,60 @@
if (options.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded,
- modifier = Modifier
- .fillMaxWidth()
- .width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+ modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
onDismissRequest = { expanded = false },
) {
options.forEachIndexed { index, option ->
- TextButton(
- modifier = Modifier
- .fillMaxHeight()
- .fillMaxWidth(),
- onClick = {
- if (selectedOptionsState.contains(index)) {
- if (index == allIndex)
- selectedOptionsState.clear()
- else {
- selectedOptionsState.remove(
- index
- )
- if (selectedOptionsState.contains(allIndex))
- selectedOptionsState.remove(
- allIndex
- )
- }
- } else {
- selectedOptionsState.add(
- index
- )
- }
- onSelectedOptionStateChange()
- }) {
- Row(
- modifier = Modifier
- .fillMaxHeight()
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.Start,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Checkbox(
- checked = selectedOptionsState.contains(index),
- onCheckedChange = null,
- )
- Text(text = option)
- }
- }
+ CheckboxItem(
+ selectedOptionsState,
+ index,
+ allIndex,
+ onSelectedOptionStateChange,
+ option,
+ )
}
}
}
}
}
+@Composable
+private fun CheckboxItem(
+ selectedOptionsState: SnapshotStateList<Int>,
+ index: Int,
+ allIndex: Int,
+ onSelectedOptionStateChange: () -> Unit,
+ option: String
+) {
+ TextButton(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = {
+ if (selectedOptionsState.contains(index)) {
+ if (index == allIndex) {
+ selectedOptionsState.clear()
+ } else {
+ selectedOptionsState.remove(index)
+ selectedOptionsState.remove(allIndex)
+ }
+ } else {
+ selectedOptionsState.add(index)
+ }
+ onSelectedOptionStateChange()
+ }) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ checked = selectedOptionsState.contains(index),
+ onCheckedChange = null,
+ )
+ Text(text = option)
+ }
+ }
+}
+
@Preview
@Composable
private fun ActionButtonsPreview() {
@@ -158,4 +161,4 @@
enabled = true,
onSelectedOptionStateChange = {})
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 0a98791..988afd7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,10 +17,11 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
-import android.content.pm.FeatureFlags
-import android.content.pm.FeatureFlagsImpl
import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
+import android.content.pm.Flags
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
@@ -85,13 +86,7 @@
loadInstantApps: Boolean,
matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> = coroutineScope {
- val hiddenSystemModulesDeferred = async {
- packageManager.getInstalledModules(0)
- .filter { it.isHidden }
- .map { it.packageName }
- .filterNotNull()
- .toSet()
- }
+ val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
val hideWhenDisabledPackagesDeferred = async {
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
}
@@ -205,6 +200,15 @@
private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
+ private fun PackageManager.getHiddenSystemModules(): Set<String> {
+ val moduleInfos = getInstalledModules(0).filter { it.isHidden }
+ val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
+ if (Flags.provideInfoOfApkInApex()) {
+ hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames }
+ }
+ return hiddenApps
+ }
+
companion object {
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index a28ebc6..458fcc9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -35,5 +35,6 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
+ "flag-junit",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index f292231..efd53a4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -21,6 +21,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.FakeFeatureFlagsImpl
import android.content.pm.Flags
+import android.content.pm.ModuleInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -28,6 +29,7 @@
import android.content.pm.UserInfo
import android.content.res.Resources
import android.os.UserManager
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
@@ -35,6 +37,7 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -50,6 +53,9 @@
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+
private val resources = mock<Resources> {
on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
}
@@ -273,6 +279,38 @@
}
@Test
+ fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+ packageManager.stub {
+ on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+ }
+ mockInstalledApplications(
+ listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+ ADMIN_USER_ID
+ )
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ }
+
+ @Test
+ fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
+ mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+ packageManager.stub {
+ on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+ }
+ mockInstalledApplications(
+ listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+ ADMIN_USER_ID
+ )
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
+ }
+
+ @Test
fun showSystemPredicate_showSystem() = runTest {
val app = SYSTEM_APP
@@ -402,6 +440,20 @@
isArchived = true
}
+ val HIDDEN_APEX_APP = ApplicationInfo().apply {
+ packageName = "hidden.apex.package"
+ }
+
+ val HIDDEN_MODULE_APP = ApplicationInfo().apply {
+ packageName = "hidden.module.package"
+ }
+
+ val HIDDEN_MODULE = ModuleInfo().apply {
+ packageName = "hidden.module.package"
+ apkInApexPackageNames = listOf("hidden.apex.package")
+ isHidden = true
+ }
+
fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
activityInfo = ActivityInfo().apply {
this.packageName = packageName
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 2ded3c6..89d6ac3 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -38,6 +38,17 @@
android:src="@drawable/add_a_photo_circled"
android:layout_gravity="bottom|right"/>
</FrameLayout>
+ <TextView
+ android:id="@+id/edit_user_info_message"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_marginTop="24dp"
+ android:textAppearance="@style/android:TextAppearance.Material.Body1"
+ android:text="@string/edit_user_info_message"
+ />
<EditText
android:id="@+id/user_name"
@@ -46,6 +57,8 @@
android:layout_gravity="center"
android:minWidth="200dp"
android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_marginTop="24dp"
android:minHeight="@dimen/min_tap_target_size"
android:ellipsize="end"
android:singleLine="true"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e64212..1092a16 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1484,6 +1484,9 @@
<!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
<string name="user_nickname">Nickname</string>
+ <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
+ <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+
<!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
<string name="user_add_user">Add user</string>
<!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 07de7fd..c482995 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -164,12 +165,16 @@
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
- // Check if the package is contained in an APEX. There is no public API to properly
- // check whether a given APK package comes from an APEX registered as module.
- // Therefore we conservatively assume that any package scanned from an /apex path is
- // a system package.
- return pkg.applicationInfo.sourceDir.startsWith(
- Environment.getApexDirectory().getAbsolutePath());
+ if (Flags.provideInfoOfApkInApex()) {
+ return pkg.getApexPackageName() != null;
+ } else {
+ // Check if the package is contained in an APEX. There is no public API to properly
+ // check whether a given APK package comes from an APEX registered as module.
+ // Therefore we conservatively assume that any package scanned from an /apex path is
+ // a system package.
+ return pkg.applicationInfo.sourceDir.startsWith(
+ Environment.getApexDirectory().getAbsolutePath());
+ }
} catch (PackageManager.NameNotFoundException e) {
return false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 8e1067f..e3012cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.ModuleInfo;
@@ -226,6 +227,11 @@
final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
for (ModuleInfo info : moduleInfos) {
mSystemModules.put(info.getPackageName(), info.isHidden());
+ if (Flags.provideInfoOfApkInApex()) {
+ for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+ mSystemModules.put(apkInApexPackageName, info.isHidden());
+ }
+ }
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 994c1ee..e32d880 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.applications;
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -24,12 +26,16 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.Utils;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -39,6 +45,8 @@
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
import java.io.File;
import java.lang.reflect.Field;
@@ -60,6 +68,10 @@
private ApplicationInfo mAppInfo;
private ApplicationsState.AppEntry mAppEntry;
private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+ private ShadowPackageManager mShadowPackageManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -70,6 +82,7 @@
mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
doReturn(mIcon).when(mIcon).mutate();
+ mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
}
@After
@@ -151,6 +164,32 @@
assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
}
+ @Test
+ public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
+ mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = APP_PACKAGE_NAME;
+ packageInfo.setApexPackageName("com.test.apex.package");
+ mShadowPackageManager.installPackage(packageInfo);
+
+ assertThat(
+ AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+ }
+
+ @Test
+ public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
+ mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = APP_PACKAGE_NAME;
+ packageInfo.applicationInfo = applicationInfo;
+ mShadowPackageManager.installPackage(packageInfo);
+
+ assertThat(
+ AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+ }
+
private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 34d8148..827d8fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
import static android.os.UserHandle.MU_ENABLED;
import static android.os.UserHandle.USER_SYSTEM;
@@ -58,6 +59,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
@@ -70,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -89,6 +92,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@@ -137,6 +141,9 @@
@Mock
private IPackageManager mPackageManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Implements(value = IconDrawableFactory.class)
public static class ShadowIconDrawableFactory {
@@ -169,7 +176,9 @@
public List<ModuleInfo> getInstalledModules(int flags) {
if (mInstalledModules.isEmpty()) {
for (String moduleName : mModuleNames) {
- mInstalledModules.add(createModuleInfo(moduleName));
+ mInstalledModules.add(
+ createModuleInfo(moduleName,
+ TextUtils.concat(moduleName, ".apex").toString()));
}
}
return mInstalledModules;
@@ -188,10 +197,11 @@
return resolveInfos;
}
- private ModuleInfo createModuleInfo(String packageName) {
+ private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
final ModuleInfo info = new ModuleInfo();
info.setName(packageName);
info.setPackageName(packageName);
+ info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
// will treat any app with package name that contains "hidden" as hidden module
info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
return info;
@@ -822,4 +832,32 @@
assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
.isEqualTo(PKG_1);
}
+
+ @Test
+ public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
+ mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationsState.sInstance = null;
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+ String normalModulePackage = "test.module.1";
+ String hiddenModulePackage = "test.hidden.module.2";
+ String hiddenApexPackage = "test.hidden.module.2.apex";
+
+ assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+ assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+ assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
+ }
+
+ @Test
+ public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
+ mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+ ApplicationsState.sInstance = null;
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+ String normalModulePackage = "test.module.1";
+ String hiddenModulePackage = "test.hidden.module.2";
+ String hiddenApexPackage = "test.hidden.module.2.apex";
+
+ assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+ assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+ assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6a8da10..f387021 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -19,11 +19,6 @@
import android.content.Context
import android.view.ViewGroup
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
@@ -45,7 +40,6 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
@SysUISingleton
class NotificationSection
@@ -53,74 +47,54 @@
constructor(
@Application private val context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
- private val controller: NotificationStackScrollLayoutController,
- private val sceneContainerFlags: SceneContainerFlags,
- private val sharedNotificationContainer: SharedNotificationContainer,
- private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
- private val stackScrollLayout: NotificationStackScrollLayout,
- private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
- private val ambientState: AmbientState,
- private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
+ controller: NotificationStackScrollLayoutController,
+ sceneContainerFlags: SceneContainerFlags,
+ sharedNotificationContainer: SharedNotificationContainer,
+ sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+ stackScrollLayout: NotificationStackScrollLayout,
+ notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ ambientState: AmbientState,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
@Main private val mainDispatcher: CoroutineDispatcher,
) {
- @Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
+
+ init {
+ if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
// This scene container section moves the NSSL to the SharedNotificationContainer.
// This also requires that SharedNotificationContainer gets moved to the
// SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
// but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
// container by the NotificationStackScrollLayoutSection.
- return
- }
-
- var isBound by remember { mutableStateOf(false) }
-
- DisposableEffect(Unit) {
- val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
-
// Ensure stackScrollLayout is a child of sharedNotificationContainer.
+
if (stackScrollLayout.parent != sharedNotificationContainer) {
(stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
}
- disposableHandles.add(
- SharedNotificationContainerBinder.bind(
- sharedNotificationContainer,
- sharedNotificationContainerViewModel,
- sceneContainerFlags,
- controller,
- notificationStackSizeCalculator,
- mainDispatcher,
- )
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel,
+ sceneContainerFlags,
+ controller,
+ notificationStackSizeCalculator,
+ mainDispatcher,
)
if (sceneContainerFlags.flexiNotifsEnabled()) {
- disposableHandles.add(
- NotificationStackAppearanceViewBinder.bind(
- context,
- sharedNotificationContainer,
- notificationStackAppearanceViewModel,
- ambientState,
- controller,
- )
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
)
}
-
- isBound = true
-
- onDispose {
- disposableHandles.forEach { it.dispose() }
- disposableHandles.clear()
- isBound = false
- }
}
+ }
- if (!isBound) {
- return
- }
-
+ @Composable
+ fun SceneScope.Notifications(modifier: Modifier = Modifier) {
NotificationStack(
viewModel = viewModel,
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 3fb8254..d70f82f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -61,11 +61,11 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
-import com.android.compose.ui.util.lerp
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index b26194f..37d763b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -30,7 +30,8 @@
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.lerp
-import com.android.compose.ui.util.lerp
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
/**
* A [State] whose [value] is animated.
@@ -282,7 +283,7 @@
} else {
val progress =
if (canOverflow) transition.progress
- else transition.progress.coerceIn(0f, 1f)
+ else transition.progress.fastCoerceIn(0f, 1f)
lerp(fromValue, toValue, progress)
}
} else fromValue ?: toValue
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e40f6b6..828e34d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -39,6 +39,8 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
@@ -362,7 +364,7 @@
isSpecified = { true },
::lerp,
)
- .coerceIn(0f, 1f)
+ .fastCoerceIn(0f, 1f)
}
@OptIn(ExperimentalComposeUiApi::class)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 529fc03..3ff869b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -283,21 +283,28 @@
}
onDragStart(drag.position, overSlop, pressed.size)
- onDrag(drag, overSlop)
- val successful =
- when (orientation) {
- Orientation.Horizontal ->
- horizontalDrag(drag.id) {
- onDrag(it, it.positionChange().x)
- it.consume()
- }
- Orientation.Vertical ->
- verticalDrag(drag.id) {
- onDrag(it, it.positionChange().y)
- it.consume()
- }
- }
+ val successful: Boolean
+ try {
+ onDrag(drag, overSlop)
+
+ successful =
+ when (orientation) {
+ Orientation.Horizontal ->
+ horizontalDrag(drag.id) {
+ onDrag(it, it.positionChange().x)
+ it.consume()
+ }
+ Orientation.Vertical ->
+ verticalDrag(drag.id) {
+ onDrag(it, it.positionChange().y)
+ it.consume()
+ }
+ }
+ } catch (t: Throwable) {
+ onDragCancel()
+ throw t
+ }
if (successful) {
onDragEnd()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 23af5ac..b3d2bc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -701,27 +701,19 @@
gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
- val swipeTransition = gestureHandler.swipeTransition
- val progress = swipeTransition.progress
val threshold = layoutImpl.transitionInterceptionThreshold
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- // The transition is always between 0 and 1. If it is close to either of these
- // intervals, we want to go directly to the TransitionState.Idle.
- // The progress value can go beyond this range in the case of overscroll.
- val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
- if (shouldSnapToIdle) {
- swipeTransition.cancelOffsetAnimation()
- layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
+ val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
+ if (hasSnappedToIdle) {
+ // If the current swipe transition is closed to 0f or 1f, then we want to
+ // interrupt the transition (snapping it to Idle) and scroll the list.
+ return@PriorityNestedScrollConnection false
}
- // Start only if we cannot consume this event
- val canStart = !shouldSnapToIdle
- if (canStart) {
- isIntercepting = true
- }
-
- canStart
+ // If the current swipe transition is *not* closed to 0f or 1f, then we want the
+ // scroll events to intercept the current transition to continue the scene
+ // transition.
+ isIntercepting = true
+ true
},
canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
val behavior: NestedScrollBehavior =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index aee6f9e..a8da551 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -24,6 +24,11 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -100,8 +105,9 @@
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
- return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+ return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
}
/**
@@ -120,9 +126,12 @@
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions = SceneTransitions.Empty,
+ stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
- return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
- .apply { update(currentScene, onChangeScene, transitions) }
+ return remember {
+ HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+ }
+ .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
}
@Stable
@@ -183,8 +192,10 @@
}
}
-internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
- SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ protected var stateLinks: List<StateLink>,
+) : SceneTransitionLayoutState {
override var transitionState: TransitionState by
mutableStateOf(TransitionState.Idle(initialScene))
protected set
@@ -195,6 +206,8 @@
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
/**
* Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
*
@@ -223,19 +236,94 @@
transitions
.transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
.transformationSpec()
-
+ cancelActiveTransitionLinks()
+ setupTransitionLinks(transition)
transitionState = transition
}
+ private fun cancelActiveTransitionLinks() {
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+ }
+ activeTransitionLinks.clear()
+ }
+
+ private fun setupTransitionLinks(transitionState: TransitionState) {
+ if (transitionState !is TransitionState.Transition) return
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
+
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
+
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transitionState,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ )
+
+ stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey)
+ activeTransitionLinks[stateLink] = linkedTransition
+ }
+ }
+
/**
* Notify that [transition] was finished and that we should settle to [idleScene]. This will do
* nothing if [transition] was interrupted since it was started.
*/
internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+ resolveActiveTransitionLinks(idleScene)
if (transitionState == transition) {
transitionState = TransitionState.Idle(idleScene)
}
}
+
+ private fun resolveActiveTransitionLinks(idleScene: SceneKey) {
+ val previousTransition = this.transitionState as? TransitionState.Transition ?: return
+ for ((link, linkedTransition) in activeTransitionLinks) {
+ if (previousTransition.fromScene == idleScene) {
+ // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ } else if (previousTransition.toScene == idleScene) {
+ // The transition ended by arriving at the toScene, move link to Idle(toScene).
+ link.target.finishTransition(linkedTransition, linkedTransition.toScene)
+ } else {
+ // The transition was interrupted by something else, we reset to initial state.
+ link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+ }
+ }
+ activeTransitionLinks.clear()
+ }
+
+ /**
+ * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
+ * to the closest scene.
+ *
+ * @return true if snapped to the closest scene.
+ */
+ internal fun snapToIdleIfClose(threshold: Float): Boolean {
+ val transition = currentTransition ?: return false
+ val progress = transition.progress
+ fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
+
+ return when {
+ isProgressCloseTo(0f) -> {
+ finishTransition(transition, transition.fromScene)
+ true
+ }
+ isProgressCloseTo(1f) -> {
+ finishTransition(transition, transition.toScene)
+ true
+ }
+ else -> false
+ }
+ }
}
/**
@@ -246,7 +334,8 @@
initialScene: SceneKey,
override var transitions: SceneTransitions,
private var changeScene: (SceneKey) -> Unit,
-) : BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
@@ -256,10 +345,12 @@
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions,
+ stateLinks: List<StateLink>,
) {
SideEffect {
this.changeScene = onChangeScene
this.transitions = transitions
+ this.stateLinks = stateLinks
targetSceneChannel.trySend(currentScene)
}
@@ -283,7 +374,8 @@
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions,
-) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+ stateLinks: List<StateLink> = emptyList(),
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
override fun setTargetScene(
targetScene: SceneKey,
coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 04254fb..603f7ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -16,6 +16,9 @@
package com.android.compose.animation.scene.transformation
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import androidx.compose.ui.util.fastCoerceIn
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
@@ -106,10 +109,10 @@
fun progress(transitionProgress: Float): Float {
return when {
start.isSpecified() && end.isSpecified() ->
- ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+ ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
!start.isSpecified() && !end.isSpecified() -> transitionProgress
- end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
- else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+ end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+ else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
new file mode 100644
index 0000000..33b57b2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A linked transition which is driven by a [originalTransition]. */
+internal class LinkedTransition(
+ private val originalTransition: TransitionState.Transition,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+) : TransitionState.Transition(fromScene, toScene) {
+
+ override val currentScene: SceneKey
+ get() {
+ return when (originalTransition.currentScene) {
+ originalTransition.fromScene -> fromScene
+ originalTransition.toScene -> toScene
+ else -> error("Original currentScene is neither FromScene nor ToScene")
+ }
+ }
+
+ override val isInitiatedByUserInput: Boolean
+ get() = originalTransition.isInitiatedByUserInput
+
+ override val isUserInputOngoing: Boolean
+ get() = originalTransition.isUserInputOngoing
+
+ override val progress: Float
+ get() = originalTransition.progress
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
new file mode 100644
index 0000000..6c29946
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
+class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
+
+ internal val target = target as BaseSceneTransitionLayoutState
+
+ /**
+ * Links two transitions (source and target) together.
+ *
+ * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
+ * `null`, `SceneA` means that any transition at the source will trigger a transition in the
+ * target to `SceneA` from any current scene.
+ */
+ class TransitionLink(
+ val sourceFrom: SceneKey?,
+ val sourceTo: SceneKey?,
+ val targetFrom: SceneKey?,
+ val targetTo: SceneKey,
+ val targetTransitionKey: TransitionKey? = null,
+ ) {
+ init {
+ if (
+ (sourceFrom != null && sourceFrom == sourceTo) ||
+ (targetFrom != null && targetFrom == targetTo)
+ )
+ error("From and To can't be the same")
+ }
+
+ internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
+ return (sourceFrom == null || sourceFrom == transition.fromScene) &&
+ (sourceTo == null || sourceTo == transition.toScene)
+ }
+
+ internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentScene) &&
+ targetTo != targetCurrentScene
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index 13747b7..e78ab29 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -20,24 +20,8 @@
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.Scale
-import kotlin.math.roundToInt
-import kotlin.math.roundToLong
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Float, stop: Float, fraction: Float): Float {
- return (1 - fraction) * start + fraction * stop
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Int, stop: Int, fraction: Float): Int {
- return start + ((stop - start) * fraction.toDouble()).roundToInt()
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Long, stop: Long, fraction: Float): Long {
- return start + ((stop - start) * fraction.toDouble()).roundToLong()
-}
/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a116501..e8854cf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -30,8 +30,8 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.ui.util.lerp
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Rule
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
new file mode 100644
index 0000000..cd99d05
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MultiPointerDraggableTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun cancellingPointerCallsOnDragStopped() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+
+ var enabled by mutableStateOf(false)
+ var started = false
+ var dragged = false
+ var stopped = false
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { enabled },
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ -> started = true },
+ onDragDelta = { _ -> dragged = true },
+ onDragStopped = { stopped = true },
+ )
+ )
+ }
+
+ fun startDraggingDown() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun releaseFinger() {
+ rule.onRoot().performTouchInput { up() }
+ }
+
+ // Swiping down does nothing because enabled is false.
+ startDraggingDown()
+ assertThat(started).isFalse()
+ assertThat(dragged).isFalse()
+ assertThat(stopped).isFalse()
+ releaseFinger()
+
+ // Enable the draggable and swipe down. This should both call onDragStarted() and
+ // onDragDelta().
+ enabled = true
+ rule.waitForIdle()
+ startDraggingDown()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isFalse()
+
+ // Disable the pointer input. This should call onDragStopped() even if didn't release the
+ // finger yet.
+ enabled = false
+ rule.waitForIdle()
+ assertThat(started).isTrue()
+ assertThat(dragged).isTrue()
+ assertThat(stopped).isTrue()
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c61917d..f81a7f2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,10 +18,14 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.animation.scene.transition.link.StateLink
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -31,93 +35,241 @@
class SceneTransitionLayoutStateTest {
@get:Rule val rule = createComposeRule()
+ class TestableTransition(
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ ) : TransitionState.Transition(fromScene, toScene) {
+ override var currentScene: SceneKey = fromScene
+ override var progress: Float = 0.0f
+ override var isInitiatedByUserInput: Boolean = false
+ override var isUserInputOngoing: Boolean = false
+ }
+
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
- .isFalse()
+ assertThat(state.isTransitioning(from = SceneA)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse()
}
@Test
fun isTransitioningTo_transition() {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
- state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
- transitionKey = null
- )
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null)
assertThat(state.isTransitioning()).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
- assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
- assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ assertThat(state.isTransitioning(from = SceneA)).isTrue()
+ assertThat(state.isTransitioning(from = SceneB)).isFalse()
+ assertThat(state.isTransitioning(to = SceneB)).isTrue()
+ assertThat(state.isTransitioning(to = SceneA)).isFalse()
+ assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
}
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
assertThat(transition).isNotNull()
assertThat(state.transitionState).isEqualTo(transition)
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@Test
fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
// Progress is 0f, so we don't animate at all and directly snap back to A.
- assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
}
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ val state = MutableSceneTransitionLayoutState(SceneA)
lateinit var transition: TransitionState.Transition
val job =
launch(start = CoroutineStart.UNDISPATCHED) {
- transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+ transition = state.setTargetScene(SceneB, coroutineScope = this)!!
}
assertThat(state.transitionState).isEqualTo(transition)
// Cancelling the scope/job still sets the state to Idle(targetScene).
job.cancel()
testScheduler.advanceUntilIdle()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ }
+
+ private fun setupLinkedStates(
+ parentInitialScene: SceneKey = SceneC,
+ childInitialScene: SceneKey = SceneA,
+ sourceFrom: SceneKey? = SceneA,
+ sourceTo: SceneKey? = SceneB,
+ targetFrom: SceneKey? = SceneC,
+ targetTo: SceneKey = SceneD
+ ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+ val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo))
+ )
+ )
+ val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
+ return Pair(
+ parentState as BaseSceneTransitionLayoutState,
+ childState as BaseSceneTransitionLayoutState
+ )
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_transitiveLink() {
+ val parentParentState =
+ MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+ val parentLink =
+ listOf(
+ StateLink(
+ parentParentState,
+ listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC))
+ )
+ )
+ val parentState =
+ MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
+ as BaseSceneTransitionLayoutState
+ val link =
+ listOf(
+ StateLink(
+ parentState,
+ listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD))
+ )
+ )
+ val childState =
+ MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
+ as BaseSceneTransitionLayoutState
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+ assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_linkProgressIsEqual() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
+
+ childTransition.progress = .5f
+ assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
+ }
+
+ @Test
+ fun linkedTransition_reverseTransitionIsNotLinked() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneB, SceneA)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ childState.startTransition(childTransition, null)
+
+ childState.finishTransition(childTransition, SceneD)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
+ val (parentState, childState) = setupLinkedStates()
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+ val parentTransition = TestableTransition(SceneC, SceneA)
+ childState.startTransition(childTransition, null)
+ parentState.startTransition(parentTransition, null)
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(parentTransition)
}
@Test
@@ -125,11 +277,11 @@
val transitionkey = TransitionKey(debugName = "foo")
val state =
MutableSceneTransitionLayoutState(
- TestScenes.SceneA,
+ SceneA,
transitions =
transitions {
- from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
- from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+ from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+ from(SceneA, to = SceneB, key = transitionkey) {
fade(TestElements.Foo)
fade(TestElements.Bar)
}
@@ -138,19 +290,19 @@
as MutableSceneTransitionLayoutStateImpl
// Default transition from A to B.
- assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+ assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(1)
// Go back to A.
- state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+ state.setTargetScene(SceneA, coroutineScope = this)
testScheduler.advanceUntilIdle()
assertThat(state.currentTransition).isNull()
- assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
// Specific transition from A to B.
assertThat(
state.setTargetScene(
- TestScenes.SceneB,
+ SceneB,
coroutineScope = this,
transitionKey = transitionkey,
)
@@ -158,4 +310,83 @@
.isNotNull()
assertThat(state.transformationSpec.transformations).hasSize(2)
}
+
+ @Test
+ fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the initial scene if it is close to 0.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ }
+
+ @Test
+ fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ state.startTransition(
+ transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+ assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+ assertThat(state.isTransitioning()).isTrue()
+
+ // Go to the final scene if it is close to 1.
+ assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+ val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneB)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
+
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+ childState.finishTransition(childTransition, SceneA)
+ assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+ assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+ }
+
+ @Test
+ fun linkedTransition_fuzzyLinksAreNotMatched() {
+ val (parentState, childState) =
+ setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
+ val childTransition = TestableTransition(SceneA, SceneB)
+
+ childState.startTransition(childTransition, null)
+ assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+ assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 001e3a5..54c7a08 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -49,7 +49,7 @@
* existing lockscreen clock.
*/
class DefaultClockController(
- ctx: Context,
+ private val ctx: Context,
private val layoutInflater: LayoutInflater,
private val resources: Resources,
private val settings: ClockSettings?,
@@ -121,7 +121,11 @@
protected var targetRegion: Rect? = null
override val config = ClockFaceConfig()
- override val layout = DefaultClockFaceLayout(view)
+ override val layout =
+ DefaultClockFaceLayout(view).apply {
+ views[0].id =
+ resources.getIdentifier("lockscreen_clock_view", "id", ctx.packageName)
+ }
override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
internal set
@@ -188,7 +192,11 @@
seedColor: Int?,
messageBuffer: MessageBuffer?,
) : DefaultClockFaceController(view, seedColor, messageBuffer) {
- override val layout = DefaultClockFaceLayout(view)
+ override val layout =
+ DefaultClockFaceLayout(view).apply {
+ views[0].id =
+ resources.getIdentifier("lockscreen_clock_view_large", "id", ctx.packageName)
+ }
override val config =
ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 754d5dc..2a87452 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -27,7 +27,11 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
-/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+/**
+ * Defines interface for classes that can provide access to data from [Settings.Secure].
+ * This repository doesn't guarantee to provide value across different users. For that
+ * see: [UserAwareSecureSettingsRepository]
+ */
interface SecureSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1519021..c6327ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,11 +24,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -90,6 +92,8 @@
whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
.thenReturn(mock(ImageView::class.java))
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
+
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
@@ -111,6 +115,7 @@
postureController,
fakeFeatureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5f932f4..e8a43ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -51,6 +52,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -202,6 +204,7 @@
whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
+ val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -232,6 +235,7 @@
postureController,
featureFlags,
mSelectedUserInteractor,
+ keyguardKeyboardInteractor,
)
kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6cc680b..2de013b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,7 +59,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRootViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+ }
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val keyguardInteractor = kosmos.keyguardInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index b60f483..63fb67d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -50,7 +50,8 @@
@Test
fun mapsDisabledDataToInactiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val actualActivationState = tileState.activationState
@@ -59,7 +60,8 @@
@Test
fun mapsEnabledDataToActiveState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val actualActivationState = tileState.activationState
assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
@@ -67,7 +69,8 @@
@Test
fun mapsEnabledDataToOnIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
@@ -77,7 +80,8 @@
@Test
fun mapsDisabledDataToOffIconState() {
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
@@ -86,11 +90,32 @@
}
@Test
- fun supportsOnlyClickAction() {
+ fun mapsUnavailableDataToOffIconState() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+ val actualIcon = tileState.icon()
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ }
+
+ @Test
+ fun supportClickActionWhenAvailable() {
val dontCare = true
- val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(dontCare))
val supportedActions = tileState.supportedActions
assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
}
+
+ @Test
+ fun doesNotSupportClickActionWhenUnavailable() {
+ val tileState: QSTileState =
+ mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+ val supportedActions = tileState.supportedActions
+ assertThat(supportedActions).isEmpty()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index a9e39354..c5a8c70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -70,8 +70,7 @@
}
@Test
- fun dataMatchesController() = runTest {
- controller.setFlashlight(false)
+ fun isEnabledDataMatchesControllerWhenAvailable() = runTest {
val flowValues: List<FlashlightTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
@@ -81,8 +80,35 @@
controller.setFlashlight(false)
runCurrent()
- assertThat(flowValues.size).isEqualTo(3)
- assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup(), 2 from this test
+ assertThat(
+ flowValues.filterIsInstance<FlashlightTileModel.FlashlightAvailable>().map {
+ it.isEnabled
+ }
+ )
+ .containsExactly(false, false, true, false)
+ .inOrder()
+ }
+
+ /**
+ * Simulates the scenario of changes in flashlight tile availability when camera is initially
+ * closed, then opened, and closed again.
+ */
+ @Test
+ fun availabilityDataMatchesControllerAvailability() = runTest {
+ val flowValues: List<FlashlightTileModel> by
+ collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(false)
+ runCurrent()
+ controller.onFlashlightAvailabilityChanged(true)
+ runCurrent()
+
+ assertThat(flowValues.size).isEqualTo(4) // 2 from setup + 2 from this test
+ assertThat(flowValues.map { it is FlashlightTileModel.FlashlightAvailable })
+ .containsExactly(true, true, false, true)
+ .inOrder()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index 28d43b3..1f19c98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -29,7 +29,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
@@ -51,7 +53,7 @@
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = false
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
@@ -61,8 +63,17 @@
assumeFalse(ActivityManager.isUserAMonkey())
val stateBeforeClick = true
- underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+ underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
verify(controller).setFlashlight(!stateBeforeClick)
}
+
+ @Test
+ fun handleClickWhenUnavailable() = runTest {
+ assumeFalse(ActivityManager.isUserAMonkey())
+
+ underTest.handleInput(click(FlashlightTileModel.FlashlightTemporarilyUnavailable))
+
+ verify(controller, never()).setFlashlight(anyBoolean())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 189ba7b..9d3f0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,6 +265,7 @@
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+ centralSurfaces = mock(),
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 12dbf11..34c5173 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.startable
+import android.app.StatusBarManager
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
import android.view.Display
@@ -48,6 +49,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -65,6 +67,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
@@ -78,6 +81,7 @@
class SceneContainerStartableTest : SysuiTestCase() {
@Mock private lateinit var windowController: NotificationShadeWindowController
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -115,6 +119,7 @@
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+ centralSurfaces = centralSurfaces,
)
}
@@ -763,6 +768,227 @@
verify(windowController, times(2)).setNotificationShadeFocusable(false)
}
+ @Test
+ fun hydrateInteractionState_whileLocked() =
+ testScope.runTest {
+ val transitionStateFlow =
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ )
+ underTest.start()
+ runCurrent()
+ verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ false,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ true,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ false,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces)
+ .setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ true,
+ )
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ @Test
+ fun hydrateInteractionState_whileUnlocked() =
+ testScope.runTest {
+ val transitionStateFlow =
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = SceneKey.Gone,
+ )
+ underTest.start()
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Bouncer,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Shade,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.Lockscreen,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+
+ clearInvocations(centralSurfaces)
+ emulateSceneTransition(
+ transitionStateFlow = transitionStateFlow,
+ toScene = SceneKey.QuickSettings,
+ verifyBeforeTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyDuringTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ verifyAfterTransition = {
+ verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+ },
+ )
+ }
+
+ private fun TestScope.emulateSceneTransition(
+ transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+ toScene: SceneKey,
+ verifyBeforeTransition: (() -> Unit)? = null,
+ verifyDuringTransition: (() -> Unit)? = null,
+ verifyAfterTransition: (() -> Unit)? = null,
+ ) {
+ val fromScene = sceneInteractor.desiredScene.value.key
+ sceneInteractor.changeScene(SceneModel(toScene), "reason")
+ runCurrent()
+ verifyBeforeTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Transition(
+ fromScene = fromScene,
+ toScene = toScene,
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ verifyDuringTransition?.invoke()
+
+ transitionStateFlow.value =
+ ObservableTransitionState.Idle(
+ scene = toScene,
+ )
+ runCurrent()
+ verifyAfterTransition?.invoke()
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
new file mode 100644
index 0000000..d3c6598
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.content.applicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.recents.utilities.Utilities
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeBackActionInteractorImplTest : SysuiTestCase() {
+ val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ val testScope = kosmos.testScope
+ val sceneInteractor = kosmos.sceneInteractor
+ val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ val underTest = kosmos.shadeBackActionInteractor
+
+ @Before
+ fun ignoreSplitShade() {
+ Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
+ }
+
+ @Test
+ fun animateCollapseQs_notOnQs() =
+ testScope.runTest {
+ setScene(SceneKey.Shade)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_entered() =
+ testScope.runTest {
+ enterDevice()
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun animateCollapseQs_fullyCollapse_locked() =
+ testScope.runTest {
+ deviceEntryRepository.setUnlocked(false)
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(true)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseQs_notFullyCollapse() =
+ testScope.runTest {
+ setScene(SceneKey.QuickSettings)
+ underTest.animateCollapseQs(false)
+ runCurrent()
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ }
+
+ private fun enterDevice() {
+ deviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ setScene(SceneKey.Gone)
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 4436be7..fd7a7f3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -116,6 +116,8 @@
/** Custom constraints to apply to Lockscreen ConstraintLayout. */
fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+ fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
}
/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
@@ -131,6 +133,10 @@
}
return constraints
}
+
+ override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
+ return constraints
+ }
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
new file mode 100644
index 0000000..ad22894
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="oval">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
new file mode 100644
index 0000000..8c2b036
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <stroke android:width="3dp"
+ android:color="@color/bouncer_password_focus_color" />
+ <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/>
+ </shape>
+ </item>
+ <item>
+ <inset android:insetLeft="-4dp"
+ android:insetRight="-4dp"
+ android:insetTop="-4dp">
+ <shape android:shape="rectangle">
+ <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+ </shape>
+ </inset>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
similarity index 100%
rename from packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
rename to packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 2fc1d2e..909d4fc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,7 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
- android:gravity="center_horizontal"
+ android:gravity="center"
android:singleLine="true"
android:textStyle="normal"
android:inputType="textPassword"
@@ -68,14 +68,14 @@
<ImageView android:id="@+id/switch_ime_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="12dp"
android:src="@drawable/ic_lockscreen_ime"
android:contentDescription="@string/accessibility_ime_switch_button"
android:clickable="true"
- android:padding="8dip"
+ android:layout_marginRight="8dp"
+ android:padding="12dip"
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="end|center_vertical"
- android:background="?android:attr/selectableItemBackground"
+ android:background="@drawable/bouncer_input_method_background"
android:visibility="gone"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index ddad1e3..e853f02 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -28,8 +28,14 @@
<!-- Width for the keyguard pin input field -->
<dimen name="keyguard_pin_field_width">292dp</dimen>
- <!-- Width for the keyguard pin input field -->
- <dimen name="keyguard_pin_field_height">48dp</dimen>
+ <!-- height for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_height">56dp</dimen>
+
+ <!-- height for the keyguard password input field -->
+ <dimen name="keyguard_password_field_height">56dp</dimen>
+
+ <!-- width for the keyguard password input field -->
+ <dimen name="keyguard_password_field_width">276dp</dimen>
<!-- Height of the sliding KeyguardSecurityContainer
(includes 2x keyguard_security_view_top_margin) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4789a22..c43e394 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,7 @@
</style>
<style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
+ <item name="android:background">@drawable/bouncer_pin_view_focused_background</item>
<item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 51012a4..cc31754 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,7 +174,7 @@
<dimen name="status_bar_clock_size">14sp</dimen>
<!-- The starting padding for the clock in the status bar. -->
- <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+ <dimen name="status_bar_clock_starting_padding">4dp</dimen>
<!-- The end padding for the clock in the status bar. -->
<dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -395,7 +395,7 @@
<dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
<!-- the padding on the start of the statusbar -->
- <dimen name="status_bar_padding_start">8dp</dimen>
+ <dimen name="status_bar_padding_start">4dp</dimen>
<!-- the padding on the end of the statusbar -->
<dimen name="status_bar_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f0cbe7a..1b71256 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3337,6 +3337,6 @@
<string name="keyboard_backlight_value">Level %1$d of %2$d</string>
<!-- Label for home control panel [CHAR LIMIT=30] -->
<string name="home_controls_dream_label">Home Controls</string>
- <!-- Description for home control panel [CHAR LIMIT=50] -->
+ <!-- Description for home control panel [CHAR LIMIT=67] -->
<string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index efd8f7f..1a10c7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,6 +30,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.Flags;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.ui.BouncerMessageView;
@@ -212,6 +213,7 @@
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
private final KeyboardRepository mKeyboardRepository;
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -226,7 +228,8 @@
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
UiEventLogger uiEventLogger,
- KeyboardRepository keyboardRepository) {
+ KeyboardRepository keyboardRepository,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -244,6 +247,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
mKeyboardRepository = keyboardRepository;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -265,7 +269,8 @@
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyguardKeyboardInteractor);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 3d8aaaf..7473e0c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -17,8 +17,10 @@
package com.android.keyguard;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.Editable;
import android.text.InputType;
@@ -27,6 +29,7 @@
import android.text.method.TextKeyListener;
import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
@@ -39,6 +42,8 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -52,6 +57,7 @@
public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
+ private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -60,6 +66,8 @@
private final DelayableExecutor mMainExecutor;
private final KeyguardViewController mKeyguardViewController;
private final boolean mShowImeAtScreenOn;
+ private Drawable mDefaultPasswordFieldBackground;
+ private Drawable mFocusedPasswordFieldBackground;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
private boolean mPaused;
@@ -121,7 +129,8 @@
KeyguardViewController keyguardViewController,
DevicePostureController postureController,
FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
@@ -130,11 +139,15 @@
mPostureController = postureController;
mMainExecutor = mainExecutor;
mKeyguardViewController = keyguardViewController;
+ mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
view.setIsLockScreenLandscapeEnabled();
}
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+ mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
+ mFocusedPasswordFieldBackground = getResources().getDrawable(
+ R.drawable.bouncer_password_view_background);
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
}
@@ -175,6 +188,27 @@
// If there's more than one IME, enable the IME switcher button
updateSwitchImeButton();
+
+ if (Flags.pinInputFieldStyledFocusState()) {
+ collectFlow(mPasswordEntry,
+ mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
+ this::setPasswordFieldFocusBackground);
+
+ ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+ layoutParams.height = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_height);
+ layoutParams.width = (int) getResources()
+ .getDimension(R.dimen.keyguard_password_field_width);
+ }
+
+ }
+
+ private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
+ if (isAnyKeyboardConnected) {
+ mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
+ } else {
+ mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ef65144..9ebae90 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -109,8 +109,12 @@
animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
- PropertyAnimator.setProperty(
- mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 1");
+ } else {
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+ }
} else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
mView.setVisibility(View.VISIBLE);
mKeyguardViewVisibilityAnimating = true;
@@ -179,9 +183,13 @@
mView.setVisibility(View.VISIBLE);
}
} else {
- log("Direct set Visibility to GONE");
- mView.setVisibility(View.GONE);
- mView.setAlpha(1f);
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ log("Using LockscreenToGoneTransition 2");
+ } else {
+ log("Direct set Visibility to GONE");
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
}
mLastOccludedState = isOccluded;
diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
new file mode 100644
index 0000000..c39d3e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) {
+ val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 6076f32..5bd7e54 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -29,7 +29,7 @@
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@
}
override fun onBackProgressed(backEvent: BackEvent) {
- if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
- shadeViewController.onBackProgressed(backEvent.progress)
+ if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
+ shadeBackActionInteractor.onBackProgressed(backEvent.progress)
}
}
}
@@ -77,12 +77,12 @@
get() =
notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
- private lateinit var shadeViewController: ShadeViewController
+ private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
private lateinit var qsController: QuickSettingsController
- fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+ fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
this.qsController = qsController
- this.shadeViewController = svController
+ this.shadeBackActionInteractor = svController
}
override fun start() {
@@ -114,16 +114,16 @@
return true
}
if (qsController.expanded) {
- shadeViewController.animateCollapseQs(false)
+ shadeBackActionInteractor.animateCollapseQs(false)
return true
}
- if (shadeViewController.closeUserSwitcherIfOpen()) {
+ if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
return true
}
if (shouldBackBeHandled()) {
- if (shadeViewController.canBeCollapsed()) {
+ if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
- shadeViewController.onBackPressed()
+ shadeBackActionInteractor.onBackPressed()
shadeController.animateCollapseShade()
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 792a7ef..c8fb044 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -17,31 +17,20 @@
package com.android.systemui.biometrics.data.repository
import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-import android.os.Handler
import android.util.Size
import android.view.DisplayInfo
-import com.android.app.tracing.traceSection
-import com.android.internal.util.ArrayUtils
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.toDisplayRotation
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import java.util.concurrent.Executor
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
+import com.android.systemui.display.data.repository.DisplayRepository
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -66,52 +55,23 @@
val currentDisplaySize: StateFlow<Size>
}
-// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
-// instead.
@SysUISingleton
class DisplayStateRepositoryImpl
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Background backgroundScope: CoroutineScope,
@Application val context: Context,
- deviceStateManager: DeviceStateManager,
- displayManager: DisplayManager,
- @Main handler: Handler,
- @Background backgroundExecutor: Executor,
- @Background backgroundDispatcher: CoroutineDispatcher,
+ deviceStateRepository: DeviceStateRepository,
+ displayRepository: DisplayRepository,
) : DisplayStateRepository {
override val isReverseDefaultRotation =
context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
override val isInRearDisplayMode: StateFlow<Boolean> =
- conflatedCallbackFlow {
- val sendRearDisplayStateUpdate = { state: Boolean ->
- trySendWithFailureLogging(
- state,
- TAG,
- "Error sending rear display state update to $state"
- )
- }
-
- val callback =
- DeviceStateManager.DeviceStateCallback { state ->
- val isInRearDisplayMode =
- ArrayUtils.contains(
- context.resources.getIntArray(
- com.android.internal.R.array.config_rearDisplayDeviceStates
- ),
- state
- )
- sendRearDisplayStateUpdate(isInRearDisplayMode)
- }
-
- sendRearDisplayStateUpdate(false)
- deviceStateManager.registerCallback(backgroundExecutor, callback)
- awaitClose { deviceStateManager.unregisterCallback(callback) }
- }
- .flowOn(backgroundDispatcher)
+ deviceStateRepository.state
+ .map { it == REAR_DISPLAY }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
@@ -123,37 +83,10 @@
}
private val currentDisplayInfo: StateFlow<DisplayInfo> =
- conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayRemoved(displayId: Int) {}
-
- override fun onDisplayAdded(displayId: Int) {}
-
- override fun onDisplayChanged(displayId: Int) {
- traceSection(
- "DisplayStateRepository" +
- ".currentRotationDisplayListener#onDisplayChanged"
- ) {
- val displayInfo = getDisplayInfo()
- trySendWithFailureLogging(
- displayInfo,
- TAG,
- "Error sending displayInfo to $displayInfo"
- )
- }
- }
- }
- displayManager.registerDisplayListener(
- callback,
- handler,
- EVENT_FLAG_DISPLAY_CHANGED
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .flowOn(backgroundDispatcher)
+ displayRepository.displayChangeEvent
+ .map { getDisplayInfo() }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.Eagerly,
initialValue = getDisplayInfo(),
)
@@ -170,7 +103,7 @@
currentDisplayInfo
.map { rotationToDisplayRotation(it.rotation) }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
)
@@ -179,7 +112,7 @@
currentDisplayInfo
.map { Size(it.naturalWidth, it.naturalHeight) }
.stateIn(
- applicationScope,
+ backgroundScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
Size(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index ec29bd6..89cdd25 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -32,19 +32,13 @@
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
interface StickyKeysRepository {
@@ -53,14 +47,12 @@
}
@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
private val inputManager: InputManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val secureSettings: SecureSettings,
- userRepository: UserRepository,
+ secureSettingsRepository: UserAwareSecureSettingsRepository,
private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {
@@ -78,25 +70,10 @@
.flowOn(backgroundDispatcher)
override val settingEnabled: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { stickyKeySettingObserver(it.id) }
- .flowOn(backgroundDispatcher)
-
- private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
- return secureSettings
- .observerFlow(userId, SETTING_KEY)
- .onStart { emit(Unit) }
- .map { isSettingEnabledForCurrentUser(userId) }
- .distinctUntilChanged()
+ secureSettingsRepository
+ .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
.onEach { stickyKeysLogger.logNewSettingValue(it) }
- }
-
- private fun isSettingEnabledForCurrentUser(userId: Int) =
- secureSettings.getIntForUser(
- /* name= */ SETTING_KEY,
- /* default= */ 0,
- /* userHandle= */ userId
- ) != 0
+ .flowOn(backgroundDispatcher)
private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
val keys = linkedMapOf<ModifierKey, Locked>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4766a84..8f08efa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -689,17 +689,18 @@
} else {
resetStateLocked();
}
- }
- if (simState == TelephonyManager.SIM_STATE_ABSENT) {
- // MVNO SIMs can become transiently NOT_READY when switching networks,
- // so we should only lock when they are ABSENT.
- if (lastSimStateWasLocked) {
- if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
- + "previous state was locked. Reset the state.");
+ } else {
+ if (lastSimStateWasLocked && mShowing) {
+ if (DEBUG_SIM_STATES) {
+ Log.d(TAG, "SIM moved to "
+ + "NOT_READY/ABSENT/UNKNOWN when the previous state "
+ + "was locked. Reset the state.");
+ }
resetStateLocked();
}
- mSimWasLocked.append(slotId, false);
}
+
+ mSimWasLocked.append(slotId, false);
}
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 5e3779a..59288a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -60,6 +60,8 @@
val currentClock: StateFlow<ClockController?>
+ val previewClock: StateFlow<ClockController>
+
val clockEventController: ClockEventController
fun setClockSize(@ClockSize size: Int)
}
@@ -120,6 +122,15 @@
initialValue = clockRegistry.createCurrentClock()
)
+ override val previewClock: StateFlow<ClockController> =
+ currentClockId
+ .map { clockRegistry.createCurrentClock() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = clockRegistry.createCurrentClock()
+ )
+
@VisibleForTesting
suspend fun getClockSize(): SettingsClockSize {
return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a97c152..0cf74a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,15 +24,15 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample as sampleUtil
import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
@SysUISingleton
@@ -62,14 +62,17 @@
listenForTransitionToCamera(scope, keyguardInteractor)
}
+ @FlowPreview
private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
// Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
// will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
// happening prematurely.
- .onEach { delay(50) }
- .sample(
+ // This should eventually be removed in favor of
+ // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
+ .sample(150L)
+ .sampleCombine(
keyguardInteractor.primaryBouncerShowing,
startedKeyguardTransitionStep,
powerInteractor.isAwake,
@@ -111,19 +114,20 @@
private fun listenForAlternateBouncerToGone() {
scope.launch {
- keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
- (isKeyguardGoingAway, keyguardState) ->
- if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- startTransitionTo(KeyguardState.GONE)
+ keyguardInteractor.isKeyguardGoingAway
+ .sampleUtil(finishedKeyguardState, ::Pair)
+ .collect { (isKeyguardGoingAway, keyguardState) ->
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+ startTransitionTo(KeyguardState.GONE)
+ }
}
- }
}
}
private fun listenForAlternateBouncerToPrimaryBouncer() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(startedKeyguardTransitionStep, ::Pair)
+ .sampleUtil(startedKeyguardTransitionStep, ::Pair)
.collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
if (
isPrimaryBouncerShowing &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8fa33ee7..5606d43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,18 +21,18 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -85,15 +85,16 @@
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
.sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- ::Pair
- ),
- ::toTriple
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
)
- .collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
+ .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
val modeOnCanceled =
if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
@@ -126,15 +127,29 @@
}
private fun listenForAodToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
(biometricUnlockState, keyguardState) ->
+ KeyguardWmStateRefactor.assertInLegacyMode()
if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
startTransitionTo(KeyguardState.GONE)
}
}
}
}
+
+ /**
+ * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
+ * AOD.
+ */
+ fun dismissAod() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7477624..6b85a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -68,11 +68,11 @@
scope.launch {
keyguardInteractor.isKeyguardShowing
.sample(
- startedKeyguardTransitionStep,
+ currentKeyguardState,
communalInteractor.isIdleOnCommunal,
)
- .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
- if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+ .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && currentState == KeyguardState.GONE) {
val to =
if (isIdleOnCommunal) {
KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 356c408..196770a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -44,6 +44,8 @@
val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
+ val previewClock: StateFlow<ClockController> = keyguardClockRepository.previewClock
+
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 8784723..c496a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.util.kotlin.toPx
import dagger.Lazy
import javax.inject.Inject
@@ -44,6 +45,7 @@
transitionInteractor: KeyguardTransitionInteractor,
inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
swipeToDismissInteractor: SwipeToDismissInteractor,
+ notificationLaunchInteractor: NotificationLaunchAnimationInteractor,
) {
/**
* The view params to use for the surface. These params describe the alpha/translation values to
@@ -53,10 +55,20 @@
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.currentKeyguardState,
- ) { startedStep, currentState ->
+ notificationLaunchInteractor.isLaunchAnimationRunning,
+ ) { startedStep, currentState, notifAnimationRunning ->
// If we're in transition to GONE, special unlock animation params apply.
if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
- if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) {
+ if (notifAnimationRunning) {
+ // If the notification launch animation is running, leave the alpha at 0f.
+ // The ActivityLaunchAnimator will morph it from the notification at the
+ // appropriate time.
+ return@combine KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ } else if (
+ inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()
+ ) {
// The Launcher icons have their own translation/alpha animations during the
// in-window animation. We'll just make the surface visible and let Launcher
// do its thing.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b43ab5e..310f13d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -60,6 +60,7 @@
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
+ private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -346,6 +347,7 @@
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ AOD -> fromAodTransitionInteractor.get().dismissAod()
else ->
Log.e(
"KeyguardTransitionInteractor",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 19d00cf..c7f262a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -42,7 +42,7 @@
private val lightRevealScrimRepository: LightRevealScrimRepository,
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
- powerInteractor: PowerInteractor,
+ private val powerInteractor: PowerInteractor,
) {
init {
@@ -83,11 +83,13 @@
// (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
// correct end states are respected even if the screen turned off (or was still off)
// when the animation finished
- powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
- it == 1f ||
- it == 0f
+ screenIsShowingContent() || it == 1f || it == 0f
}
+ private fun screenIsShowingContent() =
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+ powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 5c2df45..3ccbdba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -60,6 +60,7 @@
// The following are MutableSharedFlows, and do not require flowOn
val startedKeyguardState = transitionInteractor.startedKeyguardState
val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+ val currentKeyguardState = transitionInteractor.currentKeyguardState
suspend fun startTransitionTo(
toState: KeyguardState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 49af664..b81793e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -19,6 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -26,7 +28,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
@SysUISingleton
class WindowManagerLockscreenVisibilityInteractor
@@ -37,6 +38,7 @@
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
) {
private val defaultSurfaceBehindVisibility =
transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -72,8 +74,7 @@
*/
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
- transitionInteractor
- .isInTransitionToAnyState
+ transitionInteractor.isInTransitionToAnyState
.flatMapLatest { isInTransition ->
if (!isInTransition) {
defaultSurfaceBehindVisibility
@@ -99,12 +100,16 @@
combine(
transitionInteractor.isInTransitionToState(KeyguardState.GONE),
transitionInteractor.finishedKeyguardState,
- surfaceBehindInteractor.isAnimatingSurface
- ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ surfaceBehindInteractor.isAnimatingSurface,
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+ // Using the animation if we're animating it directly, or if the
+ // ActivityLaunchAnimator is in the process of animating it.
+ val animationsRunning = isAnimatingSurface || notifLaunchRunning
// We may still be animating the surface after the keyguard is fully GONE, since
// some animations (like the translation spring) are not tied directly to the
// transition step amount.
- isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f0e89f9..62a6e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -18,7 +18,6 @@
import android.transition.TransitionManager
import android.transition.TransitionSet
-import android.util.Log
import android.view.View.INVISIBLE
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
@@ -36,7 +35,6 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
@@ -78,10 +76,6 @@
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
- Log.d(
- "ClockViewBinder",
- "Sherry clockShouldBeCentered $clockShouldBeCentered"
- )
viewModel.clock?.let {
// Weather clock also has hasCustomPositionUpdatedAnimation as true
// TODO(b/323020908): remove ID check
@@ -169,16 +163,12 @@
rootView: ConstraintLayout,
) {
clockController?.let { clock ->
- clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
- if (clock.largeClock.layout.views.size == 1) {
- clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large
- }
- // small clock should either be a single view or container with id
- // `lockscreen_clock_view`
clock.smallClock.layout.views.forEach {
rootView.addView(it).apply { it.visibility = INVISIBLE }
}
- clock.largeClock.layout.views.forEach { rootView.addView(it) }
+ clock.largeClock.layout.views.forEach {
+ rootView.addView(it).apply { it.visibility = INVISIBLE }
+ }
}
}
fun applyConstraints(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 1b5b329..b56c998 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -17,12 +17,35 @@
package com.android.systemui.keyguard.ui.binder
+import android.content.Context
import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ClockEventController
+import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
+import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import com.android.systemui.util.Utils
+import kotlin.reflect.KSuspendFunction1
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/** Binder for the small clock view, large clock view. */
object KeyguardPreviewClockViewBinder {
@@ -45,4 +68,129 @@
}
}
}
+
+ @JvmStatic
+ fun bind(
+ context: Context,
+ rootView: ConstraintLayout,
+ viewModel: KeyguardPreviewClockViewModel,
+ clockEventController: ClockEventController,
+ updateClockAppearance: KSuspendFunction1<ClockController, Unit>
+ ) {
+ rootView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(viewModel.selectedClockSize, viewModel.previewClock) { _, clock ->
+ clock
+ }
+ .collect { previewClock ->
+ viewModel.lastClock?.let { lastClock ->
+ (lastClock.largeClock.layout.views +
+ lastClock.smallClock.layout.views)
+ .forEach { rootView.removeView(it) }
+ }
+ viewModel.lastClock = previewClock
+ clockEventController.clock = previewClock
+ updateClockAppearance(previewClock)
+
+ if (viewModel.shouldHighlightSelectedAffordance) {
+ (previewClock.largeClock.layout.views +
+ previewClock.smallClock.layout.views)
+ .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
+ }
+ previewClock.largeClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
+
+ previewClock.smallClock.layout.views.forEach {
+ (it.parent as? ViewGroup)?.removeView(it)
+ rootView.addView(it)
+ }
+ applyPreviewConstraints(context, rootView, viewModel)
+ }
+ }
+ }
+ }
+ }
+
+ private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
+ constraints.apply {
+ constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+ val largeClockTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ context.resources.getDimensionPixelSize(
+ customizationR.dimen.small_clock_padding_top
+ ) +
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_smartspace_top_offset
+ ) +
+ getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
+ getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
+ connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+ connect(
+ R.id.lockscreen_clock_view_large,
+ ConstraintSet.END,
+ PARENT_ID,
+ ConstraintSet.END
+ )
+
+ connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+ constrainHeight(
+ R.id.lockscreen_clock_view,
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+ )
+ connect(
+ R.id.lockscreen_clock_view,
+ START,
+ PARENT_ID,
+ START,
+ context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
+ val smallClockTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+ }
+ }
+
+ private fun applyPreviewConstraints(
+ context: Context,
+ rootView: ConstraintLayout,
+ viewModel: KeyguardPreviewClockViewModel
+ ) {
+ val cs = ConstraintSet().apply { clone(rootView) }
+ val clock = viewModel.previewClock.value
+ applyClockDefaultConstraints(context, cs)
+ clock.largeClock.layout.applyPreviewConstraints(cs)
+ clock.smallClock.layout.applyPreviewConstraints(cs)
+
+ // When selectedClockSize is the initial value, make both clocks invisible to avoid
+ // flickering
+ val largeClockVisibility =
+ when (viewModel.selectedClockSize.value) {
+ SettingsClockSize.DYNAMIC -> VISIBLE
+ SettingsClockSize.SMALL -> INVISIBLE
+ null -> INVISIBLE
+ }
+ val smallClockVisibility =
+ when (viewModel.selectedClockSize.value) {
+ SettingsClockSize.DYNAMIC -> INVISIBLE
+ SettingsClockSize.SMALL -> VISIBLE
+ null -> INVISIBLE
+ }
+
+ cs.apply {
+ setVisibility(clock.largeClock.layout.views, largeClockVisibility)
+ setVisibility(clock.smallClock.layout.views, smallClockVisibility)
+ }
+ cs.applyTo(rootView)
+ }
+
+ private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+ private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a0c0095..c14917b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -46,6 +46,7 @@
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -197,6 +198,9 @@
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
}
+ if (migrateClocksToBlueprint()) {
+ clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
+ }
runBlocking(mainDispatcher) {
host =
SurfaceControlViewHost(
@@ -396,11 +400,21 @@
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
+ if (migrateClocksToBlueprint()) {
+ KeyguardPreviewClockViewBinder.bind(
+ context,
+ keyguardRootView,
+ clockViewModel,
+ clockController,
+ ::updateClockAppearance
+ )
+ } else {
+ KeyguardPreviewClockViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ clockViewModel,
+ )
+ }
}
setUpSmartspace(previewContext, rootView)
@@ -474,55 +488,61 @@
private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
val resources = parentView.resources
- largeClockHostView = FrameLayout(previewContext)
- largeClockHostView.layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- )
- parentView.addView(largeClockHostView)
- largeClockHostView.isInvisible = true
+ if (!migrateClocksToBlueprint()) {
+ largeClockHostView = FrameLayout(previewContext)
+ largeClockHostView.layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ )
+ largeClockHostView.isInvisible = true
+ parentView.addView(largeClockHostView)
- smallClockHostView = FrameLayout(previewContext)
- val layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
+ smallClockHostView = FrameLayout(previewContext)
+ val layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_height
+ )
)
+ layoutParams.topMargin =
+ KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_padding_top
+ )
+ smallClockHostView.layoutParams = layoutParams
+ smallClockHostView.setPaddingRelative(
+ /* start = */ resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.clock_padding_start
+ ),
+ /* top = */ 0,
+ /* end = */ 0,
+ /* bottom = */ 0
)
- layoutParams.topMargin =
- KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
- )
- smallClockHostView.layoutParams = layoutParams
- smallClockHostView.setPaddingRelative(
- resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.clock_padding_start
- ),
- 0,
- 0,
- 0
- )
- smallClockHostView.clipChildren = false
- parentView.addView(smallClockHostView)
- smallClockHostView.isInvisible = true
+ smallClockHostView.clipChildren = false
+ parentView.addView(smallClockHostView)
+ smallClockHostView.isInvisible = true
+ }
// TODO (b/283465254): Move the listeners to KeyguardClockRepository
- val clockChangeListener =
- object : ClockRegistry.ClockChangeListener {
- override fun onCurrentClockChanged() {
- onClockChanged()
+ if (!migrateClocksToBlueprint()) {
+ val clockChangeListener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() {
+ onClockChanged()
+ }
}
- }
- clockRegistry.registerClockChangeListener(clockChangeListener)
- disposables.add(
- DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
- )
+ clockRegistry.registerClockChangeListener(clockChangeListener)
+ disposables.add(
+ DisposableHandle {
+ clockRegistry.unregisterClockChangeListener(clockChangeListener)
+ }
+ )
- clockController.registerListeners(parentView)
- disposables.add(DisposableHandle { clockController.unregisterListeners() })
+ clockController.registerListeners(parentView)
+ disposables.add(DisposableHandle { clockController.unregisterListeners() })
+ }
val receiver =
object : BroadcastReceiver() {
@@ -542,50 +562,61 @@
)
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- if (clockController.clock !is DefaultClockController) {
- clockController.clock
- ?.largeClock
- ?.events
- ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
- clockController.clock
- ?.smallClock
- ?.events
- ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView))
+ if (!migrateClocksToBlueprint()) {
+ val layoutChangeListener =
+ View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ if (clockController.clock !is DefaultClockController) {
+ clockController.clock
+ ?.largeClock
+ ?.events
+ ?.onTargetRegionChanged(
+ KeyguardClockSwitch.getLargeClockRegion(parentView)
+ )
+ clockController.clock
+ ?.smallClock
+ ?.events
+ ?.onTargetRegionChanged(
+ KeyguardClockSwitch.getSmallClockRegion(parentView)
+ )
+ }
}
- }
- parentView.addOnLayoutChangeListener(layoutChangeListener)
- disposables.add(
- DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
- )
+ parentView.addOnLayoutChangeListener(layoutChangeListener)
+ disposables.add(
+ DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
+ )
+ }
onClockChanged()
}
+ private suspend fun updateClockAppearance(clock: ClockController) {
+ clockController.clock = clock
+ val colors = wallpaperColors
+ if (clockRegistry.seedColor == null && colors != null) {
+ // Seed color null means users do not override any color on the clock. The default
+ // color will need to use wallpaper's extracted color and consider if the
+ // wallpaper's color is dark or light.
+ val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
+ val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+ val lightClockColor = wallpaperColorScheme.accent1.s100
+ val darkClockColor = wallpaperColorScheme.accent2.s600
+
+ // Note that when [wallpaperColors] is null, isWallpaperDark is true.
+ val isWallpaperDark: Boolean =
+ (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+ clock.events.onSeedColorChanged(
+ if (isWallpaperDark) lightClockColor else darkClockColor
+ )
+ }
+ }
private fun onClockChanged() {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
coroutineScope.launch {
val clock = clockRegistry.createCurrentClock()
clockController.clock = clock
-
- val colors = wallpaperColors
- if (clockRegistry.seedColor == null && colors != null) {
- // Seed color null means users do not override any color on the clock. The default
- // color will need to use wallpaper's extracted color and consider if the
- // wallpaper's color is dark or light.
- val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
- val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
- val lightClockColor = wallpaperColorScheme.accent1.s100
- val darkClockColor = wallpaperColorScheme.accent2.s600
-
- // Note that when [wallpaperColors] is null, isWallpaperDark is true.
- val isWallpaperDark: Boolean =
- (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
- clock.events.onSeedColorChanged(
- if (isWallpaperDark) lightClockColor else darkClockColor
- )
- }
-
+ updateClockAppearance(clock)
updateLargeClock(clock)
updateSmallClock(clock)
}
@@ -626,6 +657,9 @@
}
private fun updateLargeClock(clock: ClockController) {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
clock.largeClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
)
@@ -637,6 +671,9 @@
}
private fun updateSmallClock(clock: ClockController) {
+ if (migrateClocksToBlueprint()) {
+ return
+ }
clock.smallClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
)
@@ -656,6 +693,6 @@
private const val KEY_DISPLAY_ID = "display_id"
private const val KEY_COLORS = "wallpaper_colors"
- private const val DIM_ALPHA = 0.3f
+ const val DIM_ALPHA = 0.3f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b1178f5..631b342 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -201,13 +201,16 @@
}
private fun getDimen(name: String): Int {
- val res = context.packageManager.getResourcesForApplication(context.packageName)
- val id = res.getIdentifier(name, "dimen", context.packageName)
- return res.getDimensionPixelSize(id)
+ return getDimen(context, name)
}
companion object {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+ fun getDimen(context: Context, name: String): Int {
+ val res = context.packageManager.getResourcesForApplication(context.packageName)
+ val id = res.getIdentifier(name, "dimen", context.packageName)
+ return res.getDimensionPixelSize(id)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 3737e6f..d26356e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -46,6 +47,14 @@
to = KeyguardState.GONE,
)
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ )
+
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 780e323..828e033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -28,6 +28,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import javax.inject.Inject
@@ -117,7 +120,10 @@
): Flow<BurnInModel> {
return combine(
merge(
- keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
+ keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
+ it.value
+ },
keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
)
.map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index 5301302..f95a8a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -20,9 +20,14 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** View model for the small clock view, large clock view. */
class KeyguardPreviewClockViewModel
@@ -30,11 +35,24 @@
constructor(
@Application private val context: Context,
interactor: KeyguardClockInteractor,
+ @Application private val applicationScope: CoroutineScope,
) {
+ var shouldHighlightSelectedAffordance: Boolean = false
val isLargeClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
val isSmallClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+ var lastClock: ClockController? = null
+
+ val previewClock: StateFlow<ClockController> = interactor.previewClock
+
+ val selectedClockSize: StateFlow<SettingsClockSize?> =
+ interactor.selectedClockSize.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 709e184..f8a12bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -61,6 +61,9 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
@@ -92,10 +95,15 @@
val alpha: Flow<Float> =
combine(
communalInteractor.isIdleOnCommunal,
+ // The transitions are mutually exclusive, so they are safe to merge to get the last
+ // value emitted by any of them. Do not add flows that cannot make this guarantee.
merge(
aodAlphaViewModel.alpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
)
) { isIdleOnCommunal, alpha ->
if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index a26ef07..d981650 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -46,12 +46,14 @@
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
+ duration = 200.milliseconds,
onStep = { 1 - it },
onFinish = { 0f },
onCancel = { 1f },
)
+ val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 3f4f347..8d918e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -22,9 +22,12 @@
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.res.R
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
+
+private const val TAG = "SeekBarObserver"
/**
* Observer for changes from SeekBarViewModel.
@@ -39,6 +42,10 @@
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
}
+ // Trace state loggers for playing and listening states of progress bar.
+ private val playingStateLogger = TraceStateLogger("$TAG#playing")
+ private val listeningStateLogger = TraceStateLogger("$TAG#listening")
+
val seekBarEnabledMaxHeight =
holder.seekBar.context.resources.getDimensionPixelSize(
R.dimen.qs_media_enabled_seekbar_height
@@ -103,9 +110,13 @@
return
}
+ playingStateLogger.log("${data.playing}")
+ listeningStateLogger.log("${data.listening}")
+
holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
holder.seekBar.isEnabled = data.seekAvailable
- progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
+ progressDrawable?.animate =
+ data.playing && !data.scrubbing && animationEnabled && data.listening
progressDrawable?.transitionEnabled = !data.seekAvailable
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index a91917a..40a9b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -84,7 +84,16 @@
@Background private val bgExecutor: RepeatableExecutor,
private val falsingManager: FalsingManager,
) {
- private var _data = Progress(false, false, false, false, null, 0)
+ private var _data =
+ Progress(
+ enabled = false,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = null,
+ duration = 0,
+ listening = false
+ )
set(value) {
val enabledChanged = value.enabled != field.enabled
field = value
@@ -239,7 +248,7 @@
)
false
else true
- _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
+ _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
checkIfPollingNeeded()
}
@@ -258,6 +267,7 @@
scrubbing = false,
elapsedTime = position,
duration = 100,
+ listening = false,
)
}
@@ -548,9 +558,12 @@
data class Progress(
val enabled: Boolean,
val seekAvailable: Boolean,
+ /** whether playback state is not paused or connecting */
val playing: Boolean,
val scrubbing: Boolean,
val elapsedTime: Int?,
- val duration: Int
+ val duration: Int,
+ /** whether seekBar is listening to progress updates */
+ val listening: Boolean,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 02f0d12..038582c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -26,14 +26,15 @@
import androidx.core.view.GestureDetectorCompat
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
+import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.animation.PhysicsAnimator
@@ -42,6 +43,7 @@
private const val SCROLL_DELAY = 100L
private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
+private const val TAG = "MediaCarouselScrollHandler"
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio were not
@@ -63,6 +65,9 @@
private val logSmartspaceImpression: (Boolean) -> Unit,
private val logger: MediaUiEventLogger
) {
+ /** Trace state logger for media carousel visibility */
+ private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
+
/** Is the view in RTL */
val isRtl: Boolean
get() = scrollView.isLayoutRtl
@@ -182,6 +187,7 @@
if (field != value) {
field = value
seekBarUpdateListener.invoke(field)
+ visibleStateLogger.log("$visibleToUser")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..afe6285 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
package com.android.systemui.mediaprojection
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
import android.content.Context
import android.media.projection.IMediaProjection
import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@
*/
class MediaProjectionServiceHelper {
companion object {
+ /**
+ * This change id ensures that users are presented with a choice of capturing a single app
+ * or the entire screen when initiating a MediaProjection session, overriding the usage of
+ * MediaProjectionConfig#createConfigForDefaultDisplay.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
private const val TAG = "MediaProjectionServiceHelper"
private val service =
IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..0769731 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
package com.android.systemui.mediaprojection.permission;
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -104,6 +109,7 @@
}
@Override
+ @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -231,6 +237,9 @@
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
+ final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -242,6 +251,7 @@
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
+ overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
+ forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig),
+ createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean = false,
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -80,8 +82,13 @@
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
+ // The single app option should only be disabled if there is an app name provided,
+ // the client has setup a MediaProjection with
+ // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+ // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
appName != null &&
+ !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 1b3b584..58e7613 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -40,7 +40,7 @@
val icon =
Icon.Loaded(
resources.getDrawable(
- if (data.isEnabled) {
+ if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
R.drawable.qs_flashlight_icon_on
} else {
R.drawable.qs_flashlight_icon_off
@@ -51,17 +51,22 @@
)
this.icon = { icon }
- if (data.isEnabled) {
+ contentDescription = label
+
+ if (data is FlashlightTileModel.FlashlightTemporarilyUnavailable) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.quick_settings_flashlight_camera_in_use)
+ stateDescription = secondaryLabel
+ supportedActions = setOf()
+ return@build
+ } else if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
} else {
activationState = QSTileState.ActivationState.INACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
}
- contentDescription = label
- supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- )
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 53d4cf9..1544804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -38,25 +38,30 @@
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<FlashlightTileModel> = conflatedCallbackFlow {
- val initialValue = flashlightController.isEnabled
- trySend(FlashlightTileModel(initialValue))
-
val callback =
object : FlashlightController.FlashlightListener {
override fun onFlashlightChanged(enabled: Boolean) {
- trySend(FlashlightTileModel(enabled))
+ trySend(FlashlightTileModel.FlashlightAvailable(enabled))
}
override fun onFlashlightError() {
- trySend(FlashlightTileModel(false))
+ trySend(FlashlightTileModel.FlashlightAvailable(false))
}
override fun onFlashlightAvailabilityChanged(available: Boolean) {
- trySend(FlashlightTileModel(flashlightController.isEnabled))
+ trySend(
+ if (available)
+ FlashlightTileModel.FlashlightAvailable(flashlightController.isEnabled)
+ else FlashlightTileModel.FlashlightTemporarilyUnavailable
+ )
}
}
flashlightController.addCallback(callback)
awaitClose { flashlightController.removeCallback(callback) }
}
+ /**
+ * Used to determine if the tile should be displayed. Not to be confused with the availability
+ * in the data model above. This flow signals whether the tile should be shown or hidden.
+ */
override fun availability(user: UserHandle): Flow<Boolean> =
flowOf(flashlightController.hasFlashlight())
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
index 9180e30..bedd65e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -35,7 +35,10 @@
with(input) {
when (action) {
is QSTileUserAction.Click -> {
- if (!ActivityManager.isUserAMonkey()) {
+ if (
+ !ActivityManager.isUserAMonkey() &&
+ input.data is FlashlightTileModel.FlashlightAvailable
+ ) {
flashlightController.setFlashlight(!input.data.isEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index ef6b2be..f54b371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -16,9 +16,14 @@
package com.android.systemui.qs.tiles.impl.flashlight.domain.model
-/**
- * Flashlight tile model.
- *
- * @param isEnabled is true when the falshlight is enabled;
- */
-@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
+sealed interface FlashlightTileModel {
+ /**
+ * In this state, the tile can be turned on or off.
+ *
+ * @param isEnabled is true when the flashlight is on and false when off.
+ */
+ @JvmInline value class FlashlightAvailable(val isEnabled: Boolean) : FlashlightTileModel
+
+ /** In this state the tile is grayed out and flashlight cannot be turned on. */
+ data object FlashlightTemporarilyUnavailable : FlashlightTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 56c0ca9..dcd87c0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.startable
+import android.app.StatusBarManager
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -42,6 +43,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
@@ -85,6 +87,7 @@
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
private val windowController: NotificationShadeWindowController,
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ private val centralSurfaces: CentralSurfaces,
) : CoreStartable {
override fun start() {
@@ -95,6 +98,7 @@
hydrateSystemUiState()
collectFalsingSignals()
hydrateWindowFocus()
+ hydrateInteractionState()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -376,6 +380,46 @@
}
}
+ /** Keeps the interaction state of [CentralSurfaces] up-to-date. */
+ private fun hydrateInteractionState() {
+ applicationScope.launch {
+ deviceEntryInteractor.isUnlocked
+ .map { !it }
+ .flatMapLatest { isDeviceLocked ->
+ if (isDeviceLocked) {
+ sceneInteractor.transitionState
+ .mapNotNull { it as? ObservableTransitionState.Idle }
+ .map { it.scene }
+ .distinctUntilChanged()
+ .map { sceneKey ->
+ when (sceneKey) {
+ // When locked, showing the lockscreen scene should be reported
+ // as "interacting" while showing other scenes should report as
+ // "not interacting".
+ //
+ // This is done here in order to match the legacy
+ // implementation. The real reason why is lost to lore and myth.
+ SceneKey.Lockscreen -> true
+ SceneKey.Bouncer -> false
+ SceneKey.Shade -> false
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+ .collect { isInteractingOrNull ->
+ isInteractingOrNull?.let { isInteracting ->
+ centralSurfaces.setInteracting(
+ StatusBarManager.WINDOW_STATUS_BAR,
+ isInteracting,
+ )
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index be1fa2b..e9af295 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,11 +30,11 @@
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
@@ -236,7 +236,7 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mTracking = true;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), false);
SeekableSliderEventProducer eventProducer =
@@ -255,7 +255,7 @@
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mTracking = false;
- mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
+ mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
if (mListener != null) {
mListener.onChanged(mTracking, getValue(), true);
SeekableSliderEventProducer eventProducer =
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
index 3a30880..a8641bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -21,10 +21,10 @@
public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "slider started to track touch")
- SLIDER_STARTED_TRACKING_TOUCH(1472),
- @UiEvent(doc = "slider stopped tracking touch")
- SLIDER_STOPPED_TRACKING_TOUCH(1473);
+ @UiEvent(doc = "brightness slider started to track touch")
+ BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472),
+ @UiEvent(doc = "brightness slider stopped tracking touch")
+ BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e219bcc..133fa12 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1021,22 +1021,24 @@
instantCollapse();
} else {
mView.animate().cancel();
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- // Translate up by 4%.
- .translationY(mView.getHeight() * -0.04f)
- // This start delay is to give us time to animate out before
- // the launcher icons animation starts, so use that as our
- // duration.
- .setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_ACCELERATE)
- .withEndAction(() -> {
- instantCollapse();
- mView.setAlpha(1f);
- mView.setTranslationY(0f);
- })
- .start();
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ // Translate up by 4%.
+ .translationY(mView.getHeight() * -0.04f)
+ // This start delay is to give us time to animate out before
+ // the launcher icons animation starts, so use that as our
+ // duration.
+ .setDuration(unlockAnimationStartDelay)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
+ .withEndAction(() -> {
+ instantCollapse();
+ mView.setAlpha(1f);
+ mView.setTranslationY(0f);
+ })
+ .start();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 67bb814..f89a9c70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
import dagger.Binds
@@ -35,6 +36,10 @@
@Binds
@SysUISingleton
+ abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor
+
+ @Binds
+ @SysUISingleton
abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index fc2c3ee..e4d5d22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -24,6 +24,8 @@
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideShadeBackActionInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
+ sceneContainerOff: Provider<NotificationPanelViewController>
+ ): ShadeBackActionInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 4f970b3..0befb61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@
* this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
* pulled up into ShadeViewController.
*/
-interface ShadeSurface : ShadeViewController {
+interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
/** Initialize objects instead of injecting to avoid circular dependencies. */
fun initDependencies(
centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3430eed..74035bd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -79,15 +79,8 @@
/** Collapses the shade instantly without animation. */
fun instantCollapse()
- /**
- * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
- * in split shade, it will collapse the whole shade.
- *
- * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
- */
- fun animateCollapseQs(fullyCollapse: Boolean)
-
/** Returns whether the shade can be collapsed. */
+ @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
fun canBeCollapsed(): Boolean
/** Returns whether the shade is in the process of collapsing. */
@@ -142,22 +135,6 @@
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
- /**
- * Close the keyguard user switcher if it is open and capable of closing.
- *
- * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
- * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
- *
- * @return true if the keyguard user switcher was open, and is now closed
- */
- fun closeUserSwitcherIfOpen(): Boolean
-
- /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
- fun onBackPressed()
-
- /** Sets progress of the predictive back animation. */
- fun onBackProgressed(progressFraction: Float)
-
/** Sets the alpha value of the shade to a value between 0 and 255. */
fun setAlpha(alpha: Int, animate: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 1240c6e..5d966ac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,13 +19,15 @@
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
/** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+class ShadeViewControllerEmptyImpl @Inject constructor() :
+ ShadeViewController, ShadeBackActionInteractor {
override fun expand(animate: Boolean) {}
override fun expandToQs() {}
override fun expandToNotifications() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
new file mode 100644
index 0000000..15ea219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+/** Allows the back action to interact with the shade. */
+interface ShadeBackActionInteractor {
+ /**
+ * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+ * in split shade, it will collapse the whole shade.
+ *
+ * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ fun animateCollapseQs(fullyCollapse: Boolean)
+
+ /** Returns whether the shade can be collapsed. */
+ fun canBeCollapsed(): Boolean
+
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+ * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ fun closeUserSwitcherIfOpen(): Boolean
+
+ /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+ fun onBackPressed()
+
+ /** Sets progress of the predictive back animation. */
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ fun onBackProgressed(progressFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
new file mode 100644
index 0000000..9bbe1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Implementation of ShadeBackActionInteractor backed by scenes. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeBackActionInteractorImpl
+@Inject
+constructor(
+ val shadeInteractor: ShadeInteractor,
+ val sceneInteractor: SceneInteractor,
+ val deviceEntryInteractor: DeviceEntryInteractor,
+) : ShadeBackActionInteractor {
+ override fun animateCollapseQs(fullyCollapse: Boolean) {
+ if (shadeInteractor.isQsExpanded.value) {
+ val key =
+ if (fullyCollapse) {
+ if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ } else {
+ SceneKey.Shade
+ }
+ sceneInteractor.changeScene(SceneModel(key), "animateCollapseQs")
+ }
+ }
+
+ override fun canBeCollapsed(): Boolean {
+ return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
+ }
+
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+
+ override fun onBackPressed() {
+ animateCollapseQs(false)
+ }
+
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+ override fun onBackProgressed(progressFraction: Float) {
+ // Not supported. Do nothing.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 3f2c818..7c71864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -386,6 +386,7 @@
}
}
+ @Deprecated("As part of b/301915812")
private fun scheduleDelayedDozeAmountAnimation() {
val alreadyRunning = delayedDozeAmountAnimator != null
logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 20c8add..6836816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -238,6 +238,9 @@
) {
val TAG = "AvalancheSuppressor"
+ override var reason: String = "avalanche"
+ protected set
+
enum class State {
ALLOW_CONVERSATION_AFTER_AVALANCHE,
ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -252,13 +255,13 @@
override fun shouldSuppress(entry: NotificationEntry): Boolean {
val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
- val state = allow(entry)
+ val state = calculateState(entry)
val suppress = isActive && state == State.SUPPRESS
reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
return suppress
}
- fun allow(entry: NotificationEntry): State {
+ private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
entry.sbn.notification.`when` > avalancheProvider.startTime
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 2f80c5d..ee79727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -85,7 +85,7 @@
/** A reason why visual interruptions might be suppressed based on the notification. */
abstract class VisualInterruptionFilter(
override val types: Set<VisualInterruptionType>,
- override var reason: String,
+ override val reason: String,
override val uiEventId: UiEventEnum? = null,
override val eventLogData: EventLogData? = null
) : VisualInterruptionSuppressor {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 3915c376..811da51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -31,20 +32,24 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -70,6 +75,7 @@
import kotlinx.coroutines.isActive
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
+@SysUISingleton
class SharedNotificationContainerViewModel
@Inject
constructor(
@@ -80,6 +86,9 @@
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -94,6 +103,12 @@
mapOf<Edge?, Flow<Float>>(
Edge(from = LOCKSCREEN, to = DREAMING) to
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+ Edge(from = LOCKSCREEN, to = GONE) to
+ lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = ALTERNATE_BOUNCER, to = GONE) to
+ alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ Edge(from = PRIMARY_BOUNCER, to = GONE) to
+ primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
Edge(from = DREAMING, to = LOCKSCREEN) to
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
Edge(from = LOCKSCREEN, to = OCCLUDED) to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2099361..3e7089c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,6 +28,7 @@
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.newAodTransition;
import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -2497,7 +2498,8 @@
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
mDeviceInteractive = true;
- if (shouldAnimateDozeWakeup()) {
+ boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled();
+ if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
// If this is false, the power button must be physically pressed in order to
// trigger fingerprint authentication.
final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69282ae..3ad60d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1442,10 +1442,6 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
-
- if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardTransitionInteractor.startDismissKeyguardTransition();
- }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 3741f14..c0e36b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -91,7 +91,9 @@
@StatusBarFragmentScope
@Named(OPERATOR_NAME_VIEW)
static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
- return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+ operatorName.setVisibility(View.GONE);
+ return operatorName;
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index f36c335e..d509b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,6 +16,9 @@
package com.android.systemui.util.settings;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
+
import dagger.Binds;
import dagger.Module;
@@ -36,4 +39,9 @@
/** Bind GlobalSettingsImpl to GlobalSettings. */
@Binds
GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
+
+ /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
+ @Binds
+ UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
+ UserAwareSecureSettingsRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..d3e5080
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+interface UserAwareSecureSettingsRepository {
+
+ /**
+ * Emits boolean value of the setting for active user. Also emits starting value when
+ * subscribed.
+ * See: [SettingsProxy.getBool].
+ */
+ fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
+}
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val userRepository: UserRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : UserAwareSecureSettingsRepository {
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
+ return secureSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 0ba9abe..f490f3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -45,7 +45,7 @@
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -89,7 +89,7 @@
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var qsController: QuickSettingsController
- @Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var windowRootView: WindowRootView
@Mock private lateinit var viewRootImpl: ViewRootImpl
@@ -123,7 +123,7 @@
notificationShadeWindowController,
windowRootViewVisibilityInteractor
)
- .apply { this.setup(qsController, shadeViewController) }
+ .apply { this.setup(qsController, shadeBackActionInteractor) }
}
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -165,19 +165,19 @@
val result = backActionInteractor.onBackRequested()
assertTrue(result)
- verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
verify(statusBarKeyguardViewManager, never()).onBackPressed()
}
@Test
fun testOnBackRequested_closeUserSwitcherIfOpen() {
- whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+ whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
val result = backActionInteractor.onBackRequested()
assertTrue(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -189,7 +189,7 @@
assertFalse(result)
verify(statusBarKeyguardViewManager, never()).onBackPressed()
- verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+ verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
}
@Test
@@ -290,11 +290,11 @@
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
callback.onBackProgressed(createBackEvent(0.3f))
- verify(shadeViewController, never()).onBackProgressed(0.3f)
+ verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
}
@Test
@@ -305,11 +305,11 @@
powerInteractor.setAwakeForTest()
val callback = getBackInvokedCallback() as OnBackAnimationCallback
- whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+ whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
callback.onBackProgressed(createBackEvent(0.4f))
- verify(shadeViewController).onBackProgressed(0.4f)
+ verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
private fun getBackInvokedCallback(): OnBackInvokedCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index a84778a..eae953e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -16,11 +16,9 @@
package com.android.systemui.keyguard.data.repository
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.os.Handler
import android.util.Size
import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayInfo
import android.view.Surface
import androidx.test.filters.SmallTest
@@ -29,61 +27,37 @@
import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.same
-import org.mockito.Captor
-import org.mockito.Mock
import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
-private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class DisplayStateRepositoryTest : SysuiTestCase() {
- @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var deviceStateManager: DeviceStateManager
- @Mock private lateinit var displayManager: DisplayManager
- @Mock private lateinit var handler: Handler
- @Mock private lateinit var display: Display
- private lateinit var underTest: DisplayStateRepository
-
+ private val display = mock<Display>()
private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ private val fakeDeviceStateRepository = FakeDeviceStateRepository()
+ private val fakeDisplayRepository = FakeDisplayRepository()
- @Captor
- private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener>
+ private lateinit var underTest: DisplayStateRepository
@Before
fun setUp() {
- val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.array.config_rearDisplayDeviceStates,
- rearDisplayDeviceStates
- )
-
mContext.orCreateTestableResources.addOverride(
com.android.internal.R.bool.config_reverseDefaultRotation,
false
@@ -96,11 +70,8 @@
DisplayStateRepositoryImpl(
testScope.backgroundScope,
mContext,
- deviceStateManager,
- displayManager,
- handler,
- fakeExecutor,
- UnconfinedTestDispatcher(),
+ fakeDeviceStateRepository,
+ fakeDisplayRepository,
)
}
@@ -110,12 +81,10 @@
val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
runCurrent()
- val callback = deviceStateManager.captureCallback()
-
- callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+ fakeDeviceStateRepository.emit(DeviceState.FOLDED)
assertThat(isInRearDisplayMode).isFalse()
- callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+ fakeDeviceStateRepository.emit(DeviceState.REAR_DISPLAY)
assertThat(isInRearDisplayMode).isTrue()
}
@@ -125,19 +94,13 @@
val currentRotation by collectLastValue(underTest.currentRotation)
runCurrent()
- verify(displayManager)
- .registerDisplayListener(
- displayListenerCaptor.capture(),
- same(handler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
- )
-
whenever(display.getDisplayInfo(any())).then {
val info = it.getArgument<DisplayInfo>(0)
info.rotation = Surface.ROTATION_90
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90)
+
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
whenever(display.getDisplayInfo(any())).then {
@@ -145,7 +108,8 @@
info.rotation = Surface.ROTATION_180
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
}
@@ -155,13 +119,6 @@
val currentSize by collectLastValue(underTest.currentDisplaySize)
runCurrent()
- verify(displayManager)
- .registerDisplayListener(
- displayListenerCaptor.capture(),
- same(handler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
- )
-
whenever(display.getDisplayInfo(any())).then {
val info = it.getArgument<DisplayInfo>(0)
info.rotation = Surface.ROTATION_0
@@ -169,7 +126,7 @@
info.logicalHeight = 200
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentSize).isEqualTo(Size(100, 200))
whenever(display.getDisplayInfo(any())).then {
@@ -179,12 +136,7 @@
info.logicalHeight = 200
return@then true
}
- displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+ fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
assertThat(currentSize).isEqualTo(Size(200, 100))
}
}
-
-private fun DeviceStateManager.captureCallback() =
- withArgCaptor<DeviceStateManager.DeviceStateCallback> {
- verify(this@captureCallback).registerCallback(any(), capture())
- }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
deleted file mode 100644
index ed80a86..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.android.systemui.keyboard.stickykeys.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class StickyKeysRepositoryImplTest : SysuiTestCase() {
-
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
- private val secureSettings = FakeSettings()
- private val userRepository = Kosmos().fakeUserRepository
- private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
-
- @Before
- fun setup() {
- stickyKeysRepository = StickyKeysRepositoryImpl(
- mock<InputManager>(),
- dispatcher,
- secureSettings,
- userRepository,
- mock<StickyKeysLogger>()
- )
- userRepository.setUserInfos(USER_INFOS)
- setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
- }
-
- @Test
- fun settingEnabledEmitsValueForCurrentUser() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-
- assertThat(enabled).isTrue()
- }
- }
-
- @Test
- fun settingEnabledEmitsNewValueWhenSettingChanges() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectValues(stickyKeysRepository.settingEnabled)
- runCurrent()
-
- setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
-
- assertThat(enabled).containsExactly(true, false).inOrder()
- }
- }
-
- @Test
- fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
- testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
- runCurrent()
-
- userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
-
- assertThat(enabled).isFalse()
- }
- }
-
- private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
- val newValue = if (enabled) "1" else "0"
- secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
- }
-
- private companion object {
- val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
- val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
- val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 6eebb6d..d14d72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -68,11 +69,15 @@
@Before
fun setup() {
+ val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher
+ )
val stickyKeysRepository = StickyKeysRepositoryImpl(
inputManager,
dispatcher,
- secureSettings,
- userRepository,
+ settingsRepository,
mock<StickyKeysLogger>()
)
setStickyKeySetting(enabled = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
new file mode 100644
index 0000000..c174cb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromGoneTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromGoneTransitionInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
+ @Test
+ fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ ),
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ ),
+ ),
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ fun testTransitionsToLockscreen_ifFinishedInGone() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ reset(keyguardTransitionRepository)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // We're in the middle of a LOCKSCREEN -> GONE transition.
+ assertThat(keyguardTransitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 668fb64..6d8e7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,17 +27,15 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.FlingInfo
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -132,13 +130,11 @@
)
runCurrent()
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- .also {
- assertEquals(KeyguardState.LOCKSCREEN, it.from)
- assertEquals(KeyguardState.GONE, it.to)
- }
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
}
@Test
@@ -155,6 +151,6 @@
)
runCurrent()
- verify(transitionRepository, never()).startTransition(any())
+ assertThatRepository(transitionRepository).noTransitionsStarted()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index f23dd55..3f05bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.util.mockTopActivityClassName
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.assertValuesMatch
import com.google.common.truth.Truth.assertThat
@@ -192,4 +193,38 @@
)
.inOrder()
}
+
+ @Test
+ fun testSurfaceBehindModel_fromNotificationLaunch() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+ runCurrent()
+
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ )
+ runCurrent()
+
+ values.assertValuesMatch(
+ // We should be at alpha = 0f during the animation.
+ { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bb61d18..5b93df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -37,9 +37,9 @@
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -51,13 +51,12 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -254,15 +253,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -281,15 +278,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -308,15 +303,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.OCCLUDED,
+ ownerName = "FromOccludedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -336,17 +329,15 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -367,17 +358,15 @@
// WHEN the device begins to dream and the dream is lockscreen hosted
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -396,15 +385,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -423,15 +410,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.LOCKSCREEN,
+ ownerName = "FromLockscreenTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -454,17 +439,15 @@
// WHEN the lockscreen hosted dream stops
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Lockscreen should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -484,15 +467,13 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to Gone should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -514,15 +495,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -547,15 +526,13 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -579,15 +556,13 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.OCCLUDED,
+ from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -603,15 +578,13 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -649,7 +622,7 @@
// WHEN a signal comes that dreaming is enabled
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
// THEN the transition is ignored
verify(transitionRepository, never()).startTransition(any())
@@ -667,15 +640,13 @@
keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DOZING,
+ ownerName = "FromDozingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -699,15 +670,13 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.DOZING,
+ ownerName = FromDozingTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -726,15 +695,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -753,15 +720,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -776,15 +741,13 @@
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.LOCKSCREEN,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -804,17 +767,15 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -835,17 +796,15 @@
// WHEN the device begins to dream with the lockscreen hosted dream
keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ from = KeyguardState.GONE,
+ ownerName = "FromGoneTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -868,15 +827,13 @@
keyguardRepository.setKeyguardShowing(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GONE)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GLANCEABLE_HUB,
+ from = KeyguardState.GONE,
+ ownerName = FromGoneTransitionInteractor::class.simpleName,
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -894,21 +851,19 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@Test
- fun alternateBoucnerToAod() =
+ fun alternateBouncerToAod() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
bouncerRepository.setAlternateVisible(true)
@@ -924,17 +879,15 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.AOD,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -957,17 +910,15 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.DOZING,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
coroutineContext.cancelChildren()
}
@@ -987,17 +938,15 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAlternateBouncerTransitionInteractor",
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1025,18 +974,16 @@
// WHEN the alternateBouncer stops showing
bouncerRepository.setAlternateVisible(false)
- advanceUntilIdle()
+ advanceTimeBy(200L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1056,15 +1003,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1084,15 +1030,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to DOZING should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1108,15 +1053,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1140,16 +1084,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName)
- .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1171,15 +1113,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1202,15 +1143,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GONE should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1231,15 +1171,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to LOCKSCREEN should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1268,15 +1207,14 @@
keyguardRepository.setKeyguardOccluded(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to GLANCEABLE_HUB should occur
- assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromOccludedTransitionInteractor::class.simpleName,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1293,15 +1231,14 @@
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1318,15 +1255,14 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AlternateBouncer should occur
- assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromOccludedTransitionInteractor",
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1344,15 +1280,14 @@
bouncerRepository.setPrimaryShow(false)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromPrimaryBouncerTransitionInteractor",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1369,15 +1304,14 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DOZING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDozingTransitionInteractor",
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1396,15 +1330,14 @@
powerInteractor.setAwakeForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1426,15 +1359,14 @@
)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to AOD should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.AOD)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromDreamingTransitionInteractor",
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.AOD,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1450,15 +1382,14 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1474,15 +1405,14 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1498,15 +1428,14 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
// THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.AOD)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromAodTransitionInteractor",
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1532,14 +1461,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1559,14 +1487,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNull() // dragging should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = "FromLockscreenTransitionInteractor",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNull() }, // dragging should be manually animated
+ )
// WHEN the user stops dragging and shade is back to expanded
clearInvocations(transitionRepository)
@@ -1575,14 +1502,13 @@
shadeRepository.setLegacyShadeExpansion(1f)
runCurrent()
- // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.animator).isNotNull()
+ // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1614,15 +1540,13 @@
runCurrent()
// THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub opening is cancelled
clearInvocations(transitionRepository)
@@ -1634,14 +1558,13 @@
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ )
coroutineContext.cancelChildren()
}
@@ -1672,16 +1595,13 @@
progress.value = .1f
runCurrent()
- // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
// WHEN the user stops dragging and the glanceable hub closing is cancelled
clearInvocations(transitionRepository)
@@ -1693,14 +1613,11 @@
communalInteractor.setTransitionState(idleTransitionState)
runCurrent()
- // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
- val info2 =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.animator).isNull() // transition should be manually animated
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
coroutineContext.cancelChildren()
}
@@ -1715,16 +1632,13 @@
powerInteractor.setAsleepForTest()
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DOZING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DOZING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1739,16 +1653,13 @@
bouncerRepository.setPrimaryShow(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1763,16 +1674,13 @@
bouncerRepository.setAlternateVisible(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to PRIMARY_BOUNCER should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1796,16 +1704,13 @@
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to OCCLUDED should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1820,16 +1725,13 @@
keyguardRepository.setKeyguardGoingAway(true)
runCurrent()
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DOZING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.GONE)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.GONE,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
@@ -1849,33 +1751,19 @@
// WHEN the device begins to dream
keyguardRepository.setDreamingWithOverlay(true)
- advanceUntilIdle()
+ advanceTimeBy(100L)
- val info =
- withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture())
- }
- // THEN a transition to DREAMING should occur
- assertThat(info.ownerName)
- .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
- assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
- assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.animator).isNotNull()
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ animatorAssertion = { it.isNotNull() },
+ )
coroutineContext.cancelChildren()
}
- private fun createKeyguardInteractor(): KeyguardInteractor {
- return KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- commandQueue = commandQueue,
- bouncerRepository = bouncerRepository,
- powerInteractor = powerInteractor,
- )
- .keyguardInteractor
- }
-
private suspend fun TestScope.runTransitionAndSetWakefulness(
from: KeyguardState,
to: KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
new file mode 100644
index 0000000..655a551
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 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.keyguard.util
+
+import androidx.core.animation.ValueAnimator
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import junit.framework.Assert.assertEquals
+import kotlin.test.fail
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */
+class KeyguardTransitionRepositorySpySubject
+private constructor(
+ failureMetadata: FailureMetadata,
+ private val repository: KeyguardTransitionRepository,
+) : Subject(failureMetadata, repository) {
+
+ /**
+ * Asserts that we started a transition to the given state, optionally checking additional
+ * parameters. If an animator param or assertion is not provided, we will not assert anything
+ * about the animator.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, {}, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animator: ValueAnimator?,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled)
+ }
+
+ /**
+ * Asserts that we started a transition to the given state, optionally verifying additional
+ * params.
+ */
+ fun startedTransition(
+ ownerName: String? = null,
+ from: KeyguardState? = null,
+ to: KeyguardState,
+ animatorAssertion: (Subject) -> Unit,
+ modeOnCanceled: TransitionModeOnCanceled? = null,
+ ) {
+ withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) }
+ .also { transitionInfo ->
+ assertEquals(to, transitionInfo.to)
+ animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator))
+ from?.let { assertEquals(it, transitionInfo.from) }
+ ownerName?.let { assertEquals(it, transitionInfo.ownerName) }
+ modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) }
+ }
+ }
+
+ /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */
+ fun noTransitionsStarted() {
+ verify(repository, never()).startTransition(any())
+ }
+
+ companion object {
+ fun assertThat(
+ repository: KeyguardTransitionRepository
+ ): KeyguardTransitionRepositorySpySubject =
+ assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+ if (!Mockito.mockingDetails(repository).isSpy) {
+ fail(
+ "Cannot assert on a non-spy KeyguardTransitionRepository. " +
+ "Use Mockito.spy(keyguardTransitionRepository)."
+ )
+ }
+ KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+ }
+ .that(repository)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 043dae6..100e579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -24,9 +24,9 @@
import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -86,7 +86,7 @@
fun seekBarGone() {
// WHEN seek bar is disabled
val isEnabled = false
- val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0)
+ val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
observer.onChanged(data)
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
@@ -99,7 +99,7 @@
fun seekBarVisible() {
// WHEN seek bar is enabled
val isEnabled = true
- val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
observer.onChanged(data)
// THEN seek bar is visible and thick
assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -109,7 +109,7 @@
@Test
fun seekBarProgress() {
// WHEN part of the track has been played
- val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
observer.onChanged(data)
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
@@ -123,7 +123,8 @@
fun seekBarDisabledWhenSeekNotAvailable() {
// WHEN seek is not available
val isSeekAvailable = false
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isFalse()
@@ -133,7 +134,8 @@
fun seekBarEnabledWhenSeekNotAvailable() {
// WHEN seek is available
val isSeekAvailable = true
- val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+ val data =
+ SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
observer.onChanged(data)
// THEN seek bar is not enabled
assertThat(seekBarView.isEnabled()).isTrue()
@@ -144,7 +146,7 @@
// WHEN playing
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is animating
verify(mockSquigglyProgress).animate = true
@@ -155,7 +157,7 @@
// WHEN not playing & not scrubbing
val isPlaying = false
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -166,7 +168,7 @@
// WHEN playing & scrubbing
val isPlaying = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -177,7 +179,7 @@
// WHEN playing & scrubbing
val isPlaying = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable is not animating
verify(mockSquigglyProgress).animate = false
@@ -187,7 +189,7 @@
fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
val isEnabled = true
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -199,7 +201,7 @@
fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
val isEnabled = false
val isScrubbing = true
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -211,7 +213,7 @@
fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
val isEnabled = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
@@ -221,8 +223,8 @@
@Test
fun seekBarJumpAnimation() {
- val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000)
- val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000)
+ val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
+ val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
// Set initial position of progress bar
observer.onChanged(data0)
@@ -241,7 +243,7 @@
observer.animationEnabled = false
val isPlaying = true
val isScrubbing = false
- val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
observer.onChanged(data)
// THEN progress drawable does not animate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var dialog: AlertDialog
+
+ private val flags = mock<FeatureFlagsClassic>()
+ private val onStartRecordingClicked = mock<Runnable>()
+ private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+ private val mediaProjectionConfig: MediaProjectionConfig =
+ MediaProjectionConfig.createConfigForDefaultDisplay()
+ private val appName: String = "testApp"
+ private val hostUid: Int = 12345
+
+ private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+ private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleAppDisabled =
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+ @Before
+ fun setUp() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareFalse() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = false
+ val overrideDisableSingleAppOption = false
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+ }
+
+ @Test
+ fun showDialog_forceShowPartialScreenShareTrue() {
+ // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+ // overrideDisableSingleAppOption = true
+ val overrideDisableSingleAppOption = true
+ setUpAndShowDialog(overrideDisableSingleAppOption)
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val secondOptionText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text1)
+ ?.text
+
+ // check that the first option is single app and enabled
+ assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+ // check that the second option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), secondOptionText)
+ }
+
+ private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+ val delegate =
+ MediaProjectionPermissionDialogDelegate(
+ context,
+ mediaProjectionConfig,
+ {},
+ onStartRecordingClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ hostUid,
+ mediaProjectionMetricsLogger
+ )
+
+ dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+ SystemUIDialog.applyFlags(dialog)
+ SystemUIDialog.setDialogSize(dialog)
+
+ dialog.window?.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ )
+
+ delegate.onCreate(dialog, savedInstanceState = null)
+ dialog.show()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 707a297..d757d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -202,7 +202,7 @@
@Test
fun testSeekBarTrackingStarted() {
whenever(brightnessSliderView.value).thenReturn(42)
- val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
@@ -220,7 +220,7 @@
@Test
fun testSeekBarTrackingStopped() {
whenever(brightnessSliderView.value).thenReturn(23)
- val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
+ val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH
mController.onViewAttached()
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c772ee2..8a22f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,7 +63,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -197,16 +196,8 @@
() -> sceneInteractor);
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 72c52ec..f582402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -41,7 +41,6 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -50,7 +49,6 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -223,18 +221,9 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
-
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8fd9c80..fb105e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,9 +35,9 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -133,14 +133,7 @@
shadeRepository,
{ kosmos.sceneInteractor },
)
- val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- testScope.backgroundScope,
- keyguardTransitionRepository,
- { keyguardInteractor },
- { fromLockscreenTransitionInteractor },
- { fromPrimaryBouncerTransitionInteractor }
- )
+ val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 32c727c..ff882b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -69,7 +69,10 @@
val kosmos =
testKosmos().apply {
- fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ }
}
init {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
new file mode 100644
index 0000000..913759f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
+ private lateinit var repository: UserAwareSecureSettingsRepository
+
+ @Before
+ fun setup() {
+ repository = UserAwareSecureSettingsRepositoryImpl(
+ secureSettings,
+ userRepository,
+ dispatcher,
+ )
+ userRepository.setUserInfos(USER_INFOS)
+ setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+ setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForCurrentUser() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+
+ assertThat(enabled).isTrue()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+ runCurrent()
+
+ setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+ assertThat(enabled).containsExactly(true, false).inOrder()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+ assertThat(enabled).isFalse()
+ }
+ }
+
+ private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
+ secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+ }
+
+ private companion object {
+ const val SETTING_NAME = "SETTING_NAME"
+ val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 1d428c8..d45a9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -95,11 +95,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -107,7 +105,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -428,17 +425,8 @@
shadeRepository,
() -> sceneInteractor);
- FakeKeyguardTransitionRepository keyguardTransitionRepository =
- new FakeKeyguardTransitionRepository();
-
KeyguardTransitionInteractor keyguardTransitionInteractor =
- new KeyguardTransitionInteractor(
- mTestScope.getBackgroundScope(),
- keyguardTransitionRepository,
- () -> keyguardInteractor,
- () -> mFromLockscreenTransitionInteractor,
- () -> mFromPrimaryBouncerTransitionInteractor);
- CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
+ mKosmos.getKeyguardTransitionInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 85a233fd..534f773 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -20,6 +20,7 @@
import com.android.keyguard.KeyguardClockSwitch.ClockSize
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import org.mockito.Mockito
class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepository {
private val _clockSize = MutableStateFlow(LARGE)
@@ -42,6 +44,9 @@
private val _currentClock = MutableStateFlow(null)
override val currentClock = _currentClock
+
+ private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
+ override val previewClock: StateFlow<ClockController> = _previewClock
override val clockEventController: ClockEventController
get() = mock()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..da5cd67
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.fromAodTransitionInteractor by
+ Kosmos.Fixture {
+ FromAodTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = testScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..25fc67a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.fromGoneTransitionInteractor by
+ Kosmos.Fixture {
+ FromGoneTransitionInteractor(
+ transitionRepository = fakeKeyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
index a646bc6..c9c17d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
@@ -19,6 +19,7 @@
import android.content.applicationContext
import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
var Kosmos.keyguardSurfaceBehindInteractor by
Kosmos.Fixture {
@@ -30,5 +31,6 @@
inWindowLauncherUnlockAnimationInteractor
},
swipeToDismissInteractor = swipeToDismissInteractor,
+ notificationLaunchInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index e4d115e..0c38fd9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -30,5 +30,6 @@
fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
fromPrimaryBouncerTransitionInteractor =
Lazy { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index 0207280..d84988d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
val Kosmos.windowManagerLockscreenVisibilityInteractor by
Kosmos.Fixture {
@@ -26,5 +27,6 @@
surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d376f12..24bb9c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -38,6 +38,9 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
new file mode 100644
index 0000000..5dc0333
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.shadeBackActionInteractor by
+ Kosmos.Fixture {
+ ShadeBackActionInteractorImpl(
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ )
+ }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
similarity index 60%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
index a368de2..5638cfc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.0N
+ * 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,
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.model
+package com.android.systemui.statusbar.notification.data.repository
-data class PasskeyUiModel(
- val name: String,
- val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.notificationLaunchAnimationRepository by
+ Kosmos.Fixture { NotificationLaunchAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..0d84bab
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository
+
+val Kosmos.notificationLaunchAnimationInteractor by
+ Kosmos.Fixture {
+ NotificationLaunchAnimationInteractor(
+ repository = notificationLaunchAnimationRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 549a775..30d4105 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,13 +19,16 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +44,9 @@
shadeInteractor = shadeInteractor,
communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
new file mode 100644
index 0000000..5054e29
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
+
+ private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
+
+ override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
+ return settings.map { it.getOrDefault(name, defaultValue) }
+ }
+
+ fun setBoolSettingForActiveUser(name: String, value: Boolean) {
+ settings.value = settings.value.toMutableMap().apply { this[name] = value }
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
similarity index 62%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
index a368de2..94b2bdf 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.0N
+ * 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,
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.model
+package com.android.systemui.util.settings
-data class PasskeyUiModel(
- val name: String,
- val email: String,
-)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userAwareSecureSettingsRepository by
+ Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f68baf5..592c6f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -30,11 +30,11 @@
private final List<FlashlightListener> callbacks = new ArrayList<>();
@VisibleForTesting
- public boolean isAvailable;
+ public boolean isAvailable = true;
@VisibleForTesting
- public boolean isEnabled;
+ public boolean isEnabled = false;
@VisibleForTesting
- public boolean hasFlashlight;
+ public boolean hasFlashlight = true;
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
@@ -52,16 +52,26 @@
callbacks.forEach(FlashlightListener::onFlashlightError);
}
+ /**
+ * Used to decide if tile should be shown or gone
+ * @return available/unavailable
+ */
@Override
public boolean hasFlashlight() {
return hasFlashlight;
}
+ /**
+ * @param newState active/inactive
+ */
@Override
public void setFlashlight(boolean newState) {
callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
+ /**
+ * @return temporary availability
+ */
@Override
public boolean isAvailable() {
return isAvailable;
@@ -76,6 +86,9 @@
public void addCallback(FlashlightListener listener) {
super.addCallback(listener);
callbacks.add(listener);
+
+ listener.onFlashlightAvailabilityChanged(isAvailable());
+ listener.onFlashlightChanged(isEnabled());
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 78f07e4..a11cf8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -818,7 +818,7 @@
}
// Don't need to add the embedded hierarchy windows into the accessibility windows list.
- if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+ if (isEmbeddedHierarchyWindowsLocked(windowId)) {
return null;
}
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
@@ -866,21 +866,6 @@
return reportedWindow;
}
- private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
- final IBinder leashToken = mWindowIdMap.get(windowId);
- if (leashToken == null) {
- return false;
- }
-
- for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
- if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
- return true;
- }
- }
-
- return false;
- }
-
private int getTypeForWindowManagerWindowType(int windowType) {
switch (windowType) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
@@ -1490,7 +1475,7 @@
* @return The windowId of the parent window, or self if no parent exists
*/
public int resolveParentWindowIdLocked(int windowId) {
- final IBinder token = getTokenLocked(windowId);
+ final IBinder token = getLeashTokenLocked(windowId);
if (token == null) {
return windowId;
}
@@ -2095,7 +2080,7 @@
* @param windowId The windowID.
* @return The token, or {@code NULL} if this windowID doesn't exist
*/
- IBinder getTokenLocked(int windowId) {
+ IBinder getLeashTokenLocked(int windowId) {
return mWindowIdMap.get(windowId);
}
@@ -2124,6 +2109,23 @@
}
/**
+ * Checks if the window is embedded into another window so that the window should be excluded
+ * from the exposed accessibility windows, and the node tree should be embedded in the host.
+ */
+ boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+ if (mHostEmbeddedMap.size() == 0) {
+ return false;
+ }
+
+ final IBinder leashToken = getLeashTokenLocked(windowId);
+ if (leashToken == null) {
+ return false;
+ }
+
+ return mHostEmbeddedMap.containsKey(leashToken);
+ }
+
+ /**
* Checks if the window belongs to a proxy display and if so clears the focused window id.
* @param focusClearedWindowId the cleared window id.
* @return true if an observer is proxy-ed and has cleared its focused window id.
diff --git a/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
new file mode 100644
index 0000000..69c7b29
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.ButtonBarLayout;
+
+/** An extension of {@link ButtonBarLayout} for use in Autofill bottom sheets. */
+public class BottomSheetButtonBarLayout extends ButtonBarLayout {
+
+ public BottomSheetButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final View spacer = findViewById(R.id.autofill_button_bar_spacer);
+ if (spacer == null) {
+ return;
+ }
+ if (isStacked()) {
+ spacer.getLayoutParams().width = 0;
+ spacer.getLayoutParams().height =
+ getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_height);
+ setGravity(Gravity.CENTER_VERTICAL | Gravity.END);
+ } else {
+ spacer.getLayoutParams().width =
+ getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_width);
+ spacer.getLayoutParams().height = 0;
+ setGravity(Gravity.CENTER_VERTICAL);
+ }
+ }
+
+ private boolean isStacked() {
+ return getOrientation() == LinearLayout.VERTICAL;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index b2716ec..d580f3a 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,6 +15,7 @@
*/
package com.android.server.autofill.ui;
+import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static com.android.server.autofill.Helper.paramsToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -31,6 +32,7 @@
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetFieldFilter;
import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
import android.text.TextUtils;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
@@ -79,6 +81,7 @@
com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill;
+ private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
private static final TypedValue sTempTypedValue = new TypedValue();
@@ -211,7 +214,11 @@
if (sVerbose) {
Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
}
- } else {
+ } else if (Flags.autofillCredmanIntegration() && (
+ (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+ mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
+ }
+ else {
mVisibleDatasetsMaxCount = mContext.getResources()
.getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 6a63b3a..71f2b9e 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -32,4 +32,13 @@
description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
bug: "320633449"
is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_increase_datatypes_for_agent_logging"
+ namespace: "onboarding"
+ description: "Increase the number of a supported datatypes that an agent can define for its "
+ "logger."
+ bug: "296844513"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9d9e7c9..7979936 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,8 +16,8 @@
package com.android.server;
-import static android.os.Flags.stateOfHealthPublic;
import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
+import static android.os.Flags.stateOfHealthPublic;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
@@ -81,6 +81,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -157,6 +158,12 @@
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
private int mLastCharingState;
+ /**
+ * The last seen charging policy. This requires the
+ * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
+ * included in the ACTION_BATTERY_CHANGED intent extras.
+ */
+ private int mLastChargingPolicy;
private int mSequence = 1;
@@ -197,6 +204,9 @@
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
+ private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
+ mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
+
private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -527,6 +537,11 @@
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
+ if (force || mHealthInfo.chargingPolicy != mLastChargingPolicy) {
+ mLastChargingPolicy = mHealthInfo.chargingPolicy;
+ mHandler.post(this::notifyChargingPolicyChanged);
+ }
+
if (force
|| (mHealthInfo.batteryStatus != mLastBatteryStatus
|| mHealthInfo.batteryHealth != mLastBatteryHealth
@@ -827,6 +842,17 @@
mLastBatteryLevelChangedSentMs = SystemClock.elapsedRealtime();
}
+ private void notifyChargingPolicyChanged() {
+ final int newPolicy;
+ synchronized (mLock) {
+ newPolicy = mLastChargingPolicy;
+ }
+ for (BatteryManagerInternal.ChargingPolicyChangeListener listener
+ : mChargingPolicyChangeListeners) {
+ listener.onChargingPolicyChanged(newPolicy);
+ }
+ }
+
// TODO: Current code doesn't work since "--unplugged" flag in BSS was purposefully removed.
private void logBatteryStatsLocked() {
IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
@@ -1220,6 +1246,8 @@
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
+ pw.println(" Charging state: " + mHealthInfo.chargingState);
+ pw.println(" Charging policy: " + mHealthInfo.chargingPolicy);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -1452,6 +1480,19 @@
}
@Override
+ public void registerChargingPolicyChangeListener(
+ BatteryManagerInternal.ChargingPolicyChangeListener listener) {
+ mChargingPolicyChangeListeners.add(listener);
+ }
+
+ @Override
+ public int getChargingPolicy() {
+ synchronized (mLock) {
+ return mLastChargingPolicy;
+ }
+ }
+
+ @Override
public int getInvalidCharger() {
synchronized (mLock) {
return mInvalidCharger;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ea1b0f5..e7fae24 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -48,6 +48,7 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -3597,6 +3598,13 @@
return mInternalStorageSize;
}
+ @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Override
+ public int getInternalStorageRemainingLifetime() throws RemoteException {
+ super.getInternalStorageRemainingLifetime_enforcePermission();
+ return mVold.getStorageRemainingLifetime();
+ }
+
/**
* Enforces that the caller is the {@link ExternalStorageService}
*
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c37e619..d1c8c30 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -152,6 +152,11 @@
private int mActiveIndex;
/**
+ * True if the broadcast actively being dispatched to this process was re-enqueued previously.
+ */
+ private boolean mActiveReEnqueued;
+
+ /**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
@@ -312,6 +317,7 @@
final SomeArgs broadcastArgs = SomeArgs.obtain();
broadcastArgs.arg1 = record;
broadcastArgs.argi1 = recordIndex;
+ broadcastArgs.argi2 = 1;
getQueueForBroadcast(record).addFirst(broadcastArgs);
onBroadcastEnqueued(record, recordIndex);
}
@@ -609,6 +615,7 @@
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
+ mActiveReEnqueued = (next.argi2 == 1);
mActiveCountSinceIdle++;
mActiveAssumedDeliveryCountSinceIdle +=
(mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
@@ -624,12 +631,21 @@
public void makeActiveIdle() {
mActive = null;
mActiveIndex = 0;
+ mActiveReEnqueued = false;
mActiveCountSinceIdle = 0;
mActiveAssumedDeliveryCountSinceIdle = 0;
mActiveViaColdStart = false;
invalidateRunnableAt();
}
+ public boolean wasActiveBroadcastReEnqueued() {
+ // If the flag is not enabled, treat as if the broadcast was never re-enqueued.
+ if (!Flags.avoidRepeatedBcastReEnqueues()) {
+ return false;
+ }
+ return mActiveReEnqueued;
+ }
+
/**
* Update summary statistics when the given record has been enqueued.
*/
@@ -1476,6 +1492,9 @@
if (runningOomAdjusted) {
pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
}
+ if (mActiveReEnqueued) {
+ pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued);
+ }
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index db0f03f..98263df 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -542,8 +542,8 @@
updateOomAdj |= queue.runningOomAdjusted;
try {
completed = scheduleReceiverWarmLocked(queue);
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
completed = true;
}
} else {
@@ -586,7 +586,12 @@
private void clearInvalidPendingColdStart() {
logw("Clearing invalid pending cold start: " + mRunningColdStart);
- mRunningColdStart.reEnqueueActiveBroadcast();
+ if (mRunningColdStart.wasActiveBroadcastReEnqueued()) {
+ finishReceiverActiveLocked(mRunningColdStart, BroadcastRecord.DELIVERY_FAILURE,
+ "invalid start with re-enqueued broadcast");
+ } else {
+ mRunningColdStart.reEnqueueActiveBroadcast();
+ }
demoteFromRunningLocked(mRunningColdStart);
clearRunningColdStart();
enqueueUpdateRunningList();
@@ -613,19 +618,26 @@
}
}
- private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
+ private void finishOrReEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
- final BroadcastRecord record = queue.getActive();
- final int index = queue.getActiveIndex();
- setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
- BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
- queue.reEnqueueActiveBroadcast();
+ if (queue.wasActiveBroadcastReEnqueued()) {
+ // If the broadcast was already re-enqueued previously, finish it to avoid repeated
+ // delivery attempts
+ finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "re-enqueued broadcast delivery failed");
+ } else {
+ final BroadcastRecord record = queue.getActive();
+ final int index = queue.getActiveIndex();
+ setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
+ BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
+ queue.reEnqueueActiveBroadcast();
+ }
}
@Override
public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app)
- throws BroadcastDeliveryFailedException {
+ throws BroadcastRetryException {
if (DEBUG_BROADCAST) {
logv("Process " + app + " is attached");
}
@@ -653,8 +665,8 @@
if (scheduleReceiverWarmLocked(queue)) {
demoteFromRunningLocked(queue);
}
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
demoteFromRunningLocked(queue);
throw e;
}
@@ -983,7 +995,7 @@
@CheckResult
@GuardedBy("mService")
private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue)
- throws BroadcastDeliveryFailedException {
+ throws BroadcastRetryException {
checkState(queue.isActive(), "isActive");
final int cookie = traceBegin("scheduleReceiverWarmLocked");
@@ -1065,7 +1077,7 @@
*/
@CheckResult
private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
- @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException {
+ @NonNull BroadcastRecord r, int index) throws BroadcastRetryException {
final ProcessRecord app = queue.app;
final Object receiver = r.receivers.get(index);
@@ -1157,7 +1169,7 @@
// to try redelivering the broadcast to this receiver.
if (receiver instanceof ResolveInfo) {
cancelDeliveryTimeoutLocked(queue);
- throw new BroadcastDeliveryFailedException(e);
+ throw new BroadcastRetryException(e);
}
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
"remote app");
@@ -1316,8 +1328,8 @@
demoteFromRunningLocked(queue);
return true;
}
- } catch (BroadcastDeliveryFailedException e) {
- reEnqueueActiveBroadcast(queue);
+ } catch (BroadcastRetryException e) {
+ finishOrReEnqueueActiveBroadcast(queue);
demoteFromRunningLocked(queue);
return true;
}
diff --git a/services/core/java/com/android/server/am/BroadcastRetryException.java b/services/core/java/com/android/server/am/BroadcastRetryException.java
new file mode 100644
index 0000000..8bd6664
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastRetryException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+/**
+ * Exception to represent that broadcast delivery failed and we should try redelivering it.
+ */
+public class BroadcastRetryException extends BroadcastDeliveryFailedException {
+ public BroadcastRetryException(String name) {
+ super(name);
+ }
+
+ public BroadcastRetryException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 2aed847..0f75ad48 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -31,7 +31,7 @@
30017 am_low_memory (Num Processes|1|1)
# Kill a process to reclaim memory.
-30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2)
# Discard an undelivered serialized broadcast (timeout/ANR/crash)
30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 862542e..7d82f0c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1499,8 +1499,12 @@
&& !uidRec.isCurAllowListed()) {
// UID is now in the background (and not on the temp allowlist). Was it
// previously in the foreground (or on the temp allowlist)?
+ // Or, it wasn't in the foreground / allowlist, but its last background
+ // timestamp is also 0, this means it's never been in the
+ // foreground / allowlist since it's born at all.
if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
- || uidRec.isSetAllowListed()) {
+ || uidRec.isSetAllowListed()
+ || uidRec.getLastBackgroundTime() == 0) {
uidRec.setLastBackgroundTime(nowElapsed);
if (mService.mDeterministicUidIdle
|| !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
@@ -1526,6 +1530,7 @@
uidRec.setIdle(false);
}
uidRec.setLastBackgroundTime(0);
+ uidRec.setLastIdleTime(0);
}
final boolean wasCached = uidRec.getSetProcState()
> ActivityManager.PROCESS_STATE_RECEIVER;
@@ -3700,12 +3705,14 @@
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
final long bgTime = uidRec.getLastBackgroundTime();
- if (bgTime > 0 && !uidRec.isIdle()) {
+ final long idleTime = uidRec.getLastIdleTime();
+ if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
if (bgTime <= maxBgTime) {
EventLogTags.writeAmUidIdle(uidRec.getUid());
synchronized (mProcLock) {
uidRec.setIdle(true);
uidRec.setSetIdle(true);
+ uidRec.setLastIdleTime(nowElapsed);
}
mService.doStopUidLocked(uidRec.getUid(), uidRec);
} else {
diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java
index 1a692df..ac96bdc 100644
--- a/services/core/java/com/android/server/am/PhantomProcessRecord.java
+++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java
@@ -105,6 +105,11 @@
}
}
+ public long getRss(int pid) {
+ long[] rss = Process.getRss(pid);
+ return (rss != null && rss.length > 0) ? rss[0] : 0;
+ }
+
@GuardedBy("mLock")
void killLocked(String reason, boolean noisy) {
if (!mKilled) {
@@ -115,7 +120,7 @@
}
if (mPid > 0) {
EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
- mPid, mProcessName, mAdj, reason);
+ mPid, mProcessName, mAdj, reason, getRss(mPid));
if (!Process.supportsPidFd()) {
onProcDied(false);
} else {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index de6f034..d23d9fb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1217,6 +1217,11 @@
}
}
+ public long getRss(int pid) {
+ long[] rss = Process.getRss(pid);
+ return (rss != null && rss.length > 0) ? rss[0] : 0;
+ }
+
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
@@ -1260,7 +1265,7 @@
if (mPid > 0) {
mService.mProcessList.noteAppKill(this, reasonCode, subReason, description);
EventLog.writeEvent(EventLogTags.AM_KILL,
- userId, mPid, processName, mState.getSetAdj(), reason);
+ userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid));
Process.killProcessQuiet(mPid);
killProcessGroupIfNecessaryLocked(asyncKPG);
} else {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 4329afc..45fd470 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -66,6 +66,9 @@
private long mLastBackgroundTime;
@CompositeRWLock({"mService", "mProcLock"})
+ private long mLastIdleTime;
+
+ @CompositeRWLock({"mService", "mProcLock"})
private boolean mEphemeral;
@CompositeRWLock({"mService", "mProcLock"})
@@ -255,6 +258,16 @@
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getLastIdleTime() {
+ return mLastIdleTime;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ void setLastIdleTime(long lastActiveTime) {
+ mLastIdleTime = lastActiveTime;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
boolean isEphemeral() {
return mEphemeral;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2b81dbc..96c6be8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -993,17 +993,20 @@
/**
* Stops a single User. This can also trigger locking user data out depending on device's
* config ({@code mDelayUserDataLocking}) and arguments.
- * User will be unlocked when
- * - {@code mDelayUserDataLocking} is not set.
- * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+ *
+ * In the default configuration for most device and users, users will be locked when stopping.
+ * User will remain unlocked only if all the following are true
+ * <li> {@link #canDelayDataLockingForUser(int)} (based on mDelayUserDataLocking) is true
+ * <li> the parameter {@code allowDelayedLocking} is true
+ * <li> {@code keyEvictedCallback} is null
* -
*
* @param userId User Id to stop and lock the data.
* @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
* later when number of unlocked users reaches
* {@code mMaxRunnngUsers}. Note that this is respected only when
- * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
- * null. Otherwise the user will be locked.
+ * delayed locking is enabled for this user and {@keyEvictedCallback}
+ * is null. Otherwise the user nonetheless will be locked.
* @param stopUserCallback Callback to notify that user has stopped.
* @param keyEvictedCallback Callback to notify that user has been unlocked.
*/
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 31d9cc9..16dbe18 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -42,3 +42,14 @@
description: "Optimize the service bindings by different policies like skipping oom adjuster"
bug: "318717054"
}
+
+flag {
+ namespace: "backstage_power"
+ name: "avoid_repeated_bcast_re_enqueues"
+ description: "Avoid re-enqueueing a broadcast repeatedly"
+ bug: "319225224"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
new file mode 100644
index 0000000..a923daa
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * This class provides the handler to process biometric operations.
+ */
+public class BiometricHandlerProvider {
+ private static final BiometricHandlerProvider sBiometricHandlerProvider =
+ new BiometricHandlerProvider();
+
+ private final Handler mBiometricsCallbackHandler;
+ private final Handler mFingerprintHandler;
+ private final Handler mFaceHandler;
+
+ /**
+ * @return an instance of {@link BiometricHandlerProvider} which contains the three
+ * threads needed for running biometric operations
+ */
+ public static BiometricHandlerProvider getInstance() {
+ return sBiometricHandlerProvider;
+ }
+
+ private BiometricHandlerProvider() {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
+ mFingerprintHandler = getNewHandler("FingerprintHandler");
+ mFaceHandler = getNewHandler("FaceHandler");
+ }
+
+ /**
+ * @return the handler to process all biometric callback operations
+ */
+ public synchronized Handler getBiometricCallbackHandler() {
+ return mBiometricsCallbackHandler;
+ }
+
+ /**
+ * @return the handler to process all face related biometric operations
+ */
+ public synchronized Handler getFaceHandler() {
+ return mFaceHandler;
+ }
+
+ /**
+ * @return the handler to process all fingerprint related biometric operations
+ */
+ public synchronized Handler getFingerprintHandler() {
+ return mFingerprintHandler;
+ }
+
+ private Handler getNewHandler(String tag) {
+ if (Flags.deHidl()) {
+ HandlerThread handlerThread = new HandlerThread(tag);
+ handlerThread.start();
+ return new Handler(handlerThread.getLooper());
+ }
+ return new Handler(Looper.getMainLooper());
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 91a68ea..fc948da 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,7 +62,6 @@
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -140,7 +139,7 @@
// The current authentication session, null if idle/done.
@VisibleForTesting
AuthSession mAuthSession;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final BiometricCameraManager mBiometricCameraManager;
@@ -1113,14 +1112,16 @@
* @param context The system server context.
*/
public BiometricService(Context context) {
- this(context, new Injector());
+ this(context, new Injector(), BiometricHandlerProvider.getInstance());
}
@VisibleForTesting
- BiometricService(Context context, Injector injector) {
+ BiometricService(Context context, Injector injector,
+ BiometricHandlerProvider biometricHandlerProvider) {
super(context);
mInjector = injector;
+ mHandler = biometricHandlerProvider.getBiometricCallbackHandler();
mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
mImpl = new BiometricServiceWrapper();
mEnabledOnKeyguardCallbacks = new ArrayList<>();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index d01c268..f469f62 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -39,7 +39,6 @@
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -53,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -124,6 +124,8 @@
private final BiometricContext mBiometricContext;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final BiometricHandlerProvider mBiometricHandlerProvider;
@Nullable
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
@@ -166,8 +168,9 @@
@NonNull BiometricContext biometricContext,
boolean resetLockoutRequiresChallenge) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
- lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
- resetLockoutRequiresChallenge, false /* testHalEnabled */);
+ lockoutResetDispatcher, biometricContext, null /* daemon */,
+ BiometricHandlerProvider.getInstance(), resetLockoutRequiresChallenge,
+ false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@@ -178,7 +181,7 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
@Nullable IFace daemon,
- @NonNull Handler handler,
+ @NonNull BiometricHandlerProvider biometricHandlerProvider,
boolean resetLockoutRequiresChallenge,
boolean testHalEnabled) {
mContext = context;
@@ -187,7 +190,7 @@
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
- mHandler = handler;
+ mHandler = biometricHandlerProvider.getFaceHandler();
} else {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -199,18 +202,12 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
mTestHalEnabled = testHalEnabled;
+ mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initSensors(resetLockoutRequiresChallenge, props);
}
- @NonNull
- private static Handler getHandler() {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
-
private void initAuthenticationBroadcastReceiver() {
new AuthenticationStatsBroadcastReceiver(
mContext,
@@ -622,15 +619,29 @@
@Override
public void onClientStarted(
BaseClientMonitor clientMonitor) {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+ requestId));
+ } else {
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ }
}
@Override
public void onClientFinished(
BaseClientMonitor clientMonitor,
boolean success) {
- mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, client.wasAuthSuccessful());
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ client.wasAuthSuccessful()));
+ } else {
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId),
+ sensorId, requestId, client.wasAuthSuccessful());
+ }
}
});
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0388d1..fd938ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,7 +46,6 @@
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -60,6 +59,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -129,11 +129,12 @@
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final BiometricHandlerProvider mBiometricHandlerProvider;
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
// TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@Nullable private ISidefpsController mSidefpsController;
- private AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
@Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -175,8 +176,8 @@
boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
- false /* testHalEnabled */);
+ null /* daemon */, BiometricHandlerProvider.getInstance(),
+ resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */);
}
@VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -187,7 +188,7 @@
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
@Nullable IFingerprint daemon,
- @NonNull Handler handler,
+ @NonNull BiometricHandlerProvider biometricHandlerProvider,
boolean resetLockoutRequiresHardwareAuthToken,
boolean testHalEnabled) {
mContext = context;
@@ -196,7 +197,7 @@
mHalInstanceName = halInstanceName;
mFingerprintSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
- mHandler = handler;
+ mHandler = biometricHandlerProvider.getFingerprintHandler();
} else {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -207,18 +208,12 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
mTestHalEnabled = testHalEnabled;
+ mBiometricHandlerProvider = biometricHandlerProvider;
initAuthenticationBroadcastReceiver();
initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
}
- @NonNull
- private static Handler getHandler() {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
-
private void initAuthenticationBroadcastReceiver() {
new AuthenticationStatsBroadcastReceiver(
mContext,
@@ -620,7 +615,13 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
mBiometricStateCallback.onClientStarted(clientMonitor);
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+ requestId));
+ } else {
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+ }
}
@Override
@@ -632,8 +633,15 @@
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
- mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, success);
+ if (Flags.deHidl()) {
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ success));
+ } else {
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
+ }
}
});
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6fa737d..91706cf 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4895,14 +4895,14 @@
continue;
}
notificationsRapidlyCleared = notificationsRapidlyCleared
- || isNotificationRecent(r);
+ || isNotificationRecent(r.getUpdateTimeMs());
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
r.getSbn().getId(), userId, reason);
}
} else {
for (NotificationRecord notificationRecord : mNotificationList) {
- if (isNotificationRecent(notificationRecord)) {
+ if (isNotificationRecent(notificationRecord.getUpdateTimeMs())) {
notificationsRapidlyCleared = true;
break;
}
@@ -4928,14 +4928,6 @@
}
}
- private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) {
- if (!rapidClearNotificationsByListenerAppOpEnabled()) {
- return false;
- }
- return notificationRecord.getFreshnessMs(System.currentTimeMillis())
- < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
- }
-
/**
* Handle request from an approved listener to re-enable itself.
*
@@ -5059,12 +5051,11 @@
@Override
public void snoozeNotificationUntilContextFromListener(INotificationListener token,
String key, String snoozeCriterionId) {
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
- }
+ snoozeNotificationInt(callingUid, token, key, SNOOZE_UNTIL_UNSPECIFIED,
+ snoozeCriterionId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5078,12 +5069,10 @@
@Override
public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
long duration) {
+ final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mNotificationLock) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- snoozeNotificationInt(key, duration, null, info);
- }
+ snoozeNotificationInt(callingUid, token, key, duration, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -10342,16 +10331,22 @@
}
}
- void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
- ManagedServiceInfo listener) {
- if (listener == null) {
- return;
- }
- String listenerName = listener.component.toShortString();
- if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
- return;
- }
+ void snoozeNotificationInt(int callingUid, INotificationListener token, String key,
+ long duration, String snoozeCriterionId) {
+ final String packageName;
+ final long notificationUpdateTimeMs;
+
synchronized (mNotificationLock) {
+ final ManagedServiceInfo listener = mListeners.checkServiceTokenLocked(token);
+ if (listener == null) {
+ return;
+ }
+ packageName = listener.component.getPackageName();
+ String listenerName = listener.component.toShortString();
+ if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
+ return;
+ }
+
final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
if (r == null) {
return;
@@ -10359,14 +10354,20 @@
if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
return;
}
+ notificationUpdateTimeMs = r.getUpdateTimeMs();
+
+ if (DBG) {
+ Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
+ snoozeCriterionId, listenerName));
+ }
+ // Needs to post so that it can cancel notifications not yet enqueued.
+ mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
}
- if (DBG) {
- Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
- snoozeCriterionId, listenerName));
+ if (isNotificationRecent(notificationUpdateTimeMs)) {
+ mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ callingUid, packageName, /* attributionTag= */ null, /* message= */ null);
}
- // Needs to post so that it can cancel notifications not yet enqueued.
- mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
}
void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) {
@@ -10378,6 +10379,14 @@
handleSavePolicyFile();
}
+ private boolean isNotificationRecent(long notificationUpdateTimeMs) {
+ if (!rapidClearNotificationsByListenerAppOpEnabled()) {
+ return false;
+ }
+ return System.currentTimeMillis() - notificationUpdateTimeMs
+ < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
+ }
+
@GuardedBy("mNotificationLock")
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles, int mustNotHaveFlags) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43328fc..984a629 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1676,6 +1676,8 @@
private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+ appMarketIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return buildIntentSenderForUser(appMarketIntent, user);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 260fdb9..c860b5a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -962,6 +962,21 @@
getInstallSource().mInstallerPackageName, mInstallerUid);
}
+ private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
+ final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+ return false;
+ }
+ String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
+ if (emergencyInstaller == null || !ArrayUtils.contains(
+ snapshot.getPackagesForUid(mInstallerUid),
+ emergencyInstaller)) {
+ return false;
+ }
+ return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
+ mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ }
+
private static final int USER_ACTION_NOT_NEEDED = 0;
private static final int USER_ACTION_REQUIRED = 1;
private static final int USER_ACTION_PENDING_APK_PARSING = 2;
@@ -1046,6 +1061,8 @@
final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
getInstallerPackageName());
final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
+ final boolean isEmergencyInstall =
+ isEmergencyInstallerEnabled(packageName, snapshot);
final boolean isPermissionGranted = isInstallPermissionGranted
|| (isUpdatePermissionGranted && isUpdate)
|| (isSelfUpdatePermissionGranted && isSelfUpdate)
@@ -1062,7 +1079,7 @@
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
- || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
+ || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
if (noUserActionNecessary) {
return userActionNotTypicallyNeededResponse;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 04e8205..5575f52 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5044,6 +5044,10 @@
pw.print(prefix); pw.print(" updatableSystem=false");
pw.println();
}
+ if (pkg.getEmergencyInstaller() != null) {
+ pw.print(prefix); pw.print(" emergencyInstaller=");
+ pw.println(pkg.getEmergencyInstaller());
+ }
if (pkg.hasPreserveLegacyExternalStorage()) {
pw.print(prefix); pw.print(" hasPreserveLegacyExternalStorage=true");
pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index e9c6aab..b35f9c2 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -209,9 +209,8 @@
if (ret == null) {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
- } else {
- ret.attemptToRestoreIfNeededAndSave();
}
+ ret.attemptToRestoreIfNeededAndSave();
return ret;
}
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index cc26c9b..9159851 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -538,9 +538,10 @@
} else {
if (fileInfo.mUserId != userId) {
// This should be impossible: private app files are always user-specific and
- // can't be accessed from different users.
- throw new IllegalArgumentException("Cannot change userId for '" + path
- + "' from " + fileInfo.mUserId + " to " + userId);
+ // can't be accessed from different users. But it does very occasionally happen
+ // (b/323665257). Ignore such cases - we shouldn't record data from a different
+ // user.
+ return false;
}
// Changing file type (i.e. loading the same file in different ways is possible if
// unlikely. We allow it but ignore it.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7b59759..c2117ea 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2441,11 +2441,11 @@
return false;
}
- if (mStartingData != null) {
+ if (hasStartingWindow()) {
return false;
}
- final WindowState mainWin = findMainWindow();
+ final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
if (mainWin != null && mainWin.mWinAnimator.getShown()) {
// App already has a visible window...why would you want a starting window?
return false;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5806c0..68dade0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3020,8 +3020,10 @@
return false;
}
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- if (!isAnimating(TRANSITION | PARENTS)) {
+ // If a hide animation is applied, then let onAnimationFinished
+ // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false
+ // to commit invisible immediately.
+ if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) {
doAnimation = false;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 09c4f7c..6428591 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,10 +499,6 @@
}
void applyEnterAnimationLocked() {
- if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
- // It's unnecessary to play enter animation below starting window.
- return;
- }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
@@ -513,8 +509,10 @@
// We don't apply animation for application main window here since this window type
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
- // WallpaperController should handle it.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
+ // WallpaperController should handle it. Also skip play enter animation for the window
+ // below starting window.
+ if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b79d20a..c55d709 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,6 +48,7 @@
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.credentials.CredentialManager;
+import android.credentials.flags.Flags;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.graphics.GraphicsStatsService;
@@ -2795,9 +2796,14 @@
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
if (credentialManagerEnabled) {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ if(isWatch &&
+ !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+ Slog.d(TAG, "CredentialManager disabled on wear.");
+ } else {
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ }
} else {
Slog.d(TAG, "CredentialManager disabled.");
}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 50e426c..68038fa 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -49,7 +49,6 @@
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
-import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -314,7 +313,7 @@
Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
return;
}
- aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+ aml.setBatchDexoptStartCallback(Runnable::run,
(snapshot, reason, defaultPackages, builder, passedSignal) -> {
traceOnDex2oatStart();
});
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index ef9c62f..cfe701f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -272,7 +272,8 @@
AndroidPackage::hasPreserveLegacyExternalStorage,
AndroidPackage::hasRequestForegroundServiceExemption,
AndroidPackage::hasRequestRawExternalStorageAccess,
- AndroidPackage::isUpdatableSystem
+ AndroidPackage::isUpdatableSystem,
+ AndroidPackage::getEmergencyInstaller
)
override fun extraParams() = listOf(
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index d928306..9f97551 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
"testng",
"compatibility-device-util-axt",
"flag-junit",
+ "am_flags_lib",
],
libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index f875f65..fb47aa8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -38,6 +38,8 @@
import android.os.HandlerThread;
import android.os.TestLooperManager;
import android.os.UserHandle;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.SparseArray;
@@ -93,6 +95,9 @@
.spyStatic(ProcessList.class)
.build();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
@Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 75409d9..3f6117b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -75,6 +75,8 @@
import android.os.PowerExemptionManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.proto.ProtoOutputStream;
@@ -135,6 +137,17 @@
ProcessStartBehavior.SUCCESS);
/**
+ * Map of processes to behaviors indicating how the new processes should behave as needed
+ * by the tests.
+ */
+ private ArrayMap<String, ProcessBehavior> mNewProcessBehaviors = new ArrayMap<>();
+
+ /**
+ * Map of processes to behaviors indicating how the new process starts should result in.
+ */
+ private ArrayMap<String, ProcessStartBehavior> mNewProcessStartBehaviors = new ArrayMap<>();
+
+ /**
* Collection of all active processes during current test run.
*/
private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
@@ -161,15 +174,17 @@
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
assertHealth();
- final ProcessStartBehavior behavior = mNextProcessStartBehavior
- .getAndSet(ProcessStartBehavior.SUCCESS);
+ final String processName = invocation.getArgument(0);
+ final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault(
+ processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS));
if (behavior == ProcessStartBehavior.FAIL_NULL) {
return null;
}
- final String processName = invocation.getArgument(0);
final ApplicationInfo ai = invocation.getArgument(1);
+ final ProcessBehavior processBehavior = mNewProcessBehaviors.getOrDefault(
+ processName, ProcessBehavior.NORMAL);
final ProcessRecord res = makeActiveProcessRecord(ai, processName,
- ProcessBehavior.NORMAL, UnaryOperator.identity());
+ processBehavior, UnaryOperator.identity());
final ProcessRecord deliverRes;
switch (behavior) {
case SUCCESS_PREDECESSOR:
@@ -274,6 +289,8 @@
assertEquals(app.toShortString(), ProcessList.SCHED_GROUP_UNDEFINED,
mQueue.getPreferredSchedulingGroupLocked(app));
}
+ mNewProcessBehaviors.clear();
+ mNewProcessStartBehaviors.clear();
}
@Override
@@ -955,6 +972,40 @@
}
/**
+ * Verify that we handle manifest receivers in a process that always
+ * responds with {@link DeadObjectException} even after restarting.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+ public void testRepeatedDead_Manifest() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ mNewProcessBehaviors.put(PACKAGE_GREEN, ProcessBehavior.DEAD);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0))));
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ waitForIdle();
+
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ // Modern queue always kills the target process when broadcast delivery fails, where as
+ // the legacy queue leaves the process killing task to AMS
+ if (mImpl == Impl.MODERN) {
+ assertNull(receiverGreenApp);
+ }
+ final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyScheduleReceiver(receiverBlueApp, airplane);
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ verifyScheduleReceiver(receiverYellowApp, timezone);
+ }
+
+ /**
* Verify that we handle the system failing to start a process.
*/
@Test
@@ -1142,6 +1193,49 @@
}
/**
+ * Verify that when BroadcastQueue doesn't get notified when a process gets killed repeatedly,
+ * it doesn't get stuck.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+ public void testRepeatedKillWithoutNotify() throws Exception {
+ // Legacy queue does not handle repeated kills that don't get notified.
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+ mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+ withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+ waitForIdle();
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE));
+
+ // Modern queue always kills the target process when broadcast delivery fails, where as
+ // the legacy queue leaves the process killing task to AMS
+ if (mImpl == Impl.MODERN) {
+ assertNull(receiverGreenApp);
+ }
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+ verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+ }
+
+ /**
* Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing
* process due to pending sync binder transactions, is delivered as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 3355a6c..fab7610 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -44,6 +44,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -65,7 +67,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
+import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
+import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -89,6 +93,8 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -107,6 +113,8 @@
@Mock
private ActivityManagerInternal mActivityMangerInternal;
@Mock
+ private BatteryManagerInternal mBatteryManagerInternal;
+ @Mock
private Context mContext;
@Mock
private PackageManagerInternal mPackageManagerInternal;
@@ -114,6 +122,8 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -137,7 +147,7 @@
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(AppStandbyInternal.class))
.when(() -> LocalServices.getService(AppStandbyInternal.class));
- doReturn(mock(BatteryManagerInternal.class))
+ doReturn(mBatteryManagerInternal)
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -186,8 +196,17 @@
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
+ setChargingPolicy(Integer.MIN_VALUE);
+
+ ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
+ ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
+
mService = new TestJobSchedulerService(mContext);
mService.waitOnAsyncLoadingForTesting();
+
+ verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
+ chargingPolicyChangeListenerCaptor.capture());
+ mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
}
@After
@@ -1718,6 +1737,127 @@
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
}
+ @Test
+ public void testBatteryStateTrackerRegistersForImportantIntents() {
+ verify(mContext).registerReceiver(any(), ArgumentMatchers.argThat(filter -> true
+ && filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)
+ && filter.hasAction(Intent.ACTION_BATTERY_LEVEL_CHANGED)
+ && filter.hasAction(Intent.ACTION_BATTERY_LOW)
+ && filter.hasAction(Intent.ACTION_BATTERY_OKAY)
+ && filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+ && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+ }
+
+ @Test
+ public void testIsCharging_standardChargingIntent() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ Intent chargingIntent = new Intent(BatteryManager.ACTION_CHARGING);
+ Intent dischargingIntent = new Intent(BatteryManager.ACTION_DISCHARGING);
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, chargingIntent);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_batteryTooLow() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+
+ setBatteryLevel(70);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_chargeBelowThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(5);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 5; level < 80; ++level) {
+ setBatteryLevel(level);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_dischargeAboveThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(80);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 80; level > 60; --level) {
+ setBatteryLevel(level);
+ assertEquals(level >= 70, tracker.isCharging());
+ assertEquals(level >= 70, mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_notPluggedIn() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_DISCONNECTED));
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(50);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(70);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(95);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(100);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
/** Tests that rare job batching works as expected. */
@Test
public void testConnectivityJobBatching() {
@@ -2257,4 +2397,17 @@
assertFalse(mService.getPendingJobQueue().contains(job2b));
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
}
+
+ private void setBatteryLevel(int level) {
+ doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
+ mService.mBatteryStateTracker
+ .onReceive(mContext, new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED));
+ }
+
+ private void setChargingPolicy(int policy) {
+ doReturn(policy).when(mBatteryManagerInternal).getChargingPolicy();
+ if (mChargingPolicyChangeListener != null) {
+ mChargingPolicyChangeListener.onChargingPolicyChanged(policy);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index d4ef647..ea937de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -25,14 +25,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.BatteryManagerInternal;
@@ -49,8 +46,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -63,7 +58,6 @@
private BatteryController mBatteryController;
private FlexibilityController mFlexibilityController;
- private BroadcastReceiver mPowerReceiver;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid;
@@ -99,10 +93,6 @@
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Initialize real objects.
- // Capture the listeners.
- ArgumentCaptor<BroadcastReceiver> receiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
-
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
@@ -111,11 +101,6 @@
mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
mBatteryController.startTrackingLocked();
- verify(mContext).registerReceiver(receiverCaptor.capture(),
- ArgumentMatchers.argThat(filter ->
- filter.hasAction(Intent.ACTION_POWER_CONNECTED)
- && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
- mPowerReceiver = receiverCaptor.getValue();
try {
mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
// Need to do this since we're using a mock JS and not a real object.
@@ -159,9 +144,11 @@
}
private void setPowerConnected(boolean connected) {
- Intent intent = new Intent(
- connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
- mPowerReceiver.onReceive(mContext, intent);
+ doReturn(connected).when(mJobSchedulerService).isPowerConnected();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
}
private void setUidBias(int uid, int bias) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 2dfabd0..b12d6da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -893,13 +893,13 @@
@Test
public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
- final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
assertEquals(token, mMockHostToken);
}
@Test
public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
- final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
assertNull(token);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 408442b..35ad55c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -74,7 +74,9 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -83,6 +85,8 @@
import android.security.KeyStore;
import android.security.authorization.IKeystoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -100,6 +104,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -110,6 +115,8 @@
@Presubmit
@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper()
public class BiometricServiceTest {
@Rule
@@ -171,6 +178,8 @@
private UserManager mUserManager;
@Mock
private BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
@Mock
private IKeystoreAuthorization mKeystoreAuthService;
@@ -235,6 +244,14 @@
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(TestableLooper.get(this).getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(Looper.getMainLooper()));
+ }
+
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
"1:8:15", // ID1:Face:Strong
@@ -312,7 +329,7 @@
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -333,7 +350,7 @@
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -360,7 +377,7 @@
@Test
public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -377,7 +394,7 @@
public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -451,7 +468,7 @@
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(0 /* id */,
TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -1374,7 +1391,7 @@
@Test
public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
// Credential requested but not set up
@@ -1428,7 +1445,7 @@
@Test
public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
// When only biometric is requested
@@ -1515,7 +1532,7 @@
@Test
public void testRegisterAuthenticator_updatesStrengths() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
verify(mBiometricService.mBiometricStrengthController).startListening();
@@ -1533,7 +1550,7 @@
@Test
public void testWithDowngradedAuthenticator() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
final int testId = 0;
@@ -1639,7 +1656,7 @@
@Test(expected = IllegalStateException.class)
public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
@@ -1653,7 +1670,7 @@
@Test(expected = IllegalArgumentException.class)
public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
@@ -1665,7 +1682,7 @@
@Test
public void testRegistrationHappyPath_isOk() throws Exception {
// This is being tested in many of the other cases, but here's the base case.
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
for (String s : mInjector.getConfiguration(null)) {
@@ -1751,7 +1768,7 @@
final IBiometricEnabledOnKeyguardCallback callback =
mock(IBiometricEnabledOnKeyguardCallback.class);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
@@ -1775,7 +1792,7 @@
throws RemoteException {
mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
}
@@ -1799,7 +1816,7 @@
when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
.thenReturn(expectedResult);
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
@@ -1822,7 +1839,7 @@
// TODO: Reconcile the registration strength with the injector
private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1855,7 +1872,7 @@
// TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
// all tests.
private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
- mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1993,8 +2010,12 @@
return requestWrapper.eligibleSensors.get(0).getCookie();
}
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ private void waitForIdle() {
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ TestableLooper.get(this).processAllMessages();
+ } else {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
}
private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 7648bd17..9eca93e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,19 +24,28 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.HidlFaceSensorConfig;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
@@ -49,18 +58,23 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -92,6 +106,14 @@
private BiometricStateCallback mBiometricStateCallback;
@Mock
private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
+ @Mock
+ private Handler mBiometricCallbackHandler;
+ @Mock
+ private BiometricScheduler<IFace, ISession> mScheduler;
+ @Mock
+ AuthSessionCoordinator mAuthSessionCoordinator;
private final TestLooper mLooper = new TestLooper();
private SensorProps[] mSensorProps;
@@ -109,6 +131,16 @@
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
.thenReturn(FRR_THRESHOLD);
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ mBiometricCallbackHandler);
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ if (Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ mLooper.getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ Looper.getMainLooper()));
+ }
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -123,7 +155,7 @@
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
- mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
+ mBiometricContext, mDaemon, mBiometricHandlerProvider,
false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@@ -159,8 +191,7 @@
mFaceProvider = new FaceProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
- new Handler(mLooper.getLooper()),
- true /* resetLockoutRequiresChallenge */,
+ mBiometricHandlerProvider, true /* resetLockoutRequiresChallenge */,
true /* testHalEnabled */);
assertThat(mFaceProvider.mFaceSensors.get(faceId)
@@ -215,6 +246,54 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAuthenticateCallbackHandler() {
+ waitForIdle();
+
+ mFaceProvider.mFaceSensors.get(0).setScheduler(mScheduler);
+ mFaceProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()),
+ new FaceAuthenticateOptions.Builder()
+ .setSensorId(0)
+ .build(),
+ false /* restricted */, 1 /* statsClient */,
+ true /* allowBackgroundAuthentication */);
+
+ waitForIdle();
+
+ ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+ ClientMonitorCallback.class);
+ ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+ BaseClientMonitor.class);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+ Message.class);
+
+ verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+ callbackArgumentCaptor.capture());
+
+ BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+ ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+ callback.onClientStarted(client);
+
+ verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+ callback.onClientFinished(client, true /* success */);
+
+ verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+ messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+ anyBoolean());
+ }
+
private void waitForIdle() {
if (Flags.deHidl()) {
mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 258be57..0a35037 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,21 +24,31 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
@@ -50,11 +60,16 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.BiometricHandlerProvider;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -62,6 +77,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -93,6 +109,14 @@
private BiometricStateCallback mBiometricStateCallback;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricHandlerProvider mBiometricHandlerProvider;
+ @Mock
+ private Handler mBiometricCallbackHandler;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private BiometricScheduler<IFingerprint, ISession> mScheduler;
private final TestLooper mLooper = new TestLooper();
@@ -109,6 +133,16 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ mBiometricCallbackHandler);
+ if (Flags.deHidl()) {
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(mLooper.getLooper()));
+ } else {
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(Looper.getMainLooper()));
+ }
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -126,9 +160,8 @@
mFingerprintProvider = new FingerprintProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
- mDaemon, new Handler(mLooper.getLooper()),
- false /* resetLockoutRequiresHardwareAuthToken */,
- true /* testHalEnabled */);
+ mDaemon, mBiometricHandlerProvider,
+ false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */);
}
@Test
@@ -160,7 +193,7 @@
mBiometricStateCallback, mAuthenticationStateListeners,
hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
- new Handler(mLooper.getLooper()),
+ mBiometricHandlerProvider,
false /* resetLockoutRequiresHardwareAuthToken */,
true /* testHalEnabled */);
@@ -218,6 +251,56 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testScheduleAuthenticate() {
+ waitForIdle();
+
+ mFingerprintProvider.mFingerprintSensors.get(0).setScheduler(mScheduler);
+ mFingerprintProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(
+ new IBiometricSensorReceiver.Default()),
+ new FingerprintAuthenticateOptions.Builder()
+ .setSensorId(0)
+ .build(),
+ false /* restricted */, 1 /* statsClient */,
+ true /* allowBackgroundAuthentication */);
+
+ waitForIdle();
+
+ ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+ ClientMonitorCallback.class);
+ ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+ BaseClientMonitor.class);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+ Message.class);
+
+ verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+ callbackArgumentCaptor.capture());
+
+ BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+ ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+ callback.onClientStarted(client);
+
+ verify(mBiometricStateCallback).onClientStarted(eq(client));
+ verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+ callback.onClientFinished(client, true /* success */);
+
+ verify(mBiometricStateCallback).onClientFinished(eq(client), eq(true /* success */));
+ verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+ messageCaptor.capture(), anyLong());
+
+ messageCaptor.getValue().getCallback().run();
+
+ verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+ anyBoolean());
+ }
+
private void waitForIdle() {
if (Flags.deHidl()) {
mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index e075379..c0ea157 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -106,13 +106,13 @@
}
@Test
- public void testRecord_changeUserForFile_throws() {
+ public void testRecord_changeUserForFile_ignored() {
Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
PackageDynamicCodeLoading info = makePackageDcl(entry1);
- assertThrows(() -> record(info, entry2));
+ assertThat(record(info, entry2)).isFalse();
assertHasEntries(info, entry1);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3426cbe..f6cf4da 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4121,7 +4121,8 @@
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ r.getKey(), 1000, null);
verify(mWorkerHandler, never()).post(
any(NotificationManagerService.SnoozeNotificationRunnable.class));
@@ -4139,13 +4140,118 @@
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
- mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ r2.getKey(), 1000, null);
verify(mWorkerHandler).post(
any(NotificationManagerService.SnoozeNotificationRunnable.class));
}
@Test
+ public void snoozeNotificationInt_rapidSnooze_new() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create recent notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis());
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is logged.
+ verify(mAppOpsManager).noteOpNoThrow(
+ AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null,
+ null);
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_old() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create old notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis() - 60000);
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_new_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create recent notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis());
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
+ }
+
+ @Test
+ public void snoozeNotificationInt_rapidSnooze_old_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+ // Create old notification.
+ final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+ System.currentTimeMillis() - 60000);
+ mService.addNotification(nr1);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+ nr1.getKey(), 1000, null);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ // Ensure cancel event is not logged.
+ verify(mAppOpsManager, never()).noteOpNoThrow(
+ eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+ any(), any());
+ }
+
+ @Test
public void testSnoozeRunnable_tooManySnoozed_singleNotification() {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
@@ -9746,8 +9852,11 @@
throws RemoteException {
IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
- // Set up volume to be above 0 for the sound to actually play
+ // Set up volume to be above 0, and for AudioManager to signal playback should happen,
+ // for the sound to actually play
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+ when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
+ .thenReturn(true);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a0562aa..f02dd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -236,6 +236,26 @@
assertTrue(window.isOnScreen());
window.hide(false /* doAnimation */, false /* requestAnim */);
assertFalse(window.isOnScreen());
+
+ // Verifies that a window without animation can be hidden even if its parent is animating.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ assertTrue(window.isVisibleByPolicy());
+ window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class),
+ false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM);
+ window.mAttrs.windowAnimations = 0;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertFalse(window.isVisibleByPolicy());
+ assertFalse(window.isOnScreen());
+
+ // Verifies that a window with animation can be hidden after the hide animation is finished.
+ window.show(false /* doAnimation */, false /* requestAnim */);
+ window.mAttrs.windowAnimations = android.R.style.Animation_Dialog;
+ window.hide(true /* doAnimation */, true /* requestAnim */);
+ assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+ assertTrue(window.isVisibleByPolicy());
+ window.cancelAnimation();
+ assertFalse(window.isVisibleByPolicy());
}
@Test
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b7706a9..6bdc43e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -810,6 +810,9 @@
* If this setter method is never called or cleared using
* {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
* {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+ * If this setter is called and set as an empty Set, then this {@link PhoneAccount} does
+ * not support simultaneous calling with any other {@link PhoneAccount}s registered by the
+ * same application.
* <p>
* Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
* were registered by the same application. Simultaneous calling across applications is
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a188581..82ed340 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -40,6 +40,7 @@
import android.telephony.SubscriptionManager.ProfileClass;
import android.telephony.SubscriptionManager.SimDisplayNameSource;
import android.telephony.SubscriptionManager.SubscriptionType;
+import android.telephony.SubscriptionManager.TransferStatus;
import android.telephony.SubscriptionManager.UsageSetting;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -236,6 +237,11 @@
@UsageSetting
private final int mUsageSetting;
+ /**
+ * Subscription's transfer status
+ */
+ private final int mTransferStatus;
+
// Below are the fields that do not exist in the database.
/**
@@ -393,6 +399,7 @@
this.mUsageSetting = usageSetting;
this.mIsOnlyNonTerrestrialNetwork = false;
this.mServiceCapabilities = 0;
+ this.mTransferStatus = 0;
}
/**
@@ -433,6 +440,7 @@
this.mUsageSetting = builder.mUsageSetting;
this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
this.mServiceCapabilities = builder.mServiceCapabilities;
+ this.mTransferStatus = builder.mTransferStatus;
}
/**
@@ -928,6 +936,19 @@
return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
}
+ /**
+ * Get the transfer status for this subscription.
+ *
+ * @return The transfer status for this subscription.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public @TransferStatus int getTransferStatus() {
+ return mTransferStatus;
+ }
+
@NonNull
public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
new Parcelable.Creator<SubscriptionInfo>() {
@@ -967,6 +988,7 @@
.setOnlyNonTerrestrialNetwork(source.readBoolean())
.setServiceCapabilities(
SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
+ .setTransferStatus(source.readInt())
.build();
}
@@ -1010,6 +1032,7 @@
dest.writeInt(mUsageSetting);
dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
dest.writeInt(mServiceCapabilities);
+ dest.writeInt(mTransferStatus);
}
@Override
@@ -1075,6 +1098,7 @@
+ " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+ " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
mServiceCapabilities).toString()
+ + " transferStatus=" + mTransferStatus
+ "]";
}
@@ -1101,7 +1125,8 @@
that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
&& mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
&& mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
- && mServiceCapabilities == that.mServiceCapabilities;
+ && mServiceCapabilities == that.mServiceCapabilities
+ && mTransferStatus == that.mTransferStatus;
}
@Override
@@ -1110,7 +1135,8 @@
mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
- mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
+ mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities,
+ mTransferStatus);
result = 31 * result + Arrays.hashCode(mEhplmns);
result = 31 * result + Arrays.hashCode(mHplmns);
result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1314,6 +1340,8 @@
*/
private boolean mIsOnlyNonTerrestrialNetwork = false;
+ private int mTransferStatus = 0;
+
/**
* Service capabilities bitmasks the subscription supports.
*/
@@ -1363,6 +1391,7 @@
mUsageSetting = info.mUsageSetting;
mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
mServiceCapabilities = info.mServiceCapabilities;
+ mTransferStatus = info.mTransferStatus;
}
/**
@@ -1785,6 +1814,18 @@
mServiceCapabilities = combinedCapabilities;
return this;
}
+ /**
+ * Set subscription's transfer status
+ *
+ * @param status Subscription's transfer status
+ * @return The builder.
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @NonNull
+ public Builder setTransferStatus(@TransferStatus int status) {
+ mTransferStatus = status;
+ return this;
+ }
/**
* Build the {@link SubscriptionInfo}.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1749545..3fde3b6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1148,6 +1148,14 @@
*/
public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
+ /**
+ * TelephonyProvider column name to identify eSIM's transfer status.
+ * By default, it's disabled.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -1453,6 +1461,41 @@
private static final LruCache<Pair<String, Configuration>, Resources> sResourcesCache =
new LruCache<>(MAX_RESOURCE_CACHE_ENTRY_COUNT);
+
+ /**
+ * The profile has not been transferred or converted to an eSIM.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_NONE = 0;
+
+ /**
+ * The existing profile of the old device has been transferred to an eSIM of the new device.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+
+ /**
+ * The existing profile of the same device has been converted to an eSIM of the same device
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ public static final int TRANSFER_STATUS_CONVERTED = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"TRANSFER_STATUS"},
+ value = {
+ TRANSFER_STATUS_NONE,
+ TRANSFER_STATUS_TRANSFERRED_OUT,
+ TRANSFER_STATUS_CONVERTED,
+ })
+ public @interface TransferStatus {}
+
+
/**
* A listener class for monitoring changes to {@link SubscriptionInfo} records.
* <p>
@@ -4716,4 +4759,27 @@
public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
return 1 << (capability - 1);
}
+
+ /**
+ * Set the transfer status of the subscriptionInfo of the subId.
+ * @param subscriptionId The unique SubscriptionInfo key in database.
+ * @param status The transfer status to change.
+ *
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void setTransferStatus(int subscriptionId, @TransferStatus int status) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setTransferStatus(subscriptionId, status);
+ }
+ } catch (RemoteException ex) {
+ logd("setTransferStatus for subId = " + subscriptionId + " failed.");
+ throw ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 9b8e62f..0fe43b3 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,6 +16,7 @@
package android.telephony.euicc;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -50,6 +51,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -1707,4 +1709,57 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the supported carrier ids for pSIM conversion.
+ *
+ * <p>Any existing pSIM conversion supported carrier list will be replaced
+ * by the {@code carrierIds} set here.
+ *
+ * @param carrierIds is a list of carrierIds that supports pSIM conversion
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+ * @throws IllegalStateException if this method is called when {@link #isEnabled} is false.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void setPsimConversionSupportedCarriers(@NonNull Set<Integer> carrierIds) {
+ if (!isEnabled()) {
+ throw new IllegalStateException("Euicc is not enabled");
+ }
+ try {
+ int[] arr = carrierIds.stream().mapToInt(Integer::intValue).toArray();
+ getIEuiccController().setPsimConversionSupportedCarriers(arr);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Returns whether the given carrier id supports pSIM conversion or not.
+ *
+ * @param carrierId to check whether pSIM conversion is supported or not
+ * @return whether the given carrier id supports pSIM conversion or not,
+ * or false if {@link #isEnabled} is false
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public boolean isPsimConversionSupported(int carrierId) {
+ if (!isEnabled()) {
+ return false;
+ }
+ try {
+ return getIEuiccController().isPsimConversionSupported(carrierId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 3f41d56..cc770aa 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -376,4 +376,12 @@
* @param data with the sim specific configs to be backed up.
*/
void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
+
+ /**
+ * Set the transfer status of the subscriptionInfo that corresponds to subId.
+ * @param subId The unique SubscriptionInfo key in database.
+ * @param status The transfer status to change. This value must be one of the following.
+ */
+ @EnforcePermission("WRITE_EMBEDDED_SUBSCRIPTIONS")
+ void setTransferStatus(int subId, int status);
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 19f1a5b..d417772 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -57,4 +57,6 @@
boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
boolean isCompatChangeEnabled(String callingPackage, long changeId);
+ void setPsimConversionSupportedCarriers(in int[] carrierIds);
+ boolean isPsimConversionSupported(in int carrierId);
}