Merge "Fix the exception type set on ui cancellation" into udc-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 37ceb09..526e63c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1384,8 +1384,8 @@
* want to call one of these methods.
*
* Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
- * an app must hold the {@link android.Manifest.permission#INTERNET} and
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * an app must hold the
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permission to
* schedule a job that requires a network.
*
* <p class="note">
@@ -1445,8 +1445,8 @@
* constraint.
*
* Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
- * an app must hold the {@link android.Manifest.permission#INTERNET} and
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
+ * an app must hold the
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permission to
* schedule a job that requires a network.
*
* @param networkRequest The detailed description of the kind of network
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index a0634f0..dc608e7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -810,7 +810,7 @@
mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
mRecycledAssignmentInfo, mRecycledPrivilegedState);
- noteConcurrency();
+ noteConcurrency(true);
}
@VisibleForTesting
@@ -1437,11 +1437,13 @@
}
}
- private void noteConcurrency() {
+ private void noteConcurrency(boolean logForHistogram) {
mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
// TODO: log per type instead of only TOP
mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
- sConcurrencyHistogramLogger.logSample(mActiveServices.size());
+ if (logForHistogram) {
+ sConcurrencyHistogramLogger.logSample(mActiveServices.size());
+ }
}
@GuardedBy("mLock")
@@ -1582,7 +1584,9 @@
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
if (pendingJobQueue.size() == 0) {
worker.clearPreferredUid();
- noteConcurrency();
+ // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
+ // overcounting lower concurrency values as jobs end execution.
+ noteConcurrency(false);
return;
}
if (mActiveServices.size() >= mSteadyStateConcurrencyLimit) {
@@ -1612,7 +1616,9 @@
// scheduled), but we should
// be able to stop the other jobs soon so don't start running anything new until we
// get back below the limit.
- noteConcurrency();
+ // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
+ // overcounting lower concurrency values as jobs end execution.
+ noteConcurrency(false);
return;
}
}
@@ -1761,7 +1767,9 @@
}
}
- noteConcurrency();
+ // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
+ // overcounting lower concurrency values as jobs end execution.
+ noteConcurrency(false);
}
/**
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 e4e3de2..3f552b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -195,7 +195,7 @@
private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
/**
- * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling
+ * Require the app to have the ACCESS_NETWORK_STATE permissions when scheduling
* a job with a connectivity constraint.
*/
@ChangeId
@@ -1503,6 +1503,16 @@
}
toCancel.enqueueWorkLocked(work);
+ if (toCancel.getJob().isUserInitiated()) {
+ // The app is in a state to successfully schedule a UI job. Presumably, the
+ // user has asked for this additional bit of work, so remove any demotion
+ // flags. Only do this for UI jobs since they have strict scheduling
+ // requirements; it's harder to assume other jobs were scheduled due to
+ // user interaction/request.
+ toCancel.removeInternalFlags(
+ JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
+ }
mJobs.touchJob(toCancel);
sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
@@ -4001,12 +4011,6 @@
if (job.getRequiredNetwork() != null
&& CompatChanges.isChangeEnabled(
REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
- // All networking, including with the local network and even local to the device,
- // requires the INTERNET permission.
- if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) {
- throw new SecurityException(Manifest.permission.INTERNET
- + " required for jobs with a connectivity constraint");
- }
if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) {
throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE
+ " required for jobs with a connectivity constraint");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 2944095e..bf2e456 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -551,9 +551,6 @@
if (CompatChanges.isChangeEnabled(
JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
final String pkgName = job.getServiceComponent().getPackageName();
- if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) {
- return false;
- }
if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) {
return false;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 6445c3b..b5d763c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1177,6 +1177,10 @@
mInternalFlags |= flags;
}
+ public void removeInternalFlags(int flags) {
+ mInternalFlags = mInternalFlags & ~flags;
+ }
+
int getPreferredConstraintFlags() {
return mPreferredConstraints;
}
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
index d1d130d..f37f541 100644
--- a/core/java/android/app/admin/DeviceStateCache.java
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -50,6 +50,14 @@
public abstract boolean isUserOrganizationManaged(@UserIdInt int userHandle);
/**
+ * Returns whether a user has affiliated IDs.
+ */
+
+ public boolean hasAffiliationWithDevice(int userId) {
+ return false;
+ }
+
+ /**
* Empty implementation.
*/
private static class EmptyDeviceStateCache extends DeviceStateCache {
diff --git a/core/java/android/app/admin/PolicyKey.java b/core/java/android/app/admin/PolicyKey.java
index 3544c19..9b12e59 100644
--- a/core/java/android/app/admin/PolicyKey.java
+++ b/core/java/android/app/admin/PolicyKey.java
@@ -25,6 +25,7 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
@@ -41,6 +42,9 @@
@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
@SystemApi
public abstract class PolicyKey implements Parcelable {
+
+ static final String TAG = "PolicyKey";
+
/**
* @hide
*/
@@ -76,9 +80,14 @@
/**
* @hide
*/
+ @Nullable
public static PolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) {
String identifier = parser.getAttributeValue(
/* namespace= */ null, ATTR_POLICY_IDENTIFIER);
+ if (identifier == null) {
+ Log.wtf(TAG, "Error parsing generic policy key, identifier is null.");
+ return null;
+ }
return new NoArgsPolicyKey(identifier);
}
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 257ad71..5b24fb6 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -218,7 +218,8 @@
FINGERPRINT_ACQUIRED_UNKNOWN,
FINGERPRINT_ACQUIRED_IMMOBILE,
FINGERPRINT_ACQUIRED_TOO_BRIGHT,
- FINGERPRINT_ACQUIRED_POWER_PRESSED})
+ FINGERPRINT_ACQUIRED_POWER_PRESSED,
+ FINGERPRINT_ACQUIRED_RE_ENROLL})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintAcquired {}
@@ -310,6 +311,12 @@
int FINGERPRINT_ACQUIRED_POWER_PRESSED = 11;
/**
+ * This message is sent to encourage the user to re-enroll their fingerprints.
+ * @hide
+ */
+ int FINGERPRINT_ACQUIRED_RE_ENROLL = 12;
+
+ /**
* @hide
*/
int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 14f050d..708ebdf 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN;
import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS;
import android.annotation.ElapsedRealtimeLong;
@@ -270,6 +271,15 @@
static final long THROW_ON_INITIALIZE_IF_NO_DSP = 269165460L;
/**
+ * Gates returning {@link Callback#onFailure} and {@link Callback#onUnknownFailure}
+ * when asynchronous exceptions are propagated to the client. If the change is not enabled,
+ * the existing behavior of delivering {@link #STATE_ERROR} is retained.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS = 280471513L;
+
+ /**
* Controls the sensitivity threshold adjustment factor for a given model.
* Negative value corresponds to less sensitive model (high threshold) and
* a positive value corresponds to a more sensitive model (low threshold).
@@ -313,7 +323,6 @@
private final Executor mExternalExecutor;
private final Handler mHandler;
private final IBinder mBinder = new Binder();
- private final int mTargetSdkVersion;
private final boolean mSupportSandboxedDetectionService;
@GuardedBy("mLock")
@@ -856,7 +865,6 @@
new Handler(Looper.myLooper()));
mInternalCallback = new SoundTriggerListener(mHandler);
mModelManagementService = modelManagementService;
- mTargetSdkVersion = targetSdkVersion;
mSupportSandboxedDetectionService = supportSandboxedDetectionService;
}
@@ -1382,6 +1390,7 @@
*
* @hide
*/
+ // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
void onSoundModelsChanged() {
synchronized (mLock) {
if (mAvailability == STATE_INVALID
@@ -1401,20 +1410,38 @@
return;
}
- // Stop the recognition before proceeding.
- // This is done because we want to stop the recognition on an older model if it changed
- // or was deleted.
- // The availability change callback should ensure that the client starts recognition
- // again if needed.
+ // Stop the recognition before proceeding if we are in the enrolled state.
+ // The framework makes the guarantee that an actively used model is present in the
+ // system server's enrollment database. For this reason we much stop an actively running
+ // model when the underlying sound model in enrollment database no longer match.
if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
+ // A SoundTriggerFailure will be sent to the client if the model state was
+ // changed. This is an overloading of the onFailure usage because we are sending a
+ // callback even in the successful stop case. If stopRecognition is successful,
+ // suggested next action RESTART_RECOGNITION will be sent.
+ // TODO(b/281608561): This code path will be removed with other enrollment flows in
+ // this class.
try {
- stopRecognitionLocked();
- } catch (SecurityException e) {
- Slog.w(TAG, "Failed to Stop the recognition", e);
- if (mTargetSdkVersion <= Build.VERSION_CODES.R) {
- throw e;
+ int result = stopRecognitionLocked();
+ if (result == STATUS_OK) {
+ sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN,
+ "stopped recognition because of enrollment update",
+ FailureSuggestedAction.RESTART_RECOGNITION));
}
- updateAndNotifyStateChangedLocked(STATE_ERROR);
+ // only log to logcat here because many failures can be false positives such as
+ // calling stopRecognition where there is no started session.
+ Log.w(TAG, "Failed to stop recognition after enrollment update: code="
+ + result);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN,
+ "Failed to stop recognition after enrollment update: "
+ + Log.getStackTraceString(e),
+ FailureSuggestedAction.RECREATE_DETECTOR));
+ } else {
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
+ }
return;
}
}
@@ -1538,6 +1565,12 @@
@GuardedBy("mLock")
private void updateAndNotifyStateChangedLocked(int availability) {
+ updateAvailabilityLocked(availability);
+ notifyStateChangedLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateAvailabilityLocked(int availability) {
if (DBG) {
Slog.d(TAG, "Hotword availability changed from " + mAvailability
+ " -> " + availability);
@@ -1545,7 +1578,6 @@
if (!mIsAvailabilityOverriddenByTestApi) {
mAvailability = availability;
}
- notifyStateChangedLocked();
}
@GuardedBy("mLock")
@@ -1555,6 +1587,18 @@
message.sendToTarget();
}
+ @GuardedBy("mLock")
+ private void sendUnknownFailure(String failureMessage) {
+ // update but do not call onAvailabilityChanged callback for STATE_ERROR
+ updateAvailabilityLocked(STATE_ERROR);
+ Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
+ }
+
+ private void sendSoundTriggerFailure(@NonNull SoundTriggerFailure soundTriggerFailure) {
+ Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, soundTriggerFailure)
+ .sendToTarget();
+ }
+
/** @hide */
static final class SoundTriggerListener extends IHotwordRecognitionStatusCallback.Stub {
private final Handler mHandler;
@@ -1577,6 +1621,7 @@
.build())
.sendToTarget();
}
+
@Override
public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
Slog.w(TAG, "Generic sound trigger event detected at AOHD: " + event);
@@ -1726,6 +1771,7 @@
}
}
+ // TODO(b/267681692): remove the AsyncTask usage
class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> {
@Override
@@ -1744,13 +1790,17 @@
}
updateAndNotifyStateChangedLocked(availability);
}
- } catch (SecurityException e) {
+ } catch (Exception e) {
+ // Any exception here not caught will crash the process because AsyncTask does not
+ // bubble up the exceptions to the client app, so we must propagate it to the app.
Slog.w(TAG, "Failed to refresh availability", e);
- if (mTargetSdkVersion <= Build.VERSION_CODES.R) {
- throw e;
- }
synchronized (mLock) {
- updateAndNotifyStateChangedLocked(STATE_ERROR);
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendUnknownFailure(
+ "Failed to refresh availability: " + Log.getStackTraceString(e));
+ } else {
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
+ }
}
}
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 2ce5e5d..b4b0ffaf 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -74,14 +74,22 @@
public @interface SoundTriggerErrorCode {}
private final int mErrorCode;
+ private final int mSuggestedAction;
private final String mErrorMessage;
/**
* @hide
*/
@TestApi
- public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
- @NonNull String errorMessage) {
+ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) {
+ this(errorCode, errorMessage, getSuggestedActionBasedOnErrorCode(errorCode));
+ }
+
+ /**
+ * @hide
+ */
+ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage,
+ @FailureSuggestedAction.FailureSuggestedActionDef int suggestedAction) {
if (TextUtils.isEmpty(errorMessage)) {
throw new IllegalArgumentException("errorMessage is empty or null.");
}
@@ -95,7 +103,13 @@
default:
throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
}
+ if (suggestedAction != getSuggestedActionBasedOnErrorCode(errorCode)
+ && errorCode != ERROR_CODE_UNKNOWN) {
+ throw new IllegalArgumentException("Invalid suggested next action: "
+ + "errorCode=" + errorCode + ", suggestedAction=" + suggestedAction);
+ }
mErrorMessage = errorMessage;
+ mSuggestedAction = suggestedAction;
}
/**
@@ -119,7 +133,11 @@
*/
@FailureSuggestedAction.FailureSuggestedActionDef
public int getSuggestedAction() {
- switch (mErrorCode) {
+ return mSuggestedAction;
+ }
+
+ private static int getSuggestedActionBasedOnErrorCode(@SoundTriggerErrorCode int errorCode) {
+ switch (errorCode) {
case ERROR_CODE_UNKNOWN:
case ERROR_CODE_MODULE_DIED:
case ERROR_CODE_UNEXPECTED_PREEMPTION:
@@ -144,8 +162,11 @@
@Override
public String toString() {
- return "SoundTriggerFailure { errorCode = " + mErrorCode + ", errorMessage = "
- + mErrorMessage + " }";
+ return "SoundTriggerFailure {"
+ + " errorCode = " + mErrorCode
+ + ", errorMessage = " + mErrorMessage
+ + ", suggestedNextAction = " + mSuggestedAction
+ + " }";
}
public static final @NonNull Parcelable.Creator<SoundTriggerFailure> CREATOR =
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 88447da..ff3c015 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -52,6 +52,8 @@
int checkAudioOperation(int code, int usage, int uid, String packageName);
boolean shouldCollectNotes(int opCode);
void setCameraAudioRestriction(int mode);
+ void startWatchingModeWithFlags(int op, String packageName, int flags,
+ IAppOpsCallback callback);
// End of methods also called by native code.
// Any new method exposed to native must be added after the last one, do not reorder
@@ -110,8 +112,6 @@
void startWatchingStarted(in int[] ops, IAppOpsStartedCallback callback);
void stopWatchingStarted(IAppOpsStartedCallback callback);
- void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback);
-
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
void stopWatchingNoted(IAppOpsNotedCallback callback);
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 80f540c..506f19f 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -275,7 +275,7 @@
}
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public Handler getHandler() {
return mHandler;
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 2b9db70..e530aec 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -772,11 +772,12 @@
return true;
}
+ @UiThread
private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
synchronized (mLock) {
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerAdded(cuj, tracker.getViewRoot());
+ mDebugOverlay.onTrackerAdded(cuj, tracker);
}
if (DEBUG) {
Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
@@ -791,6 +792,7 @@
}
}
+ @UiThread
private void removeTracker(@CujType int cuj, int reason) {
synchronized (mLock) {
mRunningTrackers.remove(cuj);
@@ -818,7 +820,7 @@
SETTINGS_DEBUG_OVERLAY_ENABLED_KEY,
DEFAULT_DEBUG_OVERLAY_ENABLED);
if (debugOverlayEnabled && mDebugOverlay == null) {
- mDebugOverlay = new InteractionMonitorDebugOverlay(mDebugBgColor, mDebugYOffset);
+ mDebugOverlay = new InteractionMonitorDebugOverlay(mLock, mDebugBgColor, mDebugYOffset);
} else if (!debugOverlayEnabled && mDebugOverlay != null) {
mDebugOverlay.dispose();
mDebugOverlay = null;
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index 99b9f2f..ef7944c 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -19,25 +19,30 @@
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import android.annotation.ColorInt;
+import android.annotation.UiThread;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.Trace;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.WindowCallbacks;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.jank.FrameTracker.Reasons;
import com.android.internal.jank.InteractionJankMonitor.CujType;
/**
* An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
* associated with one of the CUJs being tracked. There's no guarantee which window it will
- * draw to. NOTE: sometimes the CUJ names will remain displayed on the screen longer than they
- * are actually running.
+ * draw to. Traces that use the debug overlay should not be used for performance analysis.
+ * <p>
+ * To enable the overlay, run the following: <code>adb shell device_config put
+ * interaction_jank_monitor debug_overlay_enabled true</code>
* <p>
* CUJ names will be drawn as follows:
* <ul>
@@ -45,12 +50,16 @@
* <li> Grey text indicates the CUJ ended normally and is no longer running
* <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
* </ul>
+ * @hide
*/
class InteractionMonitorDebugOverlay implements WindowCallbacks {
private static final int REASON_STILL_RUNNING = -1000;
+ private final Object mLock;
// Sparse array where the key in the CUJ and the value is the session status, or null if
// it's currently running
+ @GuardedBy("mLock")
private final SparseIntArray mRunningCujs = new SparseIntArray();
+ private Handler mHandler = null;
private FrameTracker.ViewRootWrapper mViewRoot = null;
private final Paint mDebugPaint;
private final Paint.FontMetrics mDebugFontMetrics;
@@ -59,8 +68,10 @@
private final int mBgColor;
private final double mYOffset;
private final String mPackageName;
+ private static final String TRACK_NAME = "InteractionJankMonitor";
- InteractionMonitorDebugOverlay(@ColorInt int bgColor, double yOffset) {
+ InteractionMonitorDebugOverlay(Object lock, @ColorInt int bgColor, double yOffset) {
+ mLock = lock;
mBgColor = bgColor;
mYOffset = yOffset;
mDebugPaint = new Paint();
@@ -70,18 +81,30 @@
mPackageName = context.getPackageName();
}
+ @UiThread
void dispose() {
- if (mViewRoot != null) {
- mViewRoot.removeWindowCallbacks(this);
+ if (mViewRoot != null && mHandler != null) {
+ mHandler.runWithScissors(() -> mViewRoot.removeWindowCallbacks(this),
+ InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
forceRedraw();
}
+ mHandler = null;
mViewRoot = null;
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
}
- private boolean attachViewRootIfNeeded(FrameTracker.ViewRootWrapper viewRoot) {
+ @UiThread
+ private boolean attachViewRootIfNeeded(FrameTracker tracker) {
+ FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot();
if (mViewRoot == null && viewRoot != null) {
+ // Add a trace marker so we can identify traces that were captured while the debug
+ // overlay was enabled. Traces that use the debug overlay should NOT be used for
+ // performance analysis.
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
+ mHandler = tracker.getHandler();
mViewRoot = viewRoot;
- viewRoot.addWindowCallbacks(this);
+ mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
+ InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
forceRedraw();
return true;
}
@@ -115,52 +138,61 @@
}
}
+ @UiThread
private void forceRedraw() {
- if (mViewRoot != null) {
- mViewRoot.requestInvalidateRootRenderNode();
- mViewRoot.getView().invalidate();
+ if (mViewRoot != null && mHandler != null) {
+ mHandler.runWithScissors(() -> {
+ mViewRoot.requestInvalidateRootRenderNode();
+ mViewRoot.getView().invalidate();
+ }, InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
}
}
+ @UiThread
void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
SparseArray<FrameTracker> runningTrackers) {
- mRunningCujs.put(removedCuj, reason);
- // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
- if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
- mRunningCujs.clear();
- dispose();
- } else {
- boolean needsNewViewRoot = true;
- if (mViewRoot != null) {
- // Check to see if this viewroot is still associated with one of the running
- // trackers
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (mViewRoot.equals(
- runningTrackers.valueAt(i).getViewRoot())) {
- needsNewViewRoot = false;
- break;
- }
- }
- }
- if (needsNewViewRoot) {
+ synchronized (mLock) {
+ mRunningCujs.put(removedCuj, reason);
+ // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
+ if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+ mRunningCujs.clear();
dispose();
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (attachViewRootIfNeeded(runningTrackers.valueAt(i).getViewRoot())) {
- break;
+ } else {
+ boolean needsNewViewRoot = true;
+ if (mViewRoot != null) {
+ // Check to see if this viewroot is still associated with one of the running
+ // trackers
+ for (int i = 0; i < runningTrackers.size(); i++) {
+ if (mViewRoot.equals(
+ runningTrackers.valueAt(i).getViewRoot())) {
+ needsNewViewRoot = false;
+ break;
+ }
}
}
- } else {
- forceRedraw();
+ if (needsNewViewRoot) {
+ dispose();
+ for (int i = 0; i < runningTrackers.size(); i++) {
+ if (attachViewRootIfNeeded(runningTrackers.valueAt(i))) {
+ break;
+ }
+ }
+ } else {
+ forceRedraw();
+ }
}
}
}
- void onTrackerAdded(@CujType int addedCuj, FrameTracker.ViewRootWrapper viewRoot) {
- // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
- // is still running
- mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
- attachViewRootIfNeeded(viewRoot);
- forceRedraw();
+ @UiThread
+ void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) {
+ synchronized (mLock) {
+ // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+ // is still running
+ mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
+ attachViewRootIfNeeded(tracker);
+ forceRedraw();
+ }
}
@Override
@@ -188,7 +220,6 @@
@Override
public void onPostDraw(RecordingCanvas canvas) {
- Trace.beginSection("InteractionJankMonitor#drawDebug");
final int padding = dipToPx(5);
final int h = canvas.getHeight();
final int w = canvas.getWidth();
@@ -235,6 +266,5 @@
canvas.translate(0, cujNameTextHeight);
canvas.drawText(cujName, 0, 0, mDebugPaint);
}
- Trace.endSection();
}
}
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 6fa6fa5..3ba4ea5 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -118,6 +118,9 @@
private boolean mIsSkipped = false;
private boolean mCopyingFirstPidSucceeded = false;
+ private long mPreDumpIfLockTooSlowStartUptime;
+ private long mPreDumpIfLockTooSlowDuration = 0;
+
private final int mAnrRecordPlacedOnQueueCookie =
sNextAnrRecordPlacedOnQueueCookieGenerator.incrementAndGet();
@@ -401,6 +404,17 @@
Trace.traceCounter(TRACE_TAG_ACTIVITY_MANAGER, "anrRecordsQueueSize", queueSize);
}
+ /** Records the start of AnrController#preDumpIfLockTooSlow. */
+ public void preDumpIfLockTooSlowStarted() {
+ mPreDumpIfLockTooSlowStartUptime = getUptimeMillis();
+ }
+
+ /** Records the end of AnrController#preDumpIfLockTooSlow. */
+ public void preDumpIfLockTooSlowEnded() {
+ mPreDumpIfLockTooSlowDuration +=
+ getUptimeMillis() - mPreDumpIfLockTooSlowStartUptime;
+ }
+
/** Records a skipped ANR in ProcessErrorStateRecord#appNotResponding. */
public void anrSkippedProcessErrorStateRecordAppNotResponding() {
anrSkipped("appNotResponding");
@@ -415,7 +429,7 @@
* Returns latency data as a comma separated value string for inclusion in ANR report.
*/
public String dumpAsCommaSeparatedArrayWithHeader() {
- return "DurationsV3: " + mAnrTriggerUptime
+ return "DurationsV4: " + mAnrTriggerUptime
/* triggering_to_app_not_responding_duration = */
+ "," + (mAppNotRespondingStartUptime - mAnrTriggerUptime)
/* app_not_responding_duration = */
@@ -464,6 +478,8 @@
+ "," + mEarlyDumpStatus
/* copying_first_pid_succeeded = */
+ "," + (mCopyingFirstPidSucceeded ? 1 : 0)
+ /* preDumpIfLockTooSlow_duration = */
+ + "," + mPreDumpIfLockTooSlowDuration
+ "\n\n";
}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 73e3b41..e08f085 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -675,8 +675,6 @@
<java-symbol type="string" name="contentServiceSyncNotificationTitle" />
<java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
<java-symbol type="string" name="csd_dose_reached_warning" />
- <java-symbol type="string" name="csd_dose_repeat_warning" />
- <java-symbol type="string" name="csd_entering_RS2_warning" />
<java-symbol type="string" name="csd_momentary_exposure_warning" />
<java-symbol type="string" name="date_and_time" />
<java-symbol type="string" name="date_picker_decrement_day_button" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 7c2759a..92c0dab 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3865,6 +3865,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1511273241": {
+ "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"1518495446": {
"message": "removeWindowToken: Attempted to remove non-existing token: %s",
"level": "WARN",
@@ -4297,12 +4303,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1967643923": {
- "message": "Refershing activity for camera compatibility treatment, activityRecord=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
- },
"1967975839": {
"message": "Changing app %s visible=%b performLayout=%b",
"level": "VERBOSE",
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 9a06be0..701a87f 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -51,6 +51,9 @@
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
const skcms_ICCProfile* encodedProfile = mCodec->getICCProfile();
if (encodedProfile) {
+ if (encodedProfile->has_CICP) {
+ return mCodec->computeOutputColorSpace(kN32_SkColorType);
+ }
// If the profile maps directly to an SkColorSpace, that SkColorSpace
// will be returned. Otherwise, nullptr will be returned. In either
// case, using this SkColorSpace results in doing no color correction.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4759689..b1d2e33 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3730,12 +3730,7 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
public void setA2dpSuspended(boolean enable) {
- final IAudioService service = getService();
- try {
- service.setA2dpSuspended(enable);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ AudioSystem.setParameters("A2dpSuspended=" + enable);
}
/**
@@ -3748,12 +3743,7 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
public void setLeAudioSuspended(boolean enable) {
- final IAudioService service = getService();
- try {
- service.setLeAudioSuspended(enable);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ AudioSystem.setParameters("LeAudioSuspended=" + enable);
}
/**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7ce189b..fe5afc5 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -231,12 +231,6 @@
void setBluetoothScoOn(boolean on);
- @EnforcePermission("BLUETOOTH_STACK")
- void setA2dpSuspended(boolean on);
-
- @EnforcePermission("BLUETOOTH_STACK")
- void setLeAudioSuspended(boolean enable);
-
boolean isBluetoothScoOn();
void setBluetoothA2dpOn(boolean on);
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
index ad888e5..1b80cc6 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
@@ -20,5 +20,6 @@
<style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title.Inverse">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textColor">@android:color/black</item>
</style>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
index d55a027..b386e5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
@@ -36,6 +36,9 @@
/** Same as [sensorBounds], but in native resolution. */
val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) }
+ /** Same as [overlayBounds], but in native resolution. */
+ val nativeOverlayBounds = Rect(overlayBounds).apply { scale(1f / scaleFactor) }
+
/** See [android.view.DisplayInfo.logicalWidth] */
val logicalDisplayWidth =
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index c12bfcc..0e6b281 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -32,8 +32,8 @@
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/rear_display_folded_animation"
android:importantForAccessibility="no"
- android:layout_width="@dimen/rear_display_animation_width"
- android:layout_height="@dimen/rear_display_animation_height"
+ android:layout_width="@dimen/rear_display_animation_width_opened"
+ android:layout_height="@dimen/rear_display_animation_height_opened"
android:layout_gravity="center"
android:contentDescription="@string/rear_display_accessibility_unfolded_animation"
android:scaleType="fitXY"
@@ -49,8 +49,8 @@
android:text="@string/rear_display_unfolded_bottom_sheet_title"
android:textAppearance="@style/TextAppearance.Dialog.Title"
android:lineSpacingExtra="2sp"
- android:paddingTop="@dimen/rear_display_title_top_padding"
- android:paddingBottom="@dimen/rear_display_title_bottom_padding"
+ android:paddingTop="@dimen/rear_display_title_top_padding_opened"
+ android:paddingBottom="@dimen/rear_display_title_bottom_padding_opened"
android:gravity="center_horizontal|center_vertical"
/>
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index 238fc84..6a0217ec 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <com.android.systemui.animation.view.LaunchableLinearLayout
+ <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer
android:id="@+id/ongoing_call_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
android:textColor="?android:attr/colorPrimary"
/>
- </com.android.systemui.animation.view.LaunchableLinearLayout>
+ </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer>
</FrameLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 908aac4..f277e8a 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -67,6 +67,12 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">16dp</dimen>
+ <!-- Rear Display Education dimens -->
+ <dimen name="rear_display_animation_width">246dp</dimen>
+ <dimen name="rear_display_animation_height">180dp</dimen>
+ <dimen name="rear_display_title_top_padding">4dp</dimen>
+ <dimen name="rear_display_title_bottom_padding">0dp</dimen>
+
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bf0b8a6..2024dae 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -617,8 +617,8 @@
<dimen name="qs_header_height">120dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
- <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
- <dimen name="new_qs_header_non_clickable_element_height">24dp</dimen>
+ <dimen name="qs_header_non_clickable_element_height">24sp</dimen>
+ <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen>
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
@@ -1042,7 +1042,7 @@
<dimen name="display_cutout_margin_consumption">0px</dimen>
<!-- Height of the Ongoing App Ops chip -->
- <dimen name="ongoing_appops_chip_height">24dp</dimen>
+ <dimen name="ongoing_appops_chip_height">24sp</dimen>
<!-- Side padding between background of Ongoing App Ops chip and content -->
<dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
<!-- Margin between icons of Ongoing App Ops chip -->
@@ -1772,8 +1772,12 @@
<!-- Rear Display Education dimens -->
<dimen name="rear_display_animation_width">273dp</dimen>
<dimen name="rear_display_animation_height">200dp</dimen>
+ <dimen name="rear_display_animation_width_opened">273dp</dimen>
+ <dimen name="rear_display_animation_height_opened">200dp</dimen>
<dimen name="rear_display_title_top_padding">24dp</dimen>
<dimen name="rear_display_title_bottom_padding">16dp</dimen>
+ <dimen name="rear_display_title_top_padding_opened">24dp</dimen>
+ <dimen name="rear_display_title_bottom_padding_opened">16dp</dimen>
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1cbcb9d..5b1edc7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -257,14 +257,14 @@
* Authentication has happened and it's time to dismiss keyguard. This function
* should clean up and inform KeyguardViewMediator.
*
- * @param strongAuth whether the user has authenticated with strong authentication like
+ * @param fromPrimaryAuth whether the user has authenticated with primary auth like
* pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the dismissal
* completion.
*/
@Override
- public void finish(boolean strongAuth, int targetUserId) {
- if (!mKeyguardStateController.canDismissLockScreen() && !strongAuth) {
+ public void finish(boolean fromPrimaryAuth, int targetUserId) {
+ if (!mKeyguardStateController.canDismissLockScreen() && !fromPrimaryAuth) {
Log.e(TAG,
"Tried to dismiss keyguard when lockscreen is not dismissible and user "
+ "was not authenticated with a primary security method "
@@ -283,9 +283,9 @@
}
if (mViewMediatorCallback != null) {
if (deferKeyguardDone) {
- mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
+ mViewMediatorCallback.keyguardDonePending(fromPrimaryAuth, targetUserId);
} else {
- mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
+ mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
}
}
}
@@ -603,8 +603,8 @@
/**
* Dismiss keyguard due to a user unlock event.
*/
- public void finish(boolean strongAuth, int currentUser) {
- mKeyguardSecurityCallback.finish(strongAuth, currentUser);
+ public void finish(boolean primaryAuth, int currentUser) {
+ mKeyguardSecurityCallback.finish(primaryAuth, currentUser);
}
/**
@@ -736,7 +736,7 @@
}
boolean finish = false;
- boolean strongAuth = false;
+ boolean primaryAuth = false;
int eventSubtype = -1;
BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
@@ -761,7 +761,7 @@
case Pattern:
case Password:
case PIN:
- strongAuth = true;
+ primaryAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
@@ -805,7 +805,7 @@
mUiEventLogger.log(uiEvent, getSessionId());
}
if (finish) {
- mKeyguardSecurityCallback.finish(strongAuth, targetUserId);
+ mKeyguardSecurityCallback.finish(primaryAuth, targetUserId);
}
return finish;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 77e13ce..95e97ff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -893,7 +893,7 @@
}
@VisibleForTesting
- protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
+ public void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
Assert.isMainThread();
Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
mUserFingerprintAuthenticated.put(userId,
@@ -1169,7 +1169,7 @@
}
@VisibleForTesting
- protected void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
+ public void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
Assert.isMainThread();
mUserFaceAuthenticated.put(userId,
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 9308773..50f8f7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -29,11 +29,11 @@
/**
* Report that the keyguard is done.
*
- * @param strongAuth whether the user has authenticated with strong authentication like
+ * @param primaryAuth whether the user has authenticated with primary authentication like
* pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the completion.
*/
- void keyguardDone(boolean strongAuth, int targetUserId);
+ void keyguardDone(boolean primaryAuth, int targetUserId);
/**
* Report that the keyguard is done drawing.
@@ -49,11 +49,11 @@
/**
* Report that the keyguard is dismissable, pending the next keyguardDone call.
*
- * @param strongAuth whether the user has authenticated with strong authentication like
+ * @param primaryAuth whether the user has authenticated with primary authentication like
* pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the completion.
*/
- void keyguardDonePending(boolean strongAuth, int targetUserId);
+ void keyguardDonePending(boolean primaryAuth, int targetUserId);
/**
* Report when keyguard is actually gone
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 8f41956..39d9714 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Build;
import android.provider.DeviceConfig;
@@ -57,6 +56,7 @@
private final BiometricUnlockController mBiometricUnlockController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DeviceConfigProxy mDeviceConfigProxy;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private boolean mEnabled;
@@ -65,11 +65,13 @@
BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher,
DeviceConfigProxy deviceConfigProxy,
- @Main DelayableExecutor mainExecutor
+ @Main DelayableExecutor mainExecutor,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
mDeviceConfigProxy = deviceConfigProxy;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
updateEnabled();
mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
@@ -85,10 +87,13 @@
if (!mEnabled) {
return;
}
- mBiometricUnlockController.onBiometricAcquired(type,
- BiometricConstants.BIOMETRIC_ACQUIRED_GOOD);
- mBiometricUnlockController.onBiometricAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser(), type, true /* isStrongBiometric */);
+ if (type == BiometricSourceType.FACE) {
+ mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+ true);
+ } else if (type == BiometricSourceType.FINGERPRINT) {
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(
+ KeyguardUpdateMonitor.getCurrentUser(), true);
+ }
}
private void registerForBroadcasts(boolean register) {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
new file mode 100644
index 0000000..7c394a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.systemui.authentication
+
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ AuthenticationRepositoryModule::class,
+ ],
+)
+object AuthenticationModule
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
new file mode 100644
index 0000000..cd195f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui.authentication.data.repository
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Defines interface for classes that can access authentication-related application state. */
+interface AuthenticationRepository {
+
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method.
+ *
+ * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+ */
+ val isUnlocked: StateFlow<Boolean>
+
+ /**
+ * The currently-configured authentication method. This determines how the authentication
+ * challenge is completed in order to unlock an otherwise locked device.
+ */
+ val authenticationMethod: StateFlow<AuthenticationMethodModel>
+
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismisses once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ val isBypassEnabled: StateFlow<Boolean>
+
+ /** See [isUnlocked]. */
+ fun setUnlocked(isUnlocked: Boolean)
+
+ /** See [authenticationMethod]. */
+ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel)
+
+ /** See [isBypassEnabled]. */
+ fun setBypassEnabled(isBypassEnabled: Boolean)
+}
+
+class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
+ // TODO(b/280883900): get data from real data sources in SysUI.
+
+ private val _isUnlocked = MutableStateFlow(false)
+ override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+ private val _authenticationMethod =
+ MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234))
+ override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+ _authenticationMethod.asStateFlow()
+
+ private val _isBypassEnabled = MutableStateFlow(false)
+ override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
+
+ override fun setUnlocked(isUnlocked: Boolean) {
+ _isUnlocked.value = isUnlocked
+ }
+
+ override fun setBypassEnabled(isBypassEnabled: Boolean) {
+ _isBypassEnabled.value = isBypassEnabled
+ }
+
+ override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
+ _authenticationMethod.value = authenticationMethod
+ }
+}
+
+@Module
+interface AuthenticationRepositoryModule {
+ @Binds fun repository(impl: AuthenticationRepositoryImpl): AuthenticationRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
new file mode 100644
index 0000000..5aea930
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.systemui.authentication.domain.interactor
+
+import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Hosts application business logic related to authentication. */
+@SysUISingleton
+class AuthenticationInteractor
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val repository: AuthenticationRepository,
+) {
+ /**
+ * The currently-configured authentication method. This determines how the authentication
+ * challenge is completed in order to unlock an otherwise locked device.
+ */
+ val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod
+
+ /**
+ * Whether the device is unlocked.
+ *
+ * A device that is not yet unlocked requires unlocking by completing an authentication
+ * challenge according to the current authentication method.
+ *
+ * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+ */
+ val isUnlocked: StateFlow<Boolean> =
+ combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked ->
+ isUnlockedWithAuthMethod(
+ isUnlocked = isUnlocked,
+ authMethod = authMethod,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue =
+ isUnlockedWithAuthMethod(
+ isUnlocked = repository.isUnlocked.value,
+ authMethod = repository.authenticationMethod.value,
+ )
+ )
+
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismisses once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+
+ init {
+ // UNLOCKS WHEN AUTH METHOD REMOVED.
+ //
+ // Unlocks the device if the auth method becomes None.
+ applicationScope.launch {
+ repository.authenticationMethod.collect {
+ if (it is AuthenticationMethodModel.None) {
+ unlockDevice()
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns `true` if the device currently requires authentication before content can be viewed;
+ * `false` if content can be displayed without unlocking first.
+ */
+ fun isAuthenticationRequired(): Boolean {
+ return !isUnlocked.value && authenticationMethod.value.isSecure
+ }
+
+ /**
+ * Unlocks the device, assuming that the authentication challenge has been completed
+ * successfully.
+ */
+ fun unlockDevice() {
+ repository.setUnlocked(true)
+ }
+
+ /**
+ * Locks the device. From now on, the device will remain locked until [authenticate] is called
+ * with the correct input.
+ */
+ fun lockDevice() {
+ repository.setUnlocked(false)
+ }
+
+ /**
+ * Attempts to authenticate the user and unlock the device.
+ *
+ * @param input The input from the user to try to authenticate with. This can be a list of
+ * different things, based on the current authentication method.
+ * @return `true` if the authentication succeeded and the device is now unlocked; `false`
+ * otherwise.
+ */
+ fun authenticate(input: List<Any>): Boolean {
+ val isSuccessful =
+ when (val authMethod = this.authenticationMethod.value) {
+ is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code
+ is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
+ is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
+ else -> true
+ }
+
+ if (isSuccessful) {
+ repository.setUnlocked(true)
+ }
+
+ return isSuccessful
+ }
+
+ /** Triggers a biometric-powered unlock of the device. */
+ fun biometricUnlock() {
+ // TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
+ repository.setUnlocked(true)
+ }
+
+ /** See [authenticationMethod]. */
+ fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
+ repository.setAuthenticationMethod(authenticationMethod)
+ }
+
+ /** See [isBypassEnabled]. */
+ fun toggleBypassEnabled() {
+ repository.setBypassEnabled(!repository.isBypassEnabled.value)
+ }
+
+ companion object {
+ private fun isUnlockedWithAuthMethod(
+ isUnlocked: Boolean,
+ authMethod: AuthenticationMethodModel,
+ ): Boolean {
+ return if (authMethod is AuthenticationMethodModel.None) {
+ true
+ } else {
+ isUnlocked
+ }
+ }
+
+ /**
+ * Returns a PIN code from the given list. It's assumed the given list elements are all
+ * [Int].
+ */
+ private fun List<Any>.asCode(): Int? {
+ if (isEmpty()) {
+ return null
+ }
+
+ var code = 0
+ map { it as Int }.forEach { integer -> code = code * 10 + integer }
+
+ return code
+ }
+
+ /**
+ * Returns a password from the given list. It's assumed the given list elements are all
+ * [Char].
+ */
+ private fun List<Any>.asPassword(): String {
+ val anyList = this
+ return buildString { anyList.forEach { append(it as Char) } }
+ }
+
+ /**
+ * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
+ * list. It's assumed the given list elements are all
+ * [AuthenticationMethodModel.Pattern.PatternCoordinate].
+ */
+ private fun List<Any>.asPattern():
+ List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
+ val anyList = this
+ return buildList {
+ anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
new file mode 100644
index 0000000..83250b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.systemui.authentication.shared.model
+
+/** Enumerates all known authentication methods. */
+sealed class AuthenticationMethodModel(
+ /**
+ * Whether the authentication method is considered to be "secure".
+ *
+ * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
+ * methods simply require user dismissal.
+ */
+ open val isSecure: Boolean,
+) {
+ /** There is no authentication method on the device. We shouldn't even show the lock screen. */
+ object None : AuthenticationMethodModel(isSecure = false)
+
+ /** The most basic authentication method. The lock screen can be swiped away when displayed. */
+ object Swipe : AuthenticationMethodModel(isSecure = false)
+
+ data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true)
+
+ data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
+
+ data class Pattern(val coordinates: List<PatternCoordinate>) :
+ AuthenticationMethodModel(isSecure = true) {
+
+ data class PatternCoordinate(
+ val x: Int,
+ val y: Int,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 4b17be3..6db266f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -29,7 +29,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.UserHandle;
@@ -43,6 +42,9 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.util.Optional;
+
import javax.inject.Inject;
/**
@@ -66,6 +68,7 @@
private final Handler mHandler;
private final NotificationManager mNotificationManager;
private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
+ private final FingerprintReEnrollNotification mFingerprintReEnrollNotification;
private NotificationChannel mNotificationChannel;
private boolean mFaceNotificationQueued;
private boolean mFingerprintNotificationQueued;
@@ -102,8 +105,15 @@
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_REQUIRED,
UserHandle.USER_CURRENT);
- } else if (msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL
- && biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ }
+ }
+
+ @Override
+ public void onBiometricHelp(int msgId, String helpString,
+ BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT
+ && mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ msgId)) {
mFingerprintReenrollRequired = true;
}
}
@@ -115,13 +125,16 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController,
Handler handler, NotificationManager notificationManager,
- BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver) {
+ BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver,
+ Optional<FingerprintReEnrollNotification> fingerprintReEnrollNotification) {
mContext = context;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mHandler = handler;
mNotificationManager = notificationManager;
mBroadcastReceiver = biometricNotificationBroadcastReceiver;
+ mFingerprintReEnrollNotification = fingerprintReEnrollNotification.orElse(
+ new FingerprintReEnrollNotificationImpl());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
new file mode 100644
index 0000000..ca94e99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotification.java
@@ -0,0 +1,25 @@
+/*
+ * 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.systemui.biometrics;
+
+/**
+ * Checks if the fingerprint HAL has sent a re-enrollment request.
+ */
+public interface FingerprintReEnrollNotification {
+ /** Returns true if msgId corresponds to FINGERPRINT_ACQUIRED_RE_ENROLL. */
+ boolean isFingerprintReEnrollRequired(int msgId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
new file mode 100644
index 0000000..1f86bc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintReEnrollNotificationImpl.java
@@ -0,0 +1,29 @@
+/*
+ * 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.systemui.biometrics;
+
+import android.hardware.biometrics.BiometricFingerprintConstants;
+
+/**
+ * Checks if the fingerprint HAL has sent a re-enrollment request.
+ */
+public class FingerprintReEnrollNotificationImpl implements FingerprintReEnrollNotification{
+ @Override
+ public boolean isFingerprintReEnrollRequired(int msgId) {
+ return msgId == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_RE_ENROLL;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bc44df4..2eb5330 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -466,7 +466,7 @@
if (!mOnFingerDown) {
playStartHaptic();
}
- mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
+ mKeyguardViewManager.notifyKeyguardAuthenticated(false /* primaryAuth */);
mAttemptedToDismissKeyguard = true;
}
@@ -636,7 +636,7 @@
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
}
- return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
+ return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
}
private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
index 79a0acb..cf6044f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
@@ -22,6 +22,11 @@
/** Returns whether the touch coordinates are within the sensor's bounding box. */
@SysUISingleton
class BoundingBoxOverlapDetector : OverlapDetector {
- override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean =
- touchData.isWithinSensor(nativeSensorBounds)
+ override fun isGoodOverlap(
+ touchData: NormalizedTouchData,
+ nativeSensorBounds: Rect,
+ nativeOverlayBounds: Rect,
+ ): Boolean =
+ touchData.isWithinBounds(nativeOverlayBounds) &&
+ touchData.isWithinBounds(nativeSensorBounds)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index baf8d74..f70e01d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -30,8 +30,8 @@
TARGET // Pixel within sensor center target
}
-private val isDebug = true
-private val TAG = "EllipseOverlapDetector"
+private const val isDebug = false
+private const val TAG = "EllipseOverlapDetector"
/**
* Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
@@ -39,12 +39,21 @@
*/
@SysUISingleton
class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
- override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
- // First, check if touch is within bounding box,
- if (nativeSensorBounds.contains(touchData.x.toInt(), touchData.y.toInt())) {
+ override fun isGoodOverlap(
+ touchData: NormalizedTouchData,
+ nativeSensorBounds: Rect,
+ nativeOverlayBounds: Rect,
+ ): Boolean {
+ // First, check if touch is within bounding box to exit early
+ if (touchData.isWithinBounds(nativeSensorBounds)) {
return true
}
+ // Check touch is within overlay bounds, not worth checking if outside
+ if (!touchData.isWithinBounds(nativeOverlayBounds)) {
+ return false
+ }
+
var isTargetTouched = false
var sensorPixels = 0
var coveredPixels = 0
@@ -77,7 +86,7 @@
val percentage: Float = coveredPixels.toFloat() / sensorPixels
if (isDebug) {
- Log.v(
+ Log.d(
TAG,
"covered: $coveredPixels, sensor: $sensorPixels, " +
"percentage: $percentage, isCenterTouched: $isTargetTouched"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
index 6854b50..1b4062a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
@@ -51,16 +51,16 @@
) {
/**
- * [nativeSensorBounds] contains the location and dimensions of the sensor area in native
- * resolution and natural orientation.
+ * [nativeBounds] contains the location and dimensions of the area in native resolution and
+ * natural orientation.
*
- * Returns whether the coordinates of the given pointer are within the sensor's bounding box.
+ * Returns whether the coordinates of the given pointer are within the bounding box.
*/
- fun isWithinSensor(nativeSensorBounds: Rect): Boolean {
- return nativeSensorBounds.left <= x &&
- nativeSensorBounds.right >= x &&
- nativeSensorBounds.top <= y &&
- nativeSensorBounds.bottom >= y
+ fun isWithinBounds(nativeBounds: Rect): Boolean {
+ return nativeBounds.left <= x &&
+ nativeBounds.right >= x &&
+ nativeBounds.top <= y &&
+ nativeBounds.bottom >= y
}
@JvmOverloads
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
index 0fec8ff..f163471 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
@@ -20,5 +20,9 @@
/** Determines whether the touch has a sufficient overlap with the sensor. */
interface OverlapDetector {
- fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean
+ fun isGoodOverlap(
+ touchData: NormalizedTouchData,
+ nativeSensorBounds: Rect,
+ nativeOverlayBounds: Rect
+ ): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 6c9390d..eeb0f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -46,7 +46,13 @@
val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
val pointersOnSensor =
touchData
- .filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) }
+ .filter {
+ overlapDetector.isGoodOverlap(
+ it,
+ overlayParams.nativeSensorBounds,
+ overlayParams.nativeOverlayBounds
+ )
+ }
.map { it.pointerId }
return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
new file mode 100644
index 0000000..4c817b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.systemui.bouncer.data.repo
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Provides access to bouncer-related application state. */
+@SysUISingleton
+class BouncerRepository @Inject constructor() {
+ private val _message = MutableStateFlow<String?>(null)
+ /** The user-facing message to show in the bouncer. */
+ val message: StateFlow<String?> = _message.asStateFlow()
+
+ fun setMessage(message: String?) {
+ _message.value = message
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
new file mode 100644
index 0000000..57ce580
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.systemui.bouncer.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.dagger.qualifiers.Application
+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 dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Encapsulates business logic and application state accessing use-cases. */
+class BouncerInteractor
+@AssistedInject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Application private val applicationContext: Context,
+ private val repository: BouncerRepository,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val sceneInteractor: SceneInteractor,
+ @Assisted private val containerName: String,
+) {
+
+ /** The user-facing message to show in the bouncer. */
+ val message: StateFlow<String?> = repository.message
+
+ /**
+ * The currently-configured authentication method. This determines how the authentication
+ * challenge is completed in order to unlock an otherwise locked device.
+ */
+ val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+ authenticationInteractor.authenticationMethod
+
+ init {
+ applicationScope.launch {
+ combine(
+ sceneInteractor.currentScene(containerName),
+ authenticationInteractor.authenticationMethod,
+ ::Pair,
+ )
+ .collect { (currentScene, authMethod) ->
+ if (currentScene.key == SceneKey.Bouncer) {
+ when (authMethod) {
+ is AuthenticationMethodModel.None ->
+ sceneInteractor.setCurrentScene(
+ containerName,
+ SceneModel(SceneKey.Gone),
+ )
+ is AuthenticationMethodModel.Swipe ->
+ sceneInteractor.setCurrentScene(
+ containerName,
+ SceneModel(SceneKey.LockScreen),
+ )
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
+ *
+ * @param containerName The name of the scene container to show the bouncer in.
+ * @param message An optional message to show to the user in the bouncer.
+ */
+ fun showOrUnlockDevice(
+ containerName: String,
+ message: String? = null,
+ ) {
+ if (authenticationInteractor.isAuthenticationRequired()) {
+ repository.setMessage(message ?: promptMessage(authenticationMethod.value))
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Bouncer),
+ )
+ } else {
+ authenticationInteractor.unlockDevice()
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
+
+ /**
+ * Resets the user-facing message back to the default according to the current authentication
+ * method.
+ */
+ fun resetMessage() {
+ repository.setMessage(promptMessage(authenticationMethod.value))
+ }
+
+ /** Removes the user-facing message. */
+ fun clearMessage() {
+ repository.setMessage(null)
+ }
+
+ /**
+ * Attempts to authenticate based on the given user input.
+ *
+ * If the input is correct, the device will be unlocked and the lock screen and bouncer will be
+ * dismissed and hidden.
+ */
+ fun authenticate(
+ input: List<Any>,
+ ) {
+ val isAuthenticated = authenticationInteractor.authenticate(input)
+ if (isAuthenticated) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ } else {
+ repository.setMessage(errorMessage(authenticationMethod.value))
+ }
+ }
+
+ private fun promptMessage(authMethod: AuthenticationMethodModel): String {
+ return when (authMethod) {
+ is AuthenticationMethodModel.PIN ->
+ applicationContext.getString(R.string.keyguard_enter_your_pin)
+ is AuthenticationMethodModel.Password ->
+ applicationContext.getString(R.string.keyguard_enter_your_password)
+ is AuthenticationMethodModel.Pattern ->
+ applicationContext.getString(R.string.keyguard_enter_your_pattern)
+ else -> ""
+ }
+ }
+
+ private fun errorMessage(authMethod: AuthenticationMethodModel): String {
+ return when (authMethod) {
+ is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin)
+ is AuthenticationMethodModel.Password ->
+ applicationContext.getString(R.string.kg_wrong_password)
+ is AuthenticationMethodModel.Pattern ->
+ applicationContext.getString(R.string.kg_wrong_pattern)
+ else -> ""
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): BouncerInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
new file mode 100644
index 0000000..8a183ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.systemui.bouncer.ui.viewmodel
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Holds UI state and handles user input on bouncer UIs. */
+class BouncerViewModel
+@AssistedInject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ interactorFactory: BouncerInteractor.Factory,
+ containerName: String,
+) {
+ private val interactor: BouncerInteractor = interactorFactory.create(containerName)
+
+ /** The user-facing message to show in the bouncer. */
+ val message: StateFlow<String> =
+ interactor.message
+ .map { it ?: "" }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.message.value ?: "",
+ )
+
+ /** Notifies that the authenticate button was clicked. */
+ fun onAuthenticateButtonClicked() {
+ // TODO(b/280877228): remove this and send the real input.
+ interactor.authenticate(
+ when (interactor.authenticationMethod.value) {
+ is AuthenticationMethodModel.PIN -> listOf(1, 2, 3, 4)
+ is AuthenticationMethodModel.Password -> "password".toList()
+ is AuthenticationMethodModel.Pattern ->
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+ )
+ else -> emptyList()
+ }
+ )
+ }
+
+ /** Notifies that the emergency services button was clicked. */
+ fun onEmergencyServicesButtonClicked() {
+ // TODO(b/280877228): implement this.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 17cf808..70c859e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,8 +32,10 @@
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
+import com.android.systemui.authentication.AuthenticationModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.biometrics.FingerprintReEnrollNotification;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.dagger.UdfpsModule;
@@ -152,6 +154,7 @@
AccessibilityRepositoryModule.class,
AppOpsModule.class,
AssistModule.class,
+ AuthenticationModule.class,
BiometricsModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
@@ -289,6 +292,9 @@
@BindsOptionalOf
abstract SystemStatusAnimationScheduler optionalSystemStatusAnimationScheduler();
+ @BindsOptionalOf
+ abstract FingerprintReEnrollNotification optionalFingerprintReEnrollNotification();
+
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 46ff0d9..ddc12c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -79,7 +79,6 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -744,7 +743,7 @@
}
@Override
- public void keyguardDone(boolean strongAuth, int targetUserId) {
+ public void keyguardDone(boolean primaryAuth, int targetUserId) {
if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
return;
}
@@ -765,7 +764,7 @@
}
@Override
- public void keyguardDonePending(boolean strongAuth, int targetUserId) {
+ public void keyguardDonePending(boolean primaryAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
@@ -2219,16 +2218,6 @@
}
};
- private void keyguardDone() {
- Trace.beginSection("KeyguardViewMediator#keyguardDone");
- if (DEBUG) Log.d(TAG, "keyguardDone()");
- userActivity();
- EventLog.writeEvent(70000, 2);
- Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
- mHandler.sendMessage(msg);
- Trace.endSection();
- }
-
/**
* This handler will be associated with the policy thread, which will also
* be the UI thread of the keyguard. Since the apis of the policy, and therefore
@@ -3124,7 +3113,8 @@
Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
mWakeAndUnlocking = true;
- keyguardDone();
+ mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* primaryAuth */ false);
+ userActivity();
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
new file mode 100644
index 0000000..6170180
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.systemui.keyguard.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.qualifiers.Application
+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 com.android.systemui.util.kotlin.pairwise
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Hosts business and application state accessing logic for the lock screen scene. */
+class LockScreenSceneInteractor
+@AssistedInject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val authenticationInteractor: AuthenticationInteractor,
+ bouncerInteractorFactory: BouncerInteractor.Factory,
+ private val sceneInteractor: SceneInteractor,
+ @Assisted private val containerName: String,
+) {
+ private val bouncerInteractor: BouncerInteractor =
+ bouncerInteractorFactory.create(containerName)
+
+ /** Whether the device is currently locked. */
+ val isDeviceLocked: StateFlow<Boolean> =
+ authenticationInteractor.isUnlocked
+ .map { !it }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = !authenticationInteractor.isUnlocked.value,
+ )
+
+ /** Whether it's currently possible to swipe up to dismiss the lock screen. */
+ val isSwipeToDismissEnabled: StateFlow<Boolean> =
+ combine(
+ authenticationInteractor.isUnlocked,
+ authenticationInteractor.authenticationMethod,
+ ) { isUnlocked, authMethod ->
+ isSwipeToUnlockEnabled(
+ isUnlocked = isUnlocked,
+ authMethod = authMethod,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ isSwipeToUnlockEnabled(
+ isUnlocked = authenticationInteractor.isUnlocked.value,
+ authMethod = authenticationInteractor.authenticationMethod.value,
+ ),
+ )
+
+ init {
+ // LOCKING SHOWS LOCK SCREEN.
+ //
+ // Move to the lock screen scene if the device becomes locked while in any scene.
+ applicationScope.launch {
+ authenticationInteractor.isUnlocked
+ .map { !it }
+ .distinctUntilChanged()
+ .collect { isLocked ->
+ if (isLocked) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.LockScreen),
+ )
+ }
+ }
+ }
+
+ // BYPASS UNLOCK.
+ //
+ // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
+ // lock screen scene.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.isBypassEnabled,
+ authenticationInteractor.isUnlocked,
+ sceneInteractor.currentScene(containerName),
+ ::Triple,
+ )
+ .collect { (isBypassEnabled, isUnlocked, currentScene) ->
+ if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.LockScreen) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
+ }
+
+ // SWIPE TO DISMISS LOCK SCREEN.
+ //
+ // If switched from the lock screen to the gone scene and the auth method was a swipe,
+ // unlocks the device.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.authenticationMethod,
+ sceneInteractor.currentScene(containerName).pairwise(),
+ ::Pair,
+ )
+ .collect { (authMethod, scenes) ->
+ val (previousScene, currentScene) = scenes
+ if (
+ authMethod is AuthenticationMethodModel.Swipe &&
+ previousScene.key == SceneKey.LockScreen &&
+ currentScene.key == SceneKey.Gone
+ ) {
+ authenticationInteractor.unlockDevice()
+ }
+ }
+ }
+
+ // DISMISS LOCK SCREEN IF AUTH METHOD IS REMOVED.
+ //
+ // If the auth method becomes None while on the lock screen scene, dismisses the lock
+ // screen.
+ applicationScope.launch {
+ combine(
+ authenticationInteractor.authenticationMethod,
+ sceneInteractor.currentScene(containerName),
+ ::Pair,
+ )
+ .collect { (authMethod, scene) ->
+ if (
+ scene.key == SceneKey.LockScreen &&
+ authMethod == AuthenticationMethodModel.None
+ ) {
+ sceneInteractor.setCurrentScene(
+ containerName = containerName,
+ scene = SceneModel(SceneKey.Gone),
+ )
+ }
+ }
+ }
+ }
+
+ /** Attempts to dismiss the lock screen. This will cause the bouncer to show, if needed. */
+ fun dismissLockScreen() {
+ bouncerInteractor.showOrUnlockDevice(containerName = containerName)
+ }
+
+ private fun isSwipeToUnlockEnabled(
+ isUnlocked: Boolean,
+ authMethod: AuthenticationMethodModel,
+ ): Boolean {
+ return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): LockScreenSceneInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
new file mode 100644
index 0000000..08b9613
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state and handles user input for the lock screen scene. */
+class LockScreenSceneViewModel
+@AssistedInject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ interactorFactory: LockScreenSceneInteractor.Factory,
+ @Assisted containerName: String,
+) {
+ private val interactor: LockScreenSceneInteractor = interactorFactory.create(containerName)
+
+ /** The icon for the "lock" button on the lock screen. */
+ val lockButtonIcon: StateFlow<Icon> =
+ interactor.isDeviceLocked
+ .map { isLocked -> lockIcon(isLocked = isLocked) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value),
+ )
+
+ /** The key of the scene we should switch to when swiping up. */
+ val upDestinationSceneKey: StateFlow<SceneKey> =
+ interactor.isSwipeToDismissEnabled
+ .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value),
+ )
+
+ /** Notifies that the lock button on the lock screen was clicked. */
+ fun onLockButtonClicked() {
+ interactor.dismissLockScreen()
+ }
+
+ /** Notifies that some content on the lock screen was clicked. */
+ fun onContentClicked() {
+ interactor.dismissLockScreen()
+ }
+
+ private fun upDestinationSceneKey(
+ isSwipeToUnlockEnabled: Boolean,
+ ): SceneKey {
+ return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+ }
+
+ private fun lockIcon(
+ isLocked: Boolean,
+ ): Icon {
+ return Icon.Resource(
+ res =
+ if (isLocked) {
+ R.drawable.ic_device_lock_on
+ } else {
+ R.drawable.ic_device_lock_off
+ },
+ contentDescription =
+ ContentDescription.Resource(
+ res =
+ if (isLocked) {
+ R.string.accessibility_lock_icon
+ } else {
+ R.string.accessibility_unlock_button
+ }
+ )
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): LockScreenSceneViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
new file mode 100644
index 0000000..6525a98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.systemui.qs.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Models UI state and handles user input for the quick settings scene. */
+class QuickSettingsSceneViewModel
+@AssistedInject
+constructor(
+ lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+ @Assisted containerName: String,
+) {
+ private val lockScreenSceneInteractor: LockScreenSceneInteractor =
+ lockScreenSceneInteractorFactory.create(containerName)
+
+ /** Notifies that some content in quick settings was clicked. */
+ fun onContentClicked() {
+ lockScreenSceneInteractor.dismissLockScreen()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): QuickSettingsSceneViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index dc3c820..6912114 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -16,12 +16,16 @@
package com.android.systemui.reardisplay;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerGlobal;
import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
@@ -70,6 +74,7 @@
@VisibleForTesting
SystemUIDialog mRearDisplayEducationDialog;
+ @Nullable LinearLayout mDialogViewContainer;
@Inject
public RearDisplayDialogController(Context context, CommandQueue commandQueue,
@@ -90,26 +95,51 @@
createAndShowDialog();
}
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
+ && mDialogViewContainer != null) {
+ // Refresh the dialog view when configuration is changed.
+ Context dialogContext = mRearDisplayEducationDialog.getContext();
+ View dialogView = createDialogView(dialogContext);
+ mDialogViewContainer.removeAllViews();
+ mDialogViewContainer.addView(dialogView);
+ }
+ }
+
private void createAndShowDialog() {
mServiceNotified = false;
Context dialogContext = mRearDisplayEducationDialog.getContext();
+ View dialogView = createDialogView(dialogContext);
+
+ mDialogViewContainer = new LinearLayout(dialogContext);
+ mDialogViewContainer.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ mDialogViewContainer.setOrientation(LinearLayout.VERTICAL);
+ mDialogViewContainer.addView(dialogView);
+
+ mRearDisplayEducationDialog.setView(mDialogViewContainer);
+
+ configureDialogButtons();
+
+ mRearDisplayEducationDialog.show();
+ }
+
+ private View createDialogView(Context context) {
View dialogView;
if (mStartedFolded) {
- dialogView = View.inflate(dialogContext,
+ dialogView = View.inflate(context,
R.layout.activity_rear_display_education, null);
} else {
- dialogView = View.inflate(dialogContext,
+ dialogView = View.inflate(context,
R.layout.activity_rear_display_education_opened, null);
}
LottieAnimationView animationView = dialogView.findViewById(
R.id.rear_display_folded_animation);
animationView.setRepeatCount(mAnimationRepeatCount);
- mRearDisplayEducationDialog.setView(dialogView);
-
- configureDialogButtons();
-
- mRearDisplayEducationDialog.show();
+ return dialogView;
}
/**
@@ -164,6 +194,7 @@
mServiceNotified = true;
mDeviceStateManagerGlobal.unregisterDeviceStateCallback(mDeviceStateManagerCallback);
mDeviceStateManagerGlobal.onStateRequestOverlayDismissed(shouldCancelRequest);
+ mDialogViewContainer = null;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 46f1210..0c800d4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -333,7 +333,7 @@
mLpChanged.preferredMinDisplayRefreshRate = 0;
}
Trace.setCounter("display_set_preferred_refresh_rate",
- (long) mKeyguardPreferredRefreshRate);
+ (long) mLpChanged.preferredMaxDisplayRefreshRate);
} else if (mKeyguardMaxRefreshRate > 0) {
boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
&& state.statusBarState == StatusBarState.KEYGUARD
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
new file mode 100644
index 0000000..dcae258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.shade.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state and handles user input for the shade scene. */
+class ShadeSceneViewModel
+@AssistedInject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+ @Assisted private val containerName: String,
+) {
+ private val lockScreenInteractor: LockScreenSceneInteractor =
+ lockScreenSceneInteractorFactory.create(containerName)
+
+ /** The key of the scene we should switch to when swiping up. */
+ val upDestinationSceneKey: StateFlow<SceneKey> =
+ lockScreenInteractor.isDeviceLocked
+ .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ upDestinationSceneKey(
+ isLocked = lockScreenInteractor.isDeviceLocked.value,
+ ),
+ )
+
+ /** Notifies that some content in the shade was clicked. */
+ fun onContentClicked() {
+ lockScreenInteractor.dismissLockScreen()
+ }
+
+ private fun upDestinationSceneKey(
+ isLocked: Boolean,
+ ): SceneKey {
+ return if (isLocked) SceneKey.LockScreen else SceneKey.Gone
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ containerName: String,
+ ): ShadeSceneViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index bd7840d..2d8f371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -469,13 +469,13 @@
case MODE_DISMISS_BOUNCER:
Trace.beginSection("MODE_DISMISS_BOUNCER");
mKeyguardViewController.notifyKeyguardAuthenticated(
- false /* strongAuth */);
+ false /* primaryAuth */);
Trace.endSection();
break;
case MODE_UNLOCK_COLLAPSING:
Trace.beginSection("MODE_UNLOCK_COLLAPSING");
mKeyguardViewController.notifyKeyguardAuthenticated(
- false /* strongAuth */);
+ false /* primaryAuth */);
Trace.endSection();
break;
case MODE_SHOW_BOUNCER:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
new file mode 100644
index 0000000..ce88a5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.systemui.statusbar.phone.ongoingcall
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.animation.view.LaunchableLinearLayout
+
+/**
+ * A container view for the ongoing call chip background. Needed so that we can limit the height of
+ * the background when the font size is very large (200%), in which case the background would go
+ * past the bounds of the status bar.
+ */
+class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) :
+ LaunchableLinearLayout(context, attrs) {
+
+ /** Sets where this view should fetch its max height from. */
+ var maxHeightFetcher: (() -> Int)? = null
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val maxHeight = maxHeightFetcher?.invoke()
+ val chosenHeight =
+ if (maxHeight != null) {
+ // Give 1 extra px of space (without it, the background could still be cut off)
+ measuredHeight.coerceAtMost(maxHeight - 1)
+ } else {
+ measuredHeight
+ }
+ setMeasuredDimension(measuredWidth, chosenHeight)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c73cc9e..b3af91d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -136,6 +136,9 @@
fun setChipView(chipView: View) {
tearDownChipView()
this.chipView = chipView
+ val backgroundView: OngoingCallBackgroundContainer? =
+ chipView.findViewById(R.id.ongoing_call_chip_background)
+ backgroundView?.maxHeightFetcher = { statusBarWindowController.get().statusBarHeight }
if (hasOngoingCall()) {
updateChip()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
new file mode 100644
index 0000000..2e62beb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.systemui.authentication.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthenticationInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val repository: AuthenticationRepository = AuthenticationRepositoryImpl()
+ private val underTest =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = repository,
+ )
+
+ @Test
+ fun authMethod() =
+ testScope.runTest {
+ val authMethod by collectLastValue(underTest.authenticationMethod)
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234))
+
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
+ }
+
+ @Test
+ fun isUnlocked_whenAuthMethodIsNone_isTrue() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun unlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+
+ underTest.unlockDevice()
+ runCurrent()
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun biometricUnlock() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+
+ underTest.biometricUnlock()
+ runCurrent()
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun toggleBypassEnabled() =
+ testScope.runTest {
+ val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
+ assertThat(isBypassEnabled).isFalse()
+
+ underTest.toggleBypassEnabled()
+ assertThat(isBypassEnabled).isTrue()
+
+ underTest.toggleBypassEnabled()
+ assertThat(isBypassEnabled).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndSecured_true() =
+ testScope.runTest {
+ underTest.lockDevice()
+ runCurrent()
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+
+ assertThat(underTest.isAuthenticationRequired()).isTrue()
+ }
+
+ @Test
+ fun isAuthenticationRequired_lockedAndNotSecured_false() =
+ testScope.runTest {
+ underTest.lockDevice()
+ runCurrent()
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndSecured_false() =
+ testScope.runTest {
+ underTest.unlockDevice()
+ runCurrent()
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticationRequired_unlockedAndNotSecured_false() =
+ testScope.runTest {
+ underTest.unlockDevice()
+ runCurrent()
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(underTest.isAuthenticationRequired()).isFalse()
+ }
+
+ @Test
+ fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(underTest.authenticate("password".toList())).isTrue()
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(underTest.authenticate("alohomora".toList())).isFalse()
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 0,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 1,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 2,
+ ),
+ )
+ )
+ )
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(
+ underTest.authenticate(
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 0,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 1,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 2,
+ ),
+ )
+ )
+ )
+ .isTrue()
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ underTest.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 0,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 1,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 0,
+ y = 2,
+ ),
+ )
+ )
+ )
+ assertThat(isUnlocked).isFalse()
+
+ assertThat(
+ underTest.authenticate(
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 2,
+ y = 0,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 2,
+ y = 1,
+ ),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = 2,
+ y = 2,
+ ),
+ )
+ )
+ )
+ .isFalse()
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun unlocksDevice_whenAuthMethodBecomesNone() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+
+ repository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(isUnlocked).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index b8bca3a..38c9caf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -29,7 +29,6 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
@@ -51,6 +50,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -64,11 +65,16 @@
KeyguardStateController mKeyguardStateController;
@Mock
NotificationManager mNotificationManager;
+ @Mock
+ Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional;
+ @Mock
+ FingerprintReEnrollNotification mFingerprintReEnrollNotification;
private static final String TAG = "BiometricNotificationService";
private static final int FACE_NOTIFICATION_ID = 1;
private static final int FINGERPRINT_NOTIFICATION_ID = 2;
private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
+ private static final int FINGERPRINT_ACQUIRED_RE_ENROLL = 0;
private final ArgumentCaptor<Notification> mNotificationArgumentCaptor =
ArgumentCaptor.forClass(Notification.class);
@@ -78,6 +84,11 @@
@Before
public void setUp() {
+ when(mFingerprintReEnrollNotificationOptional.orElse(any()))
+ .thenReturn(mFingerprintReEnrollNotification);
+ when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
+ FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
+
mLooper = TestableLooper.get(this);
Handler handler = new Handler(mLooper.getLooper());
BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory();
@@ -87,7 +98,8 @@
new BiometricNotificationService(mContext,
mKeyguardUpdateMonitor, mKeyguardStateController, handler,
mNotificationManager,
- broadcastReceiver);
+ broadcastReceiver,
+ mFingerprintReEnrollNotificationOptional);
biometricNotificationService.start();
ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
@@ -108,8 +120,8 @@
public void testShowFingerprintReEnrollNotification() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
- mKeyguardUpdateMonitorCallback.onBiometricError(
- BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ FINGERPRINT_ACQUIRED_RE_ENROLL,
"Testing Fingerprint Re-enrollment" /* errString */,
BiometricSourceType.FINGERPRINT
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
index 4f89b69..da55d5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
@@ -33,7 +33,7 @@
@Test
fun isGoodOverlap() {
val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
- val actual = underTest.isGoodOverlap(touchData, SENSOR)
+ val actual = underTest.isGoodOverlap(touchData, SENSOR, OVERLAY)
assertThat(actual).isEqualTo(testCase.expected)
}
@@ -50,8 +50,10 @@
validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
),
genNegativeTestCases(
- invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
- invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ invalidXs =
+ listOf(SENSOR.left - 1, SENSOR.right + 1, OVERLAY.left, OVERLAY.right),
+ invalidYs =
+ listOf(SENSOR.top - 1, SENSOR.bottom + 1, OVERLAY.top, OVERLAY.bottom),
validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
)
@@ -82,6 +84,7 @@
)
private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
private fun genTestCases(
xs: List<Int>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index fb3c185..317141b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -43,7 +43,7 @@
minor = testCase.minor,
major = testCase.major
)
- val actual = underTest.isGoodOverlap(touchData, SENSOR)
+ val actual = underTest.isGoodOverlap(touchData, SENSOR, OVERLAY)
assertThat(actual).isEqualTo(testCase.expected)
}
@@ -71,8 +71,10 @@
expected = true
),
genNegativeTestCase(
- outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
- outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ outerXs =
+ listOf(SENSOR.left - 1, SENSOR.right + 1, OVERLAY.left, OVERLAY.right),
+ outerYs =
+ listOf(SENSOR.top - 1, SENSOR.bottom + 1, OVERLAY.top, OVERLAY.bottom),
minor = 100f,
major = 100f,
expected = false
@@ -104,6 +106,7 @@
)
private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
+private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
private fun genPositiveTestCases(
innerXs: List<Int>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
index 834d0a6..3e5c43a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
@@ -16,7 +16,7 @@
@Test
fun isWithinSensor() {
val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
- val actual = touchData.isWithinSensor(SENSOR)
+ val actual = touchData.isWithinBounds(SENSOR)
assertThat(actual).isEqualTo(testCase.expected)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
new file mode 100644
index 0000000..7dd376e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.systemui.bouncer.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+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 com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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 BouncerInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val authenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val underTest =
+ BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = "container1",
+ )
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
+ overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
+ overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
+ overrideResource(R.string.kg_wrong_pin, MESSAGE_WRONG_PIN)
+ overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
+ overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
+ }
+
+ @Test
+ fun pinAuthMethod() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val message by collectLastValue(underTest.message)
+
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ underTest.showOrUnlockDevice("container1")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+
+ underTest.clearMessage()
+ assertThat(message).isNull()
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+
+ // Wrong input.
+ underTest.authenticate(listOf(9, 8, 7))
+ assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+
+ // Correct input.
+ underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun passwordAuthMethod() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val message by collectLastValue(underTest.message)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ underTest.showOrUnlockDevice("container1")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
+
+ underTest.clearMessage()
+ assertThat(message).isNull()
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
+
+ // Wrong input.
+ underTest.authenticate("alohamora".toList())
+ assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
+
+ // Correct input.
+ underTest.authenticate("password".toList())
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun patternAuthMethod() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val message by collectLastValue(underTest.message)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(emptyList())
+ )
+ authenticationInteractor.lockDevice()
+ underTest.showOrUnlockDevice("container1")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
+
+ underTest.clearMessage()
+ assertThat(message).isNull()
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
+
+ // Wrong input.
+ underTest.authenticate(
+ listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+ )
+ assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.resetMessage()
+ assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
+
+ // Correct input.
+ underTest.authenticate(emptyList())
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.showOrUnlockDevice("container1")
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.lockDevice()
+
+ underTest.showOrUnlockDevice("container1")
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun showOrUnlockDevice_customMessageShown() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+ val message by collectLastValue(underTest.message)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+
+ val customMessage = "Hello there!"
+ underTest.showOrUnlockDevice("container1", customMessage)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ assertThat(message).isEqualTo(customMessage)
+ }
+
+ companion object {
+ private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
+ private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
+ private const val MESSAGE_ENTER_YOUR_PATTERN = "Enter your pattern"
+ private const val MESSAGE_WRONG_PIN = "Wrong PIN"
+ private const val MESSAGE_WRONG_PASSWORD = "Wrong password"
+ private const val MESSAGE_WRONG_PATTERN = "Wrong pattern"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 47df64f..0a9618c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -113,8 +113,8 @@
@After
fun tearDown() {
- keyguardUnlockAnimationController.surfaceBehindEntryAnimator.cancel()
- keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.cancel()
+ keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
+ keyguardUnlockAnimationController.wallpaperAlphaAnimator.cancel()
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 83c89f1..489dc4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -519,6 +519,12 @@
}
@Test
+ public void testWakeAndUnlocking() {
+ mViewMediator.onWakeAndUnlocking();
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+ }
+
+ @Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testDoKeyguardWhileInteractive_resets() {
mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
new file mode 100644
index 0000000..749e7a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.systemui.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+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 com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LockScreenSceneInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+ private val underTest =
+ LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+
+ @Test
+ fun isDeviceLocked() =
+ testScope.runTest {
+ val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
+
+ mAuthenticationInteractor.lockDevice()
+ assertThat(isDeviceLocked).isTrue()
+
+ mAuthenticationInteractor.unlockDevice()
+ assertThat(isDeviceLocked).isFalse()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isTrue()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ mAuthenticationInteractor.unlockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isFalse()
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.unlockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ underTest.dismissLockScreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ runCurrent()
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ if (!mAuthenticationInteractor.isBypassEnabled.value) {
+ mAuthenticationInteractor.toggleBypassEnabled()
+ }
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ if (mAuthenticationInteractor.isBypassEnabled.value) {
+ mAuthenticationInteractor.toggleBypassEnabled()
+ }
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Shade))
+ runCurrent()
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.QuickSettings))
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
new file mode 100644
index 0000000..d335b09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+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 com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LockScreenSceneViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+
+ private val underTest =
+ LockScreenSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ interactorFactory =
+ object : LockScreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockScreenSceneInteractor {
+ return LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+ }
+ },
+ containerName = CONTAINER_NAME
+ )
+
+ @Test
+ fun lockButtonIcon_whenLocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ mAuthenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_on)
+ }
+
+ @Test
+ fun lockButtonIcon_whenUnlocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ mAuthenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ mAuthenticationInteractor.unlockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_off)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onContentClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
new file mode 100644
index 0000000..e8875be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.qs.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+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 com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class QuickSettingsSceneViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+
+ private val underTest =
+ QuickSettingsSceneViewModel(
+ lockScreenSceneInteractorFactory =
+ object : LockScreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockScreenSceneInteractor {
+ return LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+ }
+ },
+ containerName = CONTAINER_NAME
+ )
+
+ @Test
+ fun onContentClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index 9acd47e..55813f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -17,8 +17,10 @@
package com.android.systemui.reardisplay;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
+import android.content.res.Configuration;
import android.hardware.devicestate.DeviceStateManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -68,6 +70,27 @@
}
@Test
+ public void testClosedDialogIsRefreshedOnConfigurationChange() {
+ RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
+ mCommandQueue, mFakeExecutor);
+ controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
+ controller.setFoldedStates(new int[]{0});
+ controller.setAnimationRepeatCount(0);
+
+ controller.showRearDisplayDialog(CLOSED_BASE_STATE);
+ assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+ TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_title_text_view);
+
+ controller.onConfigurationChanged(new Configuration());
+ assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+ TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_title_text_view);
+
+ assertNotSame(deviceClosedTitleTextView, deviceClosedTitleTextView2);
+ }
+
+ @Test
public void testOpenDialogIsShown() {
RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
mCommandQueue, mFakeExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
new file mode 100644
index 0000000..688cce8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.systemui.shade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
+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 com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShadeSceneViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val sceneInteractor =
+ SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ private val mAuthenticationInteractor =
+ AuthenticationInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = AuthenticationRepositoryImpl(),
+ )
+
+ private val underTest =
+ ShadeSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ lockScreenSceneInteractorFactory =
+ object : LockScreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockScreenSceneInteractor {
+ return LockScreenSceneInteractor(
+ applicationScope = testScope.backgroundScope,
+ authenticationInteractor = mAuthenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = testScope.backgroundScope,
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = mAuthenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = containerName,
+ )
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_NAME,
+ )
+ }
+ },
+ containerName = CONTAINER_NAME
+ )
+
+ @Test
+ fun upTransitionSceneKey_deviceLocked_lockScreen() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.LockScreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_deviceUnlocked_gone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun onContentClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ mAuthenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
new file mode 100644
index 0000000..ec074d7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.systemui.statusbar.phone.ongoingcall
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class OngoingCallBackgroundContainerTest : SysuiTestCase() {
+
+ private lateinit var underTest: OngoingCallBackgroundContainer
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ TestableLooper.get(this).runWithLooper {
+ val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null)
+ underTest = chipView.requireViewById(R.id.ongoing_call_chip_background)
+ }
+ }
+
+ @Test
+ fun onMeasure_maxHeightFetcherNotSet_usesDesired() {
+ underTest.maxHeightFetcher = null
+
+ underTest.measure(
+ WIDTH_SPEC,
+ View.MeasureSpec.makeMeasureSpec(123, View.MeasureSpec.EXACTLY),
+ )
+
+ assertThat(underTest.measuredHeight).isEqualTo(123)
+ }
+
+ @Test
+ fun onMeasure_maxLargerThanDesired_usesDesired() {
+ underTest.maxHeightFetcher = { 234 }
+
+ underTest.measure(
+ WIDTH_SPEC,
+ View.MeasureSpec.makeMeasureSpec(123, View.MeasureSpec.EXACTLY),
+ )
+
+ assertThat(underTest.measuredHeight).isEqualTo(123)
+ }
+
+ @Test
+ fun onMeasure_desiredLargerThanMax_usesMaxMinusOne() {
+ underTest.maxHeightFetcher = { 234 }
+
+ underTest.measure(
+ WIDTH_SPEC,
+ View.MeasureSpec.makeMeasureSpec(567, View.MeasureSpec.EXACTLY),
+ )
+
+ // We use the max - 1 to give a bit extra space
+ assertThat(underTest.measuredHeight).isEqualTo(233)
+ }
+
+ private companion object {
+ val WIDTH_SPEC = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
index 1bdee36..e3e7933 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
@@ -21,7 +21,11 @@
class FakeOverlapDetector : OverlapDetector {
var shouldReturn: Map<Int, Boolean> = mapOf()
- override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
+ override fun isGoodOverlap(
+ touchData: NormalizedTouchData,
+ nativeSensorBounds: Rect,
+ nativeOverlayBounds: Rect
+ ): Boolean {
return shouldReturn[touchData.pointerId]
?: error("Unexpected PointerId not declared in TestCase currentPointers")
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index af6b24e..2d60716 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -558,10 +558,14 @@
try {
final Intent onClickIntent;
- if (provider.maskedBySuspendedPackage) {
+ if (provider.maskedByQuietProfile) {
+ showBadge = true;
+ onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
+ } else if (provider.maskedBySuspendedPackage) {
showBadge = mUserManager.hasBadge(appUserId);
final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
appInfo.packageName, appUserId);
+ // TODO(b/281839596): don't rely on platform always meaning suspended by admin.
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
appUserId, true);
@@ -575,9 +579,6 @@
appInfo.packageName, suspendingPackage, dialogInfo, null, null,
appUserId);
}
- } else if (provider.maskedByQuietProfile) {
- showBadge = true;
- onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
} else /* provider.maskedByLockedProfile */ {
showBadge = true;
onClickIntent = mKeyguardManager
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index bc4e8df..71401f4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -209,7 +209,6 @@
private void init() {
setupMessaging(mContext);
- initAudioHalBluetoothState();
initRoutingStrategyIds();
mPreferredCommunicationDevice = null;
updateActiveCommunicationDevice();
@@ -865,100 +864,10 @@
}
}
- // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
- @GuardedBy("mDeviceStateLock")
+ /**
+ * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
+ */
private boolean mBluetoothScoOn;
- // value of BT_SCO parameter currently applied to audio HAL.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothScoOnApplied;
-
- // A2DP suspend state requested by AudioManager.setA2dpSuspended() API.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothA2dpSuspendedExt;
- // A2DP suspend state requested by AudioDeviceInventory.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothA2dpSuspendedInt;
- // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothA2dpSuspendedApplied;
-
- // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothLeSuspendedExt;
- // LE Audio suspend state requested by AudioDeviceInventory.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothLeSuspendedInt;
- // value of LeAudioSuspended parameter currently applied to audio HAL.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothLeSuspendedApplied;
-
- private void initAudioHalBluetoothState() {
- mBluetoothScoOnApplied = false;
- AudioSystem.setParameters("BT_SCO=off");
- mBluetoothA2dpSuspendedApplied = false;
- AudioSystem.setParameters("A2dpSuspended=false");
- mBluetoothLeSuspendedApplied = false;
- AudioSystem.setParameters("LeAudioSuspended=false");
- }
-
- @GuardedBy("mDeviceStateLock")
- private void updateAudioHalBluetoothState() {
- if (mBluetoothScoOn != mBluetoothScoOnApplied) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: "
- + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied);
- }
- if (mBluetoothScoOn) {
- if (!mBluetoothA2dpSuspendedApplied) {
- AudioSystem.setParameters("A2dpSuspended=true");
- mBluetoothA2dpSuspendedApplied = true;
- }
- if (!mBluetoothLeSuspendedApplied) {
- AudioSystem.setParameters("LeAudioSuspended=true");
- mBluetoothLeSuspendedApplied = true;
- }
- AudioSystem.setParameters("BT_SCO=on");
- } else {
- AudioSystem.setParameters("BT_SCO=off");
- }
- mBluetoothScoOnApplied = mBluetoothScoOn;
- }
- if (!mBluetoothScoOnApplied) {
- if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt)
- != mBluetoothA2dpSuspendedApplied) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: "
- + mBluetoothA2dpSuspendedExt
- + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
- + ", mBluetoothA2dpSuspendedApplied: "
- + mBluetoothA2dpSuspendedApplied);
- }
- mBluetoothA2dpSuspendedApplied =
- mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt;
- if (mBluetoothA2dpSuspendedApplied) {
- AudioSystem.setParameters("A2dpSuspended=true");
- } else {
- AudioSystem.setParameters("A2dpSuspended=false");
- }
- }
- if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt)
- != mBluetoothLeSuspendedApplied) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: "
- + mBluetoothLeSuspendedExt
- + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt
- + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied);
- }
- mBluetoothLeSuspendedApplied =
- mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt;
- if (mBluetoothLeSuspendedApplied) {
- AudioSystem.setParameters("LeAudioSuspended=true");
- } else {
- AudioSystem.setParameters("LeAudioSuspended=false");
- }
- }
- }
- }
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
@@ -966,76 +875,10 @@
}
synchronized (mDeviceStateLock) {
mBluetoothScoOn = on;
- updateAudioHalBluetoothState();
postUpdateCommunicationRouteClient(eventSource);
}
}
- /*package*/ void postSetA2dpSuspended(boolean enable, String eventSource) {
- sendILMsgNoDelay(MSG_IL_SET_A2DP_SUSPENDED, SENDMSG_QUEUE, (enable ? 1 : 0), eventSource);
- }
-
- /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: "
- + enable + ", internal: " + internal
- + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
- + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt);
- }
- synchronized (mDeviceStateLock) {
- if (internal) {
- mBluetoothA2dpSuspendedInt = enable;
- } else {
- mBluetoothA2dpSuspendedExt = enable;
- }
- updateAudioHalBluetoothState();
- }
- }
-
- /*package*/ void clearA2dpSuspended() {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "clearA2dpSuspended");
- }
- synchronized (mDeviceStateLock) {
- mBluetoothA2dpSuspendedInt = false;
- mBluetoothA2dpSuspendedExt = false;
- updateAudioHalBluetoothState();
- }
- }
-
- /*package*/ void postSetLeAudioSuspended(boolean enable, String eventSource) {
- sendILMsgNoDelay(
- MSG_IL_SET_LEAUDIO_SUSPENDED, SENDMSG_QUEUE, (enable ? 1 : 0), eventSource);
- }
-
- /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: "
- + enable + ", internal: " + internal
- + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt
- + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt);
- }
- synchronized (mDeviceStateLock) {
- if (internal) {
- mBluetoothLeSuspendedInt = enable;
- } else {
- mBluetoothLeSuspendedExt = enable;
- }
- updateAudioHalBluetoothState();
- }
- }
-
- /*package*/ void clearLeAudioSuspended() {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "clearLeAudioSuspended");
- }
- synchronized (mDeviceStateLock) {
- mBluetoothLeSuspendedInt = false;
- mBluetoothLeSuspendedExt = false;
- updateAudioHalBluetoothState();
- }
- }
-
/*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.startWatchingRoutes(observer);
@@ -1821,12 +1664,6 @@
final int capturePreset = msg.arg1;
mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset);
} break;
- case MSG_IL_SET_A2DP_SUSPENDED: {
- setA2dpSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
- } break;
- case MSG_IL_SET_LEAUDIO_SUSPENDED: {
- setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
- } break;
case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
@@ -1905,8 +1742,6 @@
private static final int MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY = 47;
private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48;
private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
- private static final int MSG_IL_SET_A2DP_SUSPENDED = 50;
- private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
@@ -2185,6 +2020,12 @@
"updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
+ if (preferredCommunicationDevice == null
+ || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+ AudioSystem.setParameters("BT_SCO=off");
+ } else {
+ AudioSystem.setParameters("BT_SCO=on");
+ }
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
if (defaultDevice != null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 1d8bef1..b70c3e4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1478,7 +1478,7 @@
}
// Reset A2DP suspend state each time a new sink is connected
- mDeviceBroker.clearA2dpSuspended();
+ mAudioSystem.setParameters("A2dpSuspended=false");
// The convention for head tracking sensors associated with A2DP devices is to
// use a UUID derived from the MAC address as follows:
@@ -1751,8 +1751,7 @@
private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
// prevent any activity on the A2DP audio output to avoid unwanted
// reconnection of the sink.
- mDeviceBroker.setA2dpSuspended(
- true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater");
+ mAudioSystem.setParameters("A2dpSuspended=true");
// retrieve DeviceInfo before removing device
final String deviceKey =
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -1899,7 +1898,7 @@
"LE Audio device addr=" + address + " now available").printLog(TAG));
}
// Reset LEA suspend state each time a new sink is connected
- mDeviceBroker.clearLeAudioSuspended();
+ mAudioSystem.setParameters("LeAudioSuspended=false");
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
@@ -1954,8 +1953,7 @@
private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
// prevent any activity on the LEA output to avoid unwanted
// reconnection of the sink.
- mDeviceBroker.setLeAudioSuspended(
- true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater");
+ mAudioSystem.setParameters("LeAudioSuspended=true");
// the device will be made unavailable later, so consider it disconnected right away
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
// send the delayed message to make the device unavailable later
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a721873..24eba76 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6412,26 +6412,6 @@
mDeviceBroker.setBluetoothScoOn(on, eventSource);
}
- /** @see AudioManager#setA2dpSuspended(boolean) */
- @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
- public void setA2dpSuspended(boolean enable) {
- super.setA2dpSuspended_enforcePermission();
- final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable)
- .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
- .append(Binder.getCallingPid()).toString();
- mDeviceBroker.postSetA2dpSuspended(enable, eventSource);
- }
-
- /** @see AudioManager#setA2dpSuspended(boolean) */
- @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
- public void setLeAudioSuspended(boolean enable) {
- super.setLeAudioSuspended_enforcePermission();
- final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable)
- .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
- .append(Binder.getCallingPid()).toString();
- mDeviceBroker.postSetLeAudioSuspended(enable, eventSource);
- }
-
/** @see AudioManager#isBluetoothScoOn()
* Note that it doesn't report internal state, but state seen by apps (which may have
* called setBluetoothScoOn() */
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index e46c3cc..bfa6c36e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -445,8 +445,8 @@
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- mDeviceBroker.clearA2dpSuspended();
- mDeviceBroker.clearLeAudioSuspended();
+ AudioSystem.setParameters("A2dpSuspended=false");
+ AudioSystem.setParameters("LeAudioSuspended=false");
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index fb978b2..b474cad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -555,7 +555,7 @@
for (BiometricSchedulerOperation pendingOperation : mPendingOperations) {
Slog.d(getTag(), "[Watchdog cancelling pending] "
+ pendingOperation.getClientMonitor());
- pendingOperation.markCanceling();
+ pendingOperation.markCancelingForWatchdog();
}
Slog.d(getTag(), "[Watchdog cancelling current] "
+ mCurrentOperation.getClientMonitor());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 4825f1d..57feedc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -267,13 +267,17 @@
/** Flags this operation as canceled, if possible, but does not cancel it until started. */
public boolean markCanceling() {
- if (mState == STATE_WAITING_IN_QUEUE) {
+ if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
mState = STATE_WAITING_IN_QUEUE_CANCELING;
return true;
}
return false;
}
+ @VisibleForTesting void markCancelingForWatchdog() {
+ mState = STATE_WAITING_IN_QUEUE_CANCELING;
+ }
+
/**
* Cancel the operation now.
*
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c678a92..4208a12 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3677,8 +3677,11 @@
encapType = IkeSessionParams.ESP_ENCAP_TYPE_NONE;
break;
default:
- ipVersion = IkeSessionParams.ESP_IP_VERSION_AUTO;
- encapType = IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+ // By default, PREFERRED_IKE_PROTOCOL_IPV4_UDP is used for safety. This is
+ // because some carriers' networks do not support IPv6 very well, and using
+ // IPv4 can help to prevent problems.
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_IPV4;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_UDP;
break;
}
return new CarrierConfigInfo(mccMnc, natKeepalive, encapType, ipVersion);
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index d7563e0..d764ec4 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -17,11 +17,14 @@
package com.android.server.hdmi;
/**
- * Action to query and track the audio status of the System Audio device when enabling or using
- * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
- * 1. When enabling AVC: queries the starting audio status of the System Audio device and
- * enables the feature upon receiving a response.
- * 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
+ * Action to query and track the audio status of the System Audio device when using
+ * absolute volume behavior, or adjust-only absolute volume behavior. Must be removed when
+ * neither behavior is used.
+ *
+ * Performs two main functions:
+ * 1. When enabling AVB: queries the starting audio status of the System Audio device and
+ * adopts the appropriate volume behavior upon receiving a response.
+ * 2. While AVB is enabled: monitors <Report Audio Status> messages from the System Audio device and
* notifies AudioService if the audio status changes.
*/
final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
@@ -74,16 +77,23 @@
boolean mute = HdmiUtils.isAudioStatusMute(cmd);
int volume = HdmiUtils.getAudioStatusVolume(cmd);
+
+ // If the volume is out of range, report it as handled and ignore the message.
+ // According to the spec, such values are either reserved or indicate an unknown volume.
+ if (volume == Constants.UNKNOWN_VOLUME) {
+ return true;
+ }
+
AudioStatus audioStatus = new AudioStatus(volume, mute);
if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
- localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
+ localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
- localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
+ localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
}
if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
- localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
+ localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
}
}
mLastAudioStatus = audioStatus;
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
index 438c1ea..9484204 100644
--- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -16,9 +16,11 @@
package com.android.server.hdmi;
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
-import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceVolumeManager;
import android.media.VolumeInfo;
@@ -26,42 +28,48 @@
import java.util.concurrent.Executor;
/**
- * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
- * passes method calls to that instance.
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
+ * Allows the class to be faked for tests.
+ *
+ * See implementations {@link DefaultAudioDeviceVolumeManagerWrapper} and
+ * {@link FakeAudioFramework.FakeAudioDeviceVolumeManagerWrapper}.
*/
-public class AudioDeviceVolumeManagerWrapper
- implements AudioDeviceVolumeManagerWrapperInterface {
+public interface AudioDeviceVolumeManagerWrapper {
- private static final String TAG = "AudioDeviceVolumeManagerWrapper";
-
- private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
-
- public AudioDeviceVolumeManagerWrapper(Context context) {
- mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
- }
-
- @Override
- public void addOnDeviceVolumeBehaviorChangedListener(
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
+ * Executor, OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void addOnDeviceVolumeBehaviorChangedListener(
@NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
- throws SecurityException {
- mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
- }
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
- @Override
- public void removeOnDeviceVolumeBehaviorChangedListener(
- @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
- mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
- }
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
+ * OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
- @Override
- public void setDeviceAbsoluteVolumeBehavior(
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
+ * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ */
+ void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
- mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
- vclistener, handlesVolumeAdjustment);
- }
+ boolean handlesVolumeAdjustment);
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ */
+ void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment);
}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
deleted file mode 100644
index 1a1d4c1..0000000
--- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.hdmi;
-
-import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
-import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceVolumeManager;
-import android.media.VolumeInfo;
-
-import java.util.concurrent.Executor;
-
-/**
- * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
- * Allows the class to be faked for tests.
- */
-public interface AudioDeviceVolumeManagerWrapperInterface {
-
- /**
- * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
- * Executor, OnDeviceVolumeBehaviorChangedListener)}
- */
- void addOnDeviceVolumeBehaviorChangedListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
-
- /**
- * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
- * OnDeviceVolumeBehaviorChangedListener)}
- */
- void removeOnDeviceVolumeBehaviorChangedListener(
- @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
-
- /**
- * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
- * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
- */
- void setDeviceAbsoluteVolumeBehavior(
- @NonNull AudioDeviceAttributes device,
- @NonNull VolumeInfo volume,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment);
-}
diff --git a/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java
new file mode 100644
index 0000000..fd4dd51
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java
@@ -0,0 +1,105 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+
+import java.util.List;
+
+/**
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI control
+ * framework. Allows the class to be faked for tests.
+ *
+ * See implementations {@link DefaultAudioManagerWrapper} and
+ * {@link FakeAudioFramework.FakeAudioManagerWrapper}.
+ */
+public interface AudioManagerWrapper {
+
+ /**
+ * Wraps {@link AudioManager#adjustStreamVolume(int, int, int)}
+ */
+ void adjustStreamVolume(int streamType, int direction,
+ @AudioManager.PublicVolumeFlags int flags);
+
+ /**
+ * Wraps {@link AudioManager#setStreamVolume(int, int, int)}
+ */
+ void setStreamVolume(int streamType, int index, @AudioManager.PublicVolumeFlags int flags);
+
+ /**
+ * Wraps {@link AudioManager#getStreamVolume(int)}
+ */
+ int getStreamVolume(int streamType);
+
+ /**
+ * Wraps {@link AudioManager#getStreamMinVolume(int)}
+ */
+ int getStreamMinVolume(int streamType);
+
+ /**
+ * Wraps {@link AudioManager#getStreamMaxVolume(int)}
+ */
+ int getStreamMaxVolume(int streamType);
+
+ /**
+ * Wraps {@link AudioManager#isStreamMute(int)}
+ */
+ boolean isStreamMute(int streamType);
+
+ /**
+ * Wraps {@link AudioManager#setStreamMute(int, boolean)}
+ */
+ void setStreamMute(int streamType, boolean state);
+
+ /**
+ * Wraps {@link AudioManager#setHdmiSystemAudioSupported(boolean)}
+ */
+ int setHdmiSystemAudioSupported(boolean on);
+
+ /**
+ * Wraps {@link AudioManager#setWiredDeviceConnectionState(AudioDeviceAttributes, int)}
+ */
+ void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state);
+
+ /**
+ * Wraps {@link AudioManager#setWiredDeviceConnectionState(int, int, String, String)}
+ */
+ void setWiredDeviceConnectionState(int device, int state, String address, String name);
+
+ /**
+ * Wraps {@link AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes)}
+ */
+ @AudioManager.DeviceVolumeBehavior
+ int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device);
+
+ /**
+ * Wraps {@link AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)}
+ */
+ void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior);
+
+ /**
+ * Wraps {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+ */
+ @NonNull
+ List<AudioDeviceAttributes> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes);
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java
index a884ffb..6242c45 100644
--- a/services/core/java/com/android/server/hdmi/AudioStatus.java
+++ b/services/core/java/com/android/server/hdmi/AudioStatus.java
@@ -23,6 +23,8 @@
/**
* Immutable representation of the information in the [Audio Status] operand:
* volume status (0 <= N <= 100) and mute status (muted or unmuted).
+ * The volume level is limited to the range [0, 100] upon construction.
+ * This object cannot represent an audio status where the volume is unknown, or out of bounds.
*/
public class AudioStatus {
public static final int MAX_VOLUME = 100;
@@ -32,7 +34,7 @@
boolean mMute;
public AudioStatus(int volume, boolean mute) {
- mVolume = volume;
+ mVolume = Math.max(Math.min(volume, MAX_VOLUME), MIN_VOLUME);
mMute = mute;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index c235299..090d728 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -631,10 +631,14 @@
static final String DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX = "enable_earc_tx";
static final String DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX =
"transition_arc_to_earc_tx";
+ // Name is abbreviated slightly to avoid line length issues
+ static final String DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI =
+ "enable_numeric_soundbar_volume_ui_on_tv";
@StringDef({
DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX,
- DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX
+ DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX,
+ DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI
})
@interface FeatureFlag {}
diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..ff99ace
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * "Default" wrapper for {@link AudioDeviceVolumeManager}, as opposed to a "Fake" wrapper for
+ * testing - see {@link FakeAudioFramework.FakeAudioDeviceVolumeManagerWrapper}.
+ *
+ * Creates an instance of {@link AudioDeviceVolumeManager} and directly passes method calls
+ * to that instance.
+ */
+public class DefaultAudioDeviceVolumeManagerWrapper
+ implements AudioDeviceVolumeManagerWrapper {
+
+ private static final String TAG = "AudioDeviceVolumeManagerWrapper";
+
+ private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+
+ public DefaultAudioDeviceVolumeManagerWrapper(Context context) {
+ mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+ }
+
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
+ mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
+ vclistener, handlesVolumeAdjustment);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume,
+ executor, vclistener, handlesVolumeAdjustment);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java
new file mode 100644
index 0000000..061e145
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hdmi;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+
+import java.util.List;
+
+/**
+ * "Default" wrapper for {@link AudioManager}, as opposed to a "Fake" wrapper for testing -
+ * see {@link FakeAudioFramework.FakeAudioManagerWrapper}.
+ *
+ * Creates an instance of {@link AudioManager} and directly passes method calls to that instance.
+ *
+*/
+public class DefaultAudioManagerWrapper implements AudioManagerWrapper {
+
+ private static final String TAG = "DefaultAudioManagerWrapper";
+
+ private final AudioManager mAudioManager;
+
+ public DefaultAudioManagerWrapper(Context context) {
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ @Override
+ public void adjustStreamVolume(int streamType, int direction,
+ @AudioManager.PublicVolumeFlags int flags) {
+ mAudioManager.adjustStreamVolume(streamType, direction, flags);
+ }
+
+ @Override
+ public void setStreamVolume(int streamType, int index,
+ @AudioManager.PublicVolumeFlags int flags) {
+ mAudioManager.setStreamVolume(streamType, index, flags);
+ }
+
+ @Override
+ public int getStreamVolume(int streamType) {
+ return mAudioManager.getStreamVolume(streamType);
+ }
+
+ @Override
+ public int getStreamMinVolume(int streamType) {
+ return mAudioManager.getStreamMinVolume(streamType);
+ }
+
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ return mAudioManager.getStreamMaxVolume(streamType);
+ }
+
+ @Override
+ public boolean isStreamMute(int streamType) {
+ return mAudioManager.isStreamMute(streamType);
+ }
+
+ @Override
+ public void setStreamMute(int streamType, boolean state) {
+ mAudioManager.setStreamMute(streamType, state);
+ }
+
+ @Override
+ public int setHdmiSystemAudioSupported(boolean on) {
+ return mAudioManager.setHdmiSystemAudioSupported(on);
+ }
+
+ @Override
+ public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state) {
+ mAudioManager.setWiredDeviceConnectionState(attributes, state);
+ }
+
+ @Override
+ public void setWiredDeviceConnectionState(int device, int state, String address, String name) {
+ mAudioManager.setWiredDeviceConnectionState(device, state, address, name);
+ }
+
+ @Override
+ @AudioManager.DeviceVolumeBehavior
+ public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
+ return mAudioManager.getDeviceVolumeBehavior(device);
+ }
+
+ @Override
+ public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
+ mAudioManager.setDeviceVolumeBehavior(device, deviceVolumeBehavior);
+ }
+
+ @Override
+ @NonNull
+ public List<AudioDeviceAttributes> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ return mAudioManager.getDevicesForAttributes(attributes);
+ }
+
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 35c70fb..f37ad5e 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1012,17 +1012,23 @@
action.start();
}
- void addAvcAudioStatusAction(int targetAddress) {
+ @ServiceThreadOnly
+ void addAvbAudioStatusAction(int targetAddress) {
+ assertRunOnServiceThread();
if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
}
}
- void removeAvcAudioStatusAction() {
+ @ServiceThreadOnly
+ void removeAvbAudioStatusAction() {
+ assertRunOnServiceThread();
removeAction(AbsoluteVolumeAudioStatusAction.class);
}
- void updateAvcVolume(int volumeIndex) {
+ @ServiceThreadOnly
+ void updateAvbVolume(int volumeIndex) {
+ assertRunOnServiceThread();
for (AbsoluteVolumeAudioStatusAction action :
getActions(AbsoluteVolumeAudioStatusAction.class)) {
action.updateVolume(volumeIndex);
@@ -1035,7 +1041,7 @@
* and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
*/
@ServiceThreadOnly
- void queryAvcSupport(int targetAddress) {
+ void querySetAudioVolumeLevelSupport(int targetAddress) {
assertRunOnServiceThread();
// Send <Give Features> if using CEC 2.0 or above.
@@ -1054,7 +1060,7 @@
@Override
public void onComplete(int result) {
if (result == HdmiControlManager.RESULT_SUCCESS) {
- getService().checkAndUpdateAbsoluteVolumeControlState();
+ getService().checkAndUpdateAbsoluteVolumeBehavior();
}
}
}));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index f47c4b2..5ef06f9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -66,7 +66,7 @@
/**
* Represent a logical device of type TV residing in Android system.
*/
-final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDeviceTv";
// Whether ARC is available or not. "true" means that ARC is established between TV and
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 18a69c8..7045e65 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,7 +259,7 @@
// The addition of a local device should not notify listeners
return;
}
- mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeBehavior();
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
return;
@@ -384,7 +384,7 @@
final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
assertRunOnServiceThread();
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
- mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeBehavior();
localDevice.mCecMessageCache.flushMessagesFrom(address);
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
@@ -592,7 +592,7 @@
updateCecDevice(newDeviceInfo);
- mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeBehavior();
}
@ServiceThreadOnly
@@ -625,7 +625,7 @@
.build();
updateCecDevice(newDeviceInfo);
- mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeBehavior();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9cd5272..e87ed0a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -231,8 +231,8 @@
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HDMI_EARC, "");
- // Audio output devices used for Absolute Volume Control
- private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
+ // Audio output devices used for absolute volume behavior
+ private static final List<AudioDeviceAttributes> AVB_AUDIO_OUTPUT_DEVICES =
Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
@@ -265,7 +265,7 @@
@HdmiControlManager.VolumeControl
private int mHdmiCecVolumeControl;
- // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
+ // Caches the volume behaviors of all audio output devices in AVB_AUDIO_OUTPUT_DEVICES.
@GuardedBy("mLock")
private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
@@ -458,6 +458,9 @@
private boolean mEarcTxFeatureFlagEnabled = false;
@ServiceThreadOnly
+ private boolean mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = false;
+
+ @ServiceThreadOnly
private boolean mTransitionFromArcToEarcTxEnabled = false;
@ServiceThreadOnly
@@ -491,10 +494,10 @@
private PowerManagerInternalWrapper mPowerManagerInternal;
@Nullable
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManager;
@Nullable
- private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
+ private AudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
@Nullable
private Looper mIoLooper;
@@ -528,18 +531,20 @@
/**
* Constructor for testing.
*
- * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
- * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
+ * Takes fakes for AudioManager and AudioDeviceVolumeManager.
*
- * @see FakeAudioDeviceVolumeManagerWrapper
+ * This is especially important for AudioDeviceVolumeManager because a normally instantiated
+ * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
*/
@VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
- AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
+ AudioManagerWrapper audioManager,
+ AudioDeviceVolumeManagerWrapper audioDeviceVolumeManager) {
super(context);
mCecLocalDevices = deviceTypes;
mSettingsObserver = new SettingsObserver(mHandler);
mHdmiCecConfig = new HdmiCecConfig(context);
mDeviceConfig = new DeviceConfigWrapper();
+ mAudioManager = audioManager;
mAudioDeviceVolumeManager = audioDeviceVolumeManager;
}
@@ -680,6 +685,8 @@
Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false);
mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean(
Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false);
+ mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = mDeviceConfig.getBoolean(
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, false);
synchronized (mLock) {
mEarcEnabled = (mHdmiCecConfig.getIntValue(
@@ -899,6 +906,17 @@
false);
}
});
+
+ mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = properties.getBoolean(
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI,
+ false);
+ checkAndUpdateAbsoluteVolumeBehavior();
+ }
+ });
}
/** Returns true if the device screen is off */
boolean isScreenOff() {
@@ -934,11 +952,6 @@
}
@VisibleForTesting
- void setAudioManager(AudioManager audioManager) {
- mAudioManager = audioManager;
- }
-
- @VisibleForTesting
void setCecController(HdmiCecController cecController) {
mCecController = cecController;
}
@@ -975,11 +988,13 @@
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
mPowerManagerInternal = new PowerManagerInternalWrapper();
- mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) {
+ mAudioManager = new DefaultAudioManagerWrapper(getContext());
+ }
mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
if (mAudioDeviceVolumeManager == null) {
mAudioDeviceVolumeManager =
- new AudioDeviceVolumeManagerWrapper(getContext());
+ new DefaultAudioDeviceVolumeManagerWrapper(getContext());
}
getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
@@ -1773,7 +1788,7 @@
== HdmiControlManager.VOLUME_CONTROL_DISABLED) {
return;
}
- AudioManager audioManager = getAudioManager();
+ AudioManagerWrapper audioManager = getAudioManager();
boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
if (mute) {
if (!muted) {
@@ -2751,7 +2766,7 @@
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
pw.println("mIsCecAvailable: " + mIsCecAvailable);
pw.println("mCecVersion: " + mCecVersion);
- pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
+ pw.println("mIsAbsoluteVolumeBehaviorEnabled: " + isAbsoluteVolumeBehaviorEnabled());
// System settings
pw.println("System_settings:");
@@ -2919,7 +2934,7 @@
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
mHdmiCecVolumeControl = hdmiCecVolumeControl;
announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
- runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior);
}
// Get the source address to send out commands to devices connected to the current device
@@ -3485,7 +3500,7 @@
* Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
*/
@Nullable
- AudioManager getAudioManager() {
+ AudioManagerWrapper getAudioManager() {
return mAudioManager;
}
@@ -3493,7 +3508,7 @@
* Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
*/
@Nullable
- private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
+ private AudioDeviceVolumeManagerWrapper getAudioDeviceVolumeManager() {
return mAudioDeviceVolumeManager;
}
@@ -3882,7 +3897,7 @@
synchronized (mLock) {
mSystemAudioActivated = on;
}
- runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior);
}
@ServiceThreadOnly
@@ -3993,7 +4008,7 @@
deviceIsActiveSource, caller);
}
- runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior);
}
// This method should only be called when the device can be the active source
@@ -4189,17 +4204,17 @@
/**
* Listener for changes to the volume behavior of an audio output device. Caches the
- * volume behavior of devices used for Absolute Volume Control.
+ * volume behavior of devices used for absolute volume behavior.
*/
@VisibleForTesting
@ServiceThreadOnly
void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
assertRunOnServiceThread();
- if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) {
synchronized (mLock) {
mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
}
- checkAndUpdateAbsoluteVolumeControlState();
+ checkAndUpdateAbsoluteVolumeBehavior();
}
}
@@ -4209,7 +4224,7 @@
*/
@AudioManager.DeviceVolumeBehavior
private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
- if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) {
synchronized (mLock) {
if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
return mAudioDeviceVolumeBehaviors.get(device);
@@ -4220,25 +4235,29 @@
}
/**
- * Returns whether Absolute Volume Control is enabled or not. This is determined by the
+ * Returns whether absolute volume behavior is enabled or not. This is determined by the
* volume behavior of the relevant HDMI audio output device(s) for this device's type.
*/
- public boolean isAbsoluteVolumeControlEnabled() {
+ public boolean isAbsoluteVolumeBehaviorEnabled() {
if (!isTvDevice() && !isPlaybackDevice()) {
return false;
}
- AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
- if (avcAudioOutputDevice == null) {
+ AudioDeviceAttributes avbAudioOutputDevice = getAvbAudioOutputDevice();
+ if (avbAudioOutputDevice == null) {
return false;
}
- return getDeviceVolumeBehavior(avcAudioOutputDevice)
- == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+
+ @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior =
+ getDeviceVolumeBehavior(avbAudioOutputDevice);
+
+ return deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+ || deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY;
}
- private AudioDeviceAttributes getAvcAudioOutputDevice() {
- if (isTvDevice()) {
+ private AudioDeviceAttributes getAvbAudioOutputDevice() {
+ if (tv() != null) {
return tv().getSystemAudioOutputDevice();
- } else if (isPlaybackDevice()) {
+ } else if (playback() != null) {
return AUDIO_OUTPUT_DEVICE_HDMI;
} else {
return null;
@@ -4246,30 +4265,30 @@
}
/**
- * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
- * if necessary. AVC is enabled precisely when a specific audio output device
- * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
- * behavior.
+ * This method is responsible for adopting or disabling absolute volume behavior and
+ * adjust-only absolute volume behavior in AudioService. These volume behaviors are adopted on
+ * specific audio output devices: HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs.
*
- * AVC must be enabled on a Playback device or TV precisely when it is playing
- * audio on an external device (the System Audio device) that supports the feature.
- * This reduces to these conditions:
+ * This method enables absolute volume behavior on a Playback device or TV panel when it is
+ * playing audio on an external device (the System Audio device) that supports the feature.
+ * This allows the volume level of the System Audio device to be tracked and set by Android.
*
+ * Absolute volume behavior requires the following conditions:
* 1. If the System Audio Device is an Audio System: System Audio Mode is active
* 2. Our HDMI audio output device is using full volume behavior
* 3. CEC volume is enabled
- * 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
+ * 4. The System Audio device supports the <Set Audio Volume Level> message
*
- * If not all of these conditions are met, this method disables AVC if necessary.
- *
- * If all of these conditions are met, this method starts an action to query the System Audio
- * device's audio status, which enables AVC upon obtaining the audio status.
+ * This method enables adjust-only absolute volume behavior on TV panels when conditions
+ * 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
+ * the System Audio device and display numeric volume UI for it, even if the System Audio device
+ * does not support <Set Audio Volume Level>.
*/
@ServiceThreadOnly
- void checkAndUpdateAbsoluteVolumeControlState() {
+ void checkAndUpdateAbsoluteVolumeBehavior() {
assertRunOnServiceThread();
- // Can't enable or disable AVC before we have access to system services
+ // Can't set volume behavior before we have access to system services
if (getAudioManager() == null) {
return;
}
@@ -4281,84 +4300,110 @@
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
// TV is the System Audio Device instead.)
if (!isSystemAudioActivated()) {
- disableAbsoluteVolumeControl();
+ switchToFullVolumeBehavior();
return;
}
} else if (isPlaybackDevice() && playback() != null) {
localCecDevice = playback();
} else {
- // Either this device type doesn't support AVC, or it hasn't fully initialized yet
+ // Either this device type doesn't support AVB, or it hasn't fully initialized yet
return;
}
- HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
+ HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
localCecDevice.findAudioReceiverAddress());
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
- getDeviceVolumeBehavior(getAvcAudioOutputDevice());
+ getDeviceVolumeBehavior(getAvbAudioOutputDevice());
// Condition 2: Already using full or absolute volume behavior
boolean alreadyUsingFullOrAbsoluteVolume =
- currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
- || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL)
+ || (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)
+ || (currentVolumeBehavior
+ == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
// Condition 3: CEC volume is enabled
boolean cecVolumeEnabled =
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
- disableAbsoluteVolumeControl();
+ switchToFullVolumeBehavior();
return;
}
- // Check for safety: if the System Audio device is a candidate for AVC, we should already
+ // Check for safety: if the System Audio device is a candidate for AVB, we should already
// have received messages from it to trigger the other conditions.
if (systemAudioDeviceInfo == null) {
- disableAbsoluteVolumeControl();
+ switchToFullVolumeBehavior();
return;
}
- // Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
+
+ // Condition 4: The System Audio device supports <Set Audio Volume Level>
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
- if (!isAbsoluteVolumeControlEnabled()) {
- // Start an action that will call {@link #enableAbsoluteVolumeControl}
+ if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ // If we're currently using adjust-only absolute volume behavior, switch to
+ // full volume behavior until we successfully adopt absolute volume behavior
+ switchToFullVolumeBehavior();
+ // Start an action that will call enableAbsoluteVolumeBehavior
// once the System Audio device sends <Report Audio Status>
- localCecDevice.addAvcAudioStatusAction(
+ localCecDevice.addAvbAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
return;
case DeviceFeatures.FEATURE_NOT_SUPPORTED:
- disableAbsoluteVolumeControl();
+ // TVs may adopt adjust-only absolute volume behavior if condition 4 isn't met.
+ // This allows the device to display numeric volume UI for the System Audio device.
+ if (tv() != null && mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled) {
+ if (currentVolumeBehavior
+ != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
+ // If we're currently using absolute volume behavior, switch to full volume
+ // behavior until we successfully adopt adjust-only absolute volume behavior
+ switchToFullVolumeBehavior();
+ // Start an action that will call enableAbsoluteVolumeBehavior
+ // once the System Audio device sends <Report Audio Status>
+ localCecDevice.addAvbAudioStatusAction(
+ systemAudioDeviceInfo.getLogicalAddress());
+ }
+ } else {
+ switchToFullVolumeBehavior();
+ }
return;
case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
- disableAbsoluteVolumeControl();
- localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
- return;
- default:
- return;
+ switchToFullVolumeBehavior();
+ localCecDevice.querySetAudioVolumeLevelSupport(
+ systemAudioDeviceInfo.getLogicalAddress());
}
}
- private void disableAbsoluteVolumeControl() {
- if (isPlaybackDevice()) {
- playback().removeAvcAudioStatusAction();
- } else if (isTvDevice()) {
- tv().removeAvcAudioStatusAction();
+ /**
+ * Switches to full volume behavior, if either absolute or adjust-only absolute volume behavior
+ * are currently used. Removes the action for handling volume updates for these behaviors.
+ */
+ private void switchToFullVolumeBehavior() {
+ if (playback() != null) {
+ playback().removeAvbAudioStatusAction();
+ } else if (tv() != null) {
+ tv().removeAvbAudioStatusAction();
}
- AudioDeviceAttributes device = getAvcAudioOutputDevice();
- if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ AudioDeviceAttributes device = getAvbAudioOutputDevice();
+ int volumeBehavior = getDeviceVolumeBehavior(device);
+ if (volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+ || volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
getAudioManager().setDeviceVolumeBehavior(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
}
/**
- * Enables Absolute Volume Control. Should only be called when all the conditions for
- * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
+ * Enables absolute volume behavior or adjust-only absolute volume behavior. Should only be
+ * called when the conditions for one of these behaviors is met -
+ * see {@link #checkAndUpdateAbsoluteVolumeBehavior}.
+ *
* @param audioStatus The initial audio status to set the audio output device to
*/
- void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
+ void enableAbsoluteVolumeBehavior(AudioStatus audioStatus) {
HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
- HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
- localDevice.findAudioReceiverAddress());
+ HdmiDeviceInfo systemAudioDevice = getDeviceInfo(localDevice.findAudioReceiverAddress());
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMuted(audioStatus.getMute())
.setVolumeIndex(audioStatus.getVolume())
@@ -4370,10 +4415,21 @@
// AudioService sets the volume of the stream and device based on the input VolumeInfo
// when enabling absolute volume behavior, but not the mute state
- notifyAvcMuteChange(audioStatus.getMute());
- getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
- getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
- mAbsoluteVolumeChangedListener, true);
+ notifyAvbMuteChange(audioStatus.getMute());
+
+ // If <Set Audio Volume Level> is supported, enable absolute volume behavior.
+ // Otherwise, enable adjust-only AVB on TVs only.
+ if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
+ == DeviceFeatures.FEATURE_SUPPORTED) {
+ getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
+ getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener, true);
+ } else if (tv() != null) {
+ getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener, true);
+ }
+
}
private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
@@ -4407,6 +4463,14 @@
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
+
+ // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+ if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
+ != DeviceFeatures.FEATURE_SUPPORTED) {
+ return;
+ }
+
+ // Send <Set Audio Volume Level> to notify the System Audio device of the volume change
int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
@@ -4418,8 +4482,8 @@
if (errorCode == SendMessageResult.SUCCESS) {
// Update the volume tracked in our AbsoluteVolumeAudioStatusAction
// so it correctly processes incoming <Report Audio Status> messages
- HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
- avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
+ HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+ avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
} else {
sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
localDeviceAddress,
@@ -4477,13 +4541,13 @@
/**
* Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
- * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ * AVB is disabled, or the audio output device for AVB is not playing for STREAM_MUSIC
*/
- void notifyAvcVolumeChange(int volume) {
- if (!isAbsoluteVolumeControlEnabled()) return;
+ void notifyAvbVolumeChange(int volume) {
+ if (!isAbsoluteVolumeBehaviorEnabled()) return;
List<AudioDeviceAttributes> streamMusicDevices =
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
- if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ if (streamMusicDevices.contains(getAvbAudioOutputDevice())) {
int flags = AudioManager.FLAG_ABSOLUTE_VOLUME;
if (isTvDevice()) {
flags |= AudioManager.FLAG_SHOW_UI;
@@ -4494,13 +4558,13 @@
/**
* Notifies AudioService of a change in the mute status of the System Audio device. Has no
- * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ * effect if AVB is disabled, or the audio output device for AVB is not playing for STREAM_MUSIC
*/
- void notifyAvcMuteChange(boolean mute) {
- if (!isAbsoluteVolumeControlEnabled()) return;
+ void notifyAvbMuteChange(boolean mute) {
+ if (!isAbsoluteVolumeBehaviorEnabled()) return;
List<AudioDeviceAttributes> streamMusicDevices =
getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
- if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ if (streamMusicDevices.contains(getAvbAudioOutputDevice())) {
int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
int flags = AudioManager.FLAG_ABSOLUTE_VOLUME;
if (isTvDevice()) {
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index c4b98c2..6147c10 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -172,7 +172,7 @@
return;
}
if (mDisplayName == null) {
- mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress);
+ mDisplayName = "";
}
HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
.setLogicalAddress(mDeviceLogicalAddress)
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 7daeaf1..2703a2c0 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -172,10 +172,10 @@
}
private void sendKeyUp() {
- // When using Absolute Volume Control, query audio status after a volume key is released.
+ // When using absolute volume behavior, query audio status after a volume key is released.
// This allows us to notify AudioService of the resulting volume or mute status changes.
if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
- && localDevice().getService().isAbsoluteVolumeControlEnabled()) {
+ && localDevice().getService().isAbsoluteVolumeBehaviorEnabled()) {
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
mTargetAddress),
__ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
index eb3b33d..bfa8509 100644
--- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -90,7 +90,7 @@
}
void handleTimerEvent(int state) {
- if (updateAvcSupport(FEATURE_SUPPORTED)) {
+ if (updateSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)) {
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
} else {
finishWithCallback(HdmiControlManager.RESULT_EXCEPTION);
@@ -104,7 +104,7 @@
*
* @return Whether support was successfully updated in the network.
*/
- private boolean updateAvcSupport(
+ private boolean updateSetAudioVolumeLevelSupport(
@DeviceFeatures.FeatureSupportStatus int setAudioVolumeLevelSupport) {
HdmiCecNetwork network = localDevice().mService.getHdmiCecNetwork();
HdmiDeviceInfo currentDeviceInfo = network.getCecDeviceInfo(mTargetAddress);
diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
index d5761e170..30b188c 100644
--- a/services/core/java/com/android/server/hdmi/VolumeControlAction.java
+++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
@@ -159,7 +159,7 @@
// Update audio status if current volume position is edge of volume bar,
// i.e max or min volume.
- AudioManager audioManager = tv().getService().getAudioManager();
+ AudioManagerWrapper audioManager = tv().getService().getAudioManager();
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (mIsVolumeUp) {
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 02b7053..47f6485 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -33,6 +33,8 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -74,6 +76,7 @@
import android.view.KeyEvent;
import com.android.server.LocalServices;
+import com.android.server.uri.UriGrantsManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -101,6 +104,10 @@
static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
private static final String TAG = "MediaSessionRecord";
+ private static final String[] ART_URIS = new String[] {
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI};
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
@@ -154,6 +161,7 @@
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
+ private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
@@ -215,6 +223,7 @@
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
+ mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
@@ -1080,21 +1089,45 @@
public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription)
throws RemoteException {
synchronized (mLock) {
- MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
- .build();
- // This is to guarantee that the underlying bundle is unparceled
- // before we set it to prevent concurrent reads from throwing an
- // exception
- if (temp != null) {
- temp.size();
- }
- mMetadata = temp;
mDuration = duration;
mMetadataDescription = metadataDescription;
+ mMetadata = sanitizeMediaMetadata(metadata);
}
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
}
+ private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) {
+ if (metadata == null) {
+ return null;
+ }
+ MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata);
+ for (String key: ART_URIS) {
+ String uriString = metadata.getString(key);
+ if (TextUtils.isEmpty(uriString)) {
+ continue;
+ }
+ Uri uri = Uri.parse(uriString);
+ if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ continue;
+ }
+ try {
+ mUgmInternal.checkGrantUriPermission(getUid(),
+ getPackageName(),
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, getUserId()));
+ } catch (SecurityException e) {
+ metadataBuilder.putString(key, null);
+ }
+ }
+ MediaMetadata sanitizedMetadata = metadataBuilder.build();
+ // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled
+ // before we set it to prevent concurrent reads from throwing an
+ // exception
+ sanitizedMetadata.size();
+ return sanitizedMetadata;
+ }
+
@Override
public void setPlaybackState(PlaybackState state) throws RemoteException {
int oldState = mPlaybackState == null
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e8bf82f..1641d61 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -104,6 +104,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.utils.RequestThrottle;
import libcore.io.IoUtils;
@@ -1308,6 +1309,13 @@
}
}
+ private boolean isValidForInstallConstraints(PackageStateInternal ps,
+ String installerPackageName) {
+ return TextUtils.equals(ps.getInstallSource().mInstallerPackageName, installerPackageName)
+ || TextUtils.equals(ps.getInstallSource().mUpdateOwnerPackageName,
+ installerPackageName);
+ }
+
private CompletableFuture<InstallConstraintsResult> checkInstallConstraintsInternal(
String installerPackageName, List<String> packageNames,
InstallConstraints constraints, long timeoutMillis) {
@@ -1324,8 +1332,7 @@
if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
for (var packageName : packageNames) {
var ps = snapshot.getPackageStateInternal(packageName);
- if (ps == null || !TextUtils.equals(
- ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+ if (ps == null || !isValidForInstallConstraints(ps, installerPackageName)) {
throw new SecurityException("Caller has no access to package " + packageName);
}
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 9127a93..ba82577 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -697,8 +697,6 @@
Computer snapshot, int userId, boolean suspend) {
final Set<String> toSuspend = packagesToSuspendInQuietMode(snapshot, userId);
if (!suspend) {
- // Note: this method is called from DPMS constructor to suspend apps on upgrade, but
- // it won't enter here because 'suspend' will equal 'true'.
final DevicePolicyManagerInternal dpm =
LocalServices.getService(DevicePolicyManagerInternal.class);
if (dpm != null) {
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index a694f5f..ecd5bd2 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -274,13 +274,14 @@
mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
mSensorPrivacyStateController = SensorPrivacyStateController.getInstance();
+ correctStateIfNeeded();
+
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
OP_CAMERA, OP_PHONE_CALL_CAMERA, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO};
mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
-
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -313,6 +314,20 @@
userId, toggleType, sensor, state.isEnabled()));
}
+ // If sensor privacy is enabled for a sensor, but the device doesn't support sensor privacy
+ // for that sensor, then disable privacy
+ private void correctStateIfNeeded() {
+ mSensorPrivacyStateController.forEachState((type, user, sensor, state) -> {
+ if (type != TOGGLE_TYPE_SOFTWARE) {
+ return;
+ }
+ if (!supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor) && state.isEnabled()) {
+ setToggleSensorPrivacyUnchecked(
+ TOGGLE_TYPE_SOFTWARE, user, OTHER, sensor, false);
+ }
+ });
+ }
+
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
@@ -721,15 +736,30 @@
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
}
+
if (!canChangeToggleSensorPrivacy(userId, sensor)) {
return;
}
+ if (enable && !supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)) {
+ // Do not enable sensor privacy if the device doesn't support it
+ return;
+ }
setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable);
}
private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source,
int sensor, boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " setToggleSensorPrivacyUnchecked("
+ + "userId=" + userId
+ + " source=" + source
+ + " sensor=" + sensor
+ + " enable=" + enable
+ + ")");
+ }
final long[] lastChange = new long[1];
mSensorPrivacyStateController.atomic(() -> {
SensorState sensorState = mSensorPrivacyStateController
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7926216..9f738ed 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -585,6 +585,11 @@
public abstract void setDeviceOwnerUid(int uid);
/**
+ * Called by DevicePolicyManagerService to set the uids of the profile owners.
+ */
+ public abstract void setProfileOwnerUids(Set<Integer> uids);
+
+ /**
* Set all associated companion app that belongs to a userId.
* @param userId
* @param companionAppUids ActivityTaskManager will take ownership of this Set, the caller
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f35343c..48569f6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -158,6 +158,7 @@
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.admin.DevicePolicyCache;
+import android.app.admin.DeviceStateCache;
import android.app.assist.ActivityId;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -783,6 +784,8 @@
private int mDeviceOwnerUid = Process.INVALID_UID;
+ private Set<Integer> mProfileOwnerUids = new ArraySet<Integer>();
+
private final class SettingObserver extends ContentObserver {
private final Uri mFontScaleUri = Settings.System.getUriFor(FONT_SCALE);
private final Uri mHideErrorDialogsUri = Settings.Global.getUriFor(HIDE_ERROR_DIALOGS);
@@ -5360,6 +5363,15 @@
mDeviceOwnerUid = uid;
}
+ boolean isAffiliatedProfileOwner(int uid) {
+ return uid >= 0 && mProfileOwnerUids.contains(uid)
+ && DeviceStateCache.getInstance().hasAffiliationWithDevice(UserHandle.getUserId(uid));
+ }
+
+ void setProfileOwnerUids(Set<Integer> uids) {
+ mProfileOwnerUids = uids;
+ }
+
/**
* Saves the current activity manager state and includes the saved state in the next dump of
* activity manager.
@@ -6916,6 +6928,13 @@
}
@Override
+ public void setProfileOwnerUids(Set<Integer> uids) {
+ synchronized (mGlobalLock) {
+ ActivityTaskManagerService.this.setProfileOwnerUids(uids);
+ }
+ }
+
+ @Override
public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) {
synchronized (mGlobalLock) {
mCompanionAppUidsMap.put(userId, companionAppUids);
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 90ec964..2df601f 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -34,6 +34,7 @@
import android.view.InputApplicationHandle;
import com.android.internal.os.TimeoutRecord;
+import com.android.server.FgThread;
import com.android.server.am.StackTracesDumpHelper;
import com.android.server.criticalevents.CriticalEventLog;
@@ -68,7 +69,9 @@
TimeoutRecord timeoutRecord) {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+ timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted();
preDumpIfLockTooSlow();
+ timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded();
final ActivityRecord activity;
timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
boolean blamePendingFocusRequest = false;
@@ -108,7 +111,7 @@
if (!blamePendingFocusRequest) {
Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: "
+ timeoutRecord.mReason);
- dumpAnrStateLocked(activity, null /* windowState */, timeoutRecord.mReason);
+ dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason);
mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
}
}
@@ -159,7 +162,9 @@
*/
private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken,
TimeoutRecord timeoutRecord) {
+ timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted();
preDumpIfLockTooSlow();
+ timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded();
final int pid;
final boolean aboveSystem;
final ActivityRecord activity;
@@ -178,7 +183,7 @@
? windowState.mActivityRecord : null;
Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason);
aboveSystem = isWindowAboveSystem(windowState);
- dumpAnrStateLocked(activity, windowState, timeoutRecord.mReason);
+ dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason);
}
if (activity != null) {
activity.inputDispatchingTimedOut(timeoutRecord, pid);
@@ -197,7 +202,7 @@
timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
synchronized (mService.mGlobalLock) {
timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
- dumpAnrStateLocked(null /* activity */, null /* windowState */, timeoutRecord.mReason);
+ dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason);
}
// We cannot determine the z-order of the window, so place the anr dialog as high
@@ -351,15 +356,23 @@
}
- private void dumpAnrStateLocked(ActivityRecord activity, WindowState windowState,
- String reason) {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "dumpAnrStateLocked()");
- mService.saveANRStateLocked(activity, windowState, reason);
- mService.mAtmService.saveANRState(reason);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
+ /**
+ * Executes asynchronously on the fg thread not to block the stack dump for
+ * the ANRing processes.
+ */
+ private void dumpAnrStateAsync(ActivityRecord activity, WindowState windowState,
+ String reason) {
+ FgThread.getExecutor().execute(() -> {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "dumpAnrStateLocked()");
+ synchronized (mService.mGlobalLock) {
+ mService.saveANRStateLocked(activity, windowState, reason);
+ mService.mAtmService.saveANRState(reason);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ });
}
private boolean isWindowAboveSystem(@NonNull WindowState windowState) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 1a3d673..dc49e8c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -342,6 +342,14 @@
/*background*/ true, callingUid, realCallingUid,
intent, "Device Owner");
}
+ // don't abort if the callingUid is a affiliated profile owner
+ if (mService.isAffiliatedProfileOwner(callingUid)) {
+ return logStartAllowedAndReturnCode(
+ BAL_ALLOW_ALLOWLISTED_COMPONENT,
+ resultIfPiSenderAllowsBal, balAllowedByPiSender,
+ /*background*/ true, callingUid, realCallingUid,
+ intent, "Affiliated Profile Owner");
+ }
// don't abort if the callingUid has companion device
final int callingUserId = UserHandle.getUserId(callingUid);
if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index ae93a94..2b72215 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -223,7 +223,7 @@
try {
activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
- "Refershing activity for camera compatibility treatment, "
+ "Refreshing activity for camera compatibility treatment, "
+ "activityRecord=%s", activity);
final ClientTransaction transaction = ClientTransaction.obtain(
activity.app.getThread(), activity.token);
@@ -311,11 +311,14 @@
}
}
- // Refreshing only when configuration changes after rotation.
+ // Refreshing only when configuration changes after rotation or camera split screen aspect ratio
+ // treatment is enabled
private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
Configuration lastReportedConfig) {
- return newConfig.windowConfiguration.getDisplayRotation()
- != lastReportedConfig.windowConfiguration.getDisplayRotation()
+ final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
+ != lastReportedConfig.windowConfiguration.getDisplayRotation());
+ return (displayRotationChanged
+ || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
&& isTreatmentEnabledForActivity(activity)
&& activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index f492bab..a93cb8ad 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -214,7 +214,7 @@
// otherwise the apps get blacked out when they are resumed and do not have focus yet.
private boolean mIsCompatFakeFocusEnabled;
- // Whether should use split screen aspect ratio for the activity when camera compat treatment
+ // Whether we should use split screen aspect ratio for the activity when camera compat treatment
// is enabled and activity is connected to the camera in fullscreen.
private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
@@ -1118,7 +1118,7 @@
}
/**
- * Whether should use split screen aspect ratio for the activity when camera compat treatment
+ * Whether we should use split screen aspect ratio for the activity when camera compat treatment
* is enabled and activity is connected to the camera in fullscreen.
*/
boolean isCameraCompatSplitScreenAspectRatioEnabled() {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0288e4b..5b39790 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -958,7 +958,7 @@
* Whether we use split screen aspect ratio for the activity when camera compat treatment
* is active because the corresponding config is enabled and activity supports resizing.
*/
- private boolean isCameraCompatSplitScreenAspectRatioAllowed() {
+ boolean isCameraCompatSplitScreenAspectRatioAllowed() {
return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
&& !mActivityRecord.shouldCreateCompatDisplayInsets();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 25b7df4..7776132 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9014,7 +9014,7 @@
final SurfaceControl mirror = SurfaceControl.mirrorSurface(displaySc);
outSurfaceControl.copyFrom(mirror, "WMS.mirrorDisplay");
-
+ mirror.release();
return true;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 6d51bd7..d960439 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -16,6 +16,8 @@
package com.android.server.devicepolicy;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -70,6 +72,7 @@
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
private static final String TAG_BYPASS_ROLE_QUALIFICATIONS = "bypass-role-qualifications";
+ private static final String TAG_KEEP_PROFILES_RUNNING = "keep-profiles-running";
private static final String ATTR_VALUE = "value";
private static final String ATTR_ALIAS = "alias";
private static final String ATTR_ID = "id";
@@ -193,6 +196,12 @@
// starts.
String mNewUserDisclaimer = NEW_USER_DISCLAIMER_NOT_NEEDED;
+ /**
+ * Effective state of the feature flag. It is updated to the current configuration value
+ * during boot and doesn't change value after than unless overridden by test code.
+ */
+ boolean mEffectiveKeepProfilesRunning = DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
+
DevicePolicyData(@UserIdInt int userId) {
mUserId = userId;
}
@@ -392,6 +401,12 @@
out.endTag(null, TAG_BYPASS_ROLE_QUALIFICATIONS);
}
+ if (policyData.mEffectiveKeepProfilesRunning != DEFAULT_KEEP_PROFILES_RUNNING_FLAG) {
+ out.startTag(null, TAG_KEEP_PROFILES_RUNNING);
+ out.attributeBoolean(null, ATTR_VALUE, policyData.mEffectiveKeepProfilesRunning);
+ out.endTag(null, TAG_KEEP_PROFILES_RUNNING);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -574,7 +589,10 @@
parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_BYPASS_ROLE_QUALIFICATIONS.equals(tag)) {
policy.mBypassDevicePolicyManagementRoleQualifications = true;
- policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE);
+ policy.mCurrentRoleHolder = parser.getAttributeValue(null, ATTR_VALUE);
+ } else if (TAG_KEEP_PROFILES_RUNNING.equals(tag)) {
+ policy.mEffectiveKeepProfilesRunning = parser.getAttributeBoolean(
+ null, ATTR_VALUE, DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
// Deprecated tags below
} else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
if (policy.mUserControlDisabledPackages == null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 1392d02..1322225 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -56,6 +56,7 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -1511,7 +1512,7 @@
readInner(parser);
} catch (XmlPullParserException | IOException | ClassNotFoundException e) {
- Log.e(TAG, "Error parsing resources file", e);
+ Slogf.wtf(TAG, "Error parsing resources file", e);
} finally {
IoUtils.closeQuietly(input);
}
@@ -1533,7 +1534,7 @@
readEnforcingAdminsInner(parser);
break;
default:
- Log.e(TAG, "Unknown tag " + tag);
+ Slogf.wtf(TAG, "Unknown tag " + tag);
}
}
}
@@ -1554,7 +1555,7 @@
policyState = PolicyState.readFromXml(parser);
break;
default:
- Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
}
}
@@ -1564,7 +1565,9 @@
}
mLocalPolicies.get(userId).put(policyKey, policyState);
} else {
- Log.e(TAG, "Error parsing local policy");
+ Slogf.wtf(TAG, "Error parsing local policy, policyKey is "
+ + (policyKey == null ? "null" : policyKey) + ", and policyState is "
+ + (policyState == null ? "null" : policyState) + ".");
}
}
@@ -1583,20 +1586,26 @@
policyState = PolicyState.readFromXml(parser);
break;
default:
- Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
}
}
if (policyKey != null && policyState != null) {
mGlobalPolicies.put(policyKey, policyState);
} else {
- Log.e(TAG, "Error parsing global policy");
+ Slogf.wtf(TAG, "Error parsing global policy, policyKey is "
+ + (policyKey == null ? "null" : policyKey) + ", and policyState is "
+ + (policyState == null ? "null" : policyState) + ".");
}
}
private void readEnforcingAdminsInner(TypedXmlPullParser parser)
throws XmlPullParserException {
EnforcingAdmin admin = EnforcingAdmin.readFromXml(parser);
+ if (admin == null) {
+ Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null.");
+ return;
+ }
if (!mEnforcingAdmins.contains(admin.getUserId())) {
mEnforcingAdmins.put(admin.getUserId(), new HashSet<>());
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ef45cee..9d1c77d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -446,7 +446,6 @@
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -489,6 +488,7 @@
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -677,7 +677,7 @@
// to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
// be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
// step.
- static final int DPMS_VERSION = 4;
+ static final int DPMS_VERSION = 5;
static {
SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -874,7 +874,7 @@
// TODO(b/265683382) remove the flag after rollout.
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
- private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
+ public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG =
"enable_work_profile_telephony";
@@ -889,12 +889,6 @@
private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
/**
- * This feature flag is checked once after boot and this value us used until the next reboot to
- * avoid needing to handle the flag changing on the fly.
- */
- private boolean mKeepProfilesRunning = isKeepProfilesRunningFlagEnabled();
-
- /**
* For apps targeting U+
* Enable multiple admins to coexist on the same device.
*/
@@ -2096,12 +2090,6 @@
performPolicyVersionUpgrade();
}
- // TODO(b/265683382) move it into an upgrade step when removing the flag, so that it is
- // executed only once on upgrading devices, not every boot.
- if (mKeepProfilesRunning) {
- suspendAppsForQuietProfiles();
- }
-
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
@@ -2202,12 +2190,12 @@
return packageNameAndSignature;
}
- private void suspendAppsForQuietProfiles() {
+ private void suspendAppsForQuietProfiles(boolean toSuspend) {
PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
List<UserInfo> users = mUserManager.getUsers();
for (UserInfo user : users) {
if (user.isManagedProfile() && user.isQuietModeEnabled()) {
- pmi.setPackagesSuspendedForQuietMode(user.id, true);
+ pmi.setPackagesSuspendedForQuietMode(user.id, toSuspend);
}
}
}
@@ -3482,6 +3470,10 @@
revertTransferOwnershipIfNecessaryLocked();
}
updateUsbDataSignal();
+
+ // In case flag value has changed, we apply it during boot to avoid doing it concurrently
+ // with user toggling quiet mode.
+ setKeepProfileRunningEnabledUnchecked(isKeepProfilesRunningFlagEnabled());
}
// TODO(b/230841522) Make it static.
@@ -5561,7 +5553,9 @@
if (isPermissionCheckFlagEnabled()) {
CallerIdentity caller = getCallerIdentity(who, callerPackageName);
ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_WIPE_DATA,
+ who,
+ /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA,
+ /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA,
caller.getPackageName(), affectedUserId).getActiveAdmin();
} else {
// This API can only be called by an active device admin,
@@ -5843,9 +5837,11 @@
ActiveAdmin ap;
if (isPermissionCheckFlagEnabled()) {
CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- // TODO: Allow use of USES_POLICY_FORCE_LOCK
ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_LOCK, caller.getPackageName(),
+ who,
+ /*permission=*/ MANAGE_DEVICE_POLICY_LOCK,
+ /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
+ caller.getPackageName(),
affectedUserId).getActiveAdmin();
} else {
ap = getActiveAdminForCallerLocked(
@@ -10071,6 +10067,13 @@
if (!mHasTelephonyFeature) {
return;
}
+ if (!LocalServices.getService(SystemServiceManager.class).isBootCompleted()) {
+ Slogf.i(LOG_TAG, "Skip clearing managed profile Apn before boot completed");
+ // Cannot talk to APN content provider before system boots
+ // Ideally we should delay the cleanup post boot_completed, not just
+ // skipping it altogether.
+ return;
+ }
final List<ApnSetting> apns = getOverrideApnsUnchecked();
for (ApnSetting apn : apns) {
if (apn.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) {
@@ -10326,6 +10329,7 @@
policy.mSecondaryLockscreenEnabled = false;
policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
policy.mAffiliationIds.clear();
+ resetAffiliationCacheLocked();
policy.mLockTaskPackages.clear();
if (!isPolicyEngineForFinanceFlagEnabled()) {
updateLockTaskPackagesLocked(mContext, policy.mLockTaskPackages, userId);
@@ -11317,7 +11321,8 @@
(size == 1 ? "" : "s"));
}
pw.println();
- pw.println("Keep profiles running: " + mKeepProfilesRunning);
+ pw.println("Keep profiles running: "
+ + getUserData(UserHandle.USER_SYSTEM).mEffectiveKeepProfilesRunning);
pw.println();
mPolicyCache.dump(pw);
@@ -11802,9 +11807,10 @@
if (isPermissionCheckFlagEnabled()) {
CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- // TODO: Support USES_POLICY_DISABLE_KEYGUARD_FEATURES
ap = enforcePermissionAndGetEnforcingAdmin(
- admin, MANAGE_DEVICE_POLICY_KEYGUARD,
+ admin,
+ /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD,
+ /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES,
caller.getPackageName(), affectedUserId).getActiveAdmin();
} else {
ap = getActiveAdminForCallerLocked(admin,
@@ -13350,23 +13356,23 @@
caller = getCallerIdentity(who);
}
int userId = caller.getUserId();
+ int affectedUserId = parent ? getProfileParentId(userId) : userId;
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
if (isPolicyEngineForFinanceFlagEnabled()) {
if (!isDeviceOwner(caller) && !isProfileOwner(caller)) {
+ EnforcingAdmin admin = enforcePermissionForUserRestriction(
+ who,
+ key,
+ caller.getPackageName(),
+ affectedUserId);
if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
throw new IllegalStateException("Calling package is not targeting Android U.");
}
if (!UserRestrictionsUtils.isValidRestriction(key)) {
throw new IllegalArgumentException("Invalid restriction key: " + key);
}
- int affectedUserId = parent ? getProfileParentId(userId) : userId;
- EnforcingAdmin admin = enforcePermissionForUserRestriction(
- who,
- key,
- caller.getPackageName(),
- affectedUserId);
PolicyDefinition<Boolean> policyDefinition =
PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
if (enabledFromThisOwner) {
@@ -16267,7 +16273,7 @@
@Override
public boolean isKeepProfilesRunningEnabled() {
- return mKeepProfilesRunning;
+ return getUserDataUnchecked(UserHandle.USER_SYSTEM).mEffectiveKeepProfilesRunning;
}
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
@@ -18017,10 +18023,16 @@
synchronized (getLockObject()) {
getUserData(callingUserId).mAffiliationIds = affiliationIds;
saveSettingsLocked(callingUserId);
- if (callingUserId != UserHandle.USER_SYSTEM && isDeviceOwner(admin, callingUserId)) {
+ mStateCache.setHasAffiliationWithDevice(callingUserId,
+ isUserAffiliatedWithDeviceLocked(callingUserId));
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ resetAffiliationCacheLocked();
+ } else if (callingUserId != UserHandle.USER_SYSTEM && isDeviceOwner(admin,
+ callingUserId)) {
// Affiliation ids specified by the device owner are additionally stored in
// UserHandle.USER_SYSTEM's DevicePolicyData.
getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds;
+ mStateCache.setHasAffiliationWithDevice(UserHandle.USER_SYSTEM, true);
saveSettingsLocked(UserHandle.USER_SYSTEM);
}
@@ -18034,6 +18046,15 @@
}
}
+ private void resetAffiliationCacheLocked() {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ for (UserInfo user : mUserManager.getUsers()) {
+ mStateCache.setHasAffiliationWithDevice(user.id,
+ isUserAffiliatedWithDeviceLocked(user.id));
+ }
+ });
+ }
+
@Override
public List<String> getAffiliationIds(ComponentName admin) {
if (!mHasFeature) {
@@ -22998,6 +23019,7 @@
MANAGE_DEVICE_POLICY_FACTORY_RESET,
MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
MANAGE_DEVICE_POLICY_KEYGUARD,
+ MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -23005,7 +23027,7 @@
MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_TIME,
MANAGE_DEVICE_POLICY_USERS,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
+ MANAGE_DEVICE_POLICY_WIPE_DATA
);
/**
@@ -23525,14 +23547,15 @@
*
* @param callerPackageName The package name of the calling application.
* @param adminPolicy The admin policy that should grant holders permission.
- * @param permission The name of the permission being checked.
+ * @param permissions The names of the permissions being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
* the associated cross-user permission if the caller's user is different to the target user.
*/
private void enforcePermissions(String[] permissions, int adminPolicy,
String callerPackageName, int targetUserId) throws SecurityException {
- if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+ if (hasAdminPolicy(adminPolicy, callerPackageName)
+ && mInjector.userHandleGetCallingUserId() == targetUserId) {
return;
}
enforcePermissions(permissions, callerPackageName, targetUserId);
@@ -23561,8 +23584,9 @@
private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
- ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
- return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+ ActiveAdmin deviceAdmin = getActiveAdminWithPolicyForUidLocked(
+ null, adminPolicy, caller.getUid());
+ return deviceAdmin != null;
}
/**
@@ -23755,6 +23779,18 @@
return false;
}
+ private void setKeepProfileRunningEnabledUnchecked(boolean keepProfileRunning) {
+ synchronized (getLockObject()) {
+ DevicePolicyData policyData = getUserDataUnchecked(UserHandle.USER_SYSTEM);
+ if (policyData.mEffectiveKeepProfilesRunning == keepProfileRunning) {
+ return;
+ }
+ policyData.mEffectiveKeepProfilesRunning = keepProfileRunning;
+ saveSettingsLocked(UserHandle.USER_SYSTEM);
+ }
+ suspendAppsForQuietProfiles(keepProfileRunning);
+ }
+
private boolean isWorkProfileTelephonyEnabled() {
return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
&& isWorkProfileTelephonySubscriptionManagerFlagEnabled();
@@ -23779,7 +23815,7 @@
public void setOverrideKeepProfilesRunning(boolean enabled) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
- mKeepProfilesRunning = enabled;
+ setKeepProfileRunningEnabledUnchecked(enabled);
Slog.i(LOG_TAG, "Keep profiles running overridden to: " + enabled);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
index 011a282..47607d7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
@@ -42,6 +42,8 @@
private AtomicInteger mDeviceOwnerType = new AtomicInteger(NO_DEVICE_OWNER);
private Map<Integer, Boolean> mHasProfileOwner = new ConcurrentHashMap<>();
+ private Map<Integer, Boolean> mAffiliationWithDevice = new ConcurrentHashMap<>();
+
@GuardedBy("mLock")
private boolean mIsDeviceProvisioned = false;
@@ -70,6 +72,19 @@
}
}
+ void setHasAffiliationWithDevice(int userId, Boolean hasAffiliateProfileOwner) {
+ if (hasAffiliateProfileOwner) {
+ mAffiliationWithDevice.put(userId, true);
+ } else {
+ mAffiliationWithDevice.remove(userId);
+ }
+ }
+
+ @Override
+ public boolean hasAffiliationWithDevice(int userId) {
+ return mAffiliationWithDevice.getOrDefault(userId, false);
+ }
+
@Override
public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
if (mHasProfileOwner.getOrDefault(userHandle, false)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 3ed2d34..5243d14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -30,6 +30,7 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.role.RoleManagerLocal;
import com.android.server.LocalManagerRegistry;
+import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParserException;
@@ -51,6 +52,9 @@
*
*/
final class EnforcingAdmin {
+
+ static final String TAG = "EnforcingAdmin";
+
static final String ROLE_AUTHORITY_PREFIX = "role:";
static final String DPC_AUTHORITY = "enterprise";
static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
@@ -286,6 +290,7 @@
}
}
+ @Nullable
static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException {
String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
@@ -294,13 +299,25 @@
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
if (isRoleAuthority) {
+ if (packageName == null) {
+ Slogf.wtf(TAG, "Error parsing EnforcingAdmin with RoleAuthority, packageName is "
+ + "null.");
+ return null;
+ }
// TODO(b/281697976): load active admin
return new EnforcingAdmin(packageName, userId, null);
} else {
+ if (packageName == null || authoritiesStr == null) {
+ Slogf.wtf(TAG, "Error parsing EnforcingAdmin, packageName is "
+ + (packageName == null ? "null" : packageName) + ", and authorities is "
+ + (authoritiesStr == null ? "null" : authoritiesStr) + ".");
+ return null;
+ }
String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
ComponentName componentName = className == null
? null : new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
+ // TODO(b/281697976): load active admin
return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 194647fd..0c1c406 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -131,7 +131,8 @@
}
notifyChangeLocked();
- pushToActivityTaskManagerLocked();
+ pushDeviceOwnerUidToActivityTaskManagerLocked();
+ pushProfileOwnerUidsToActivityTaskManagerLocked();
}
}
@@ -163,11 +164,16 @@
}
@GuardedBy("mData")
- private void pushToActivityTaskManagerLocked() {
+ private void pushDeviceOwnerUidToActivityTaskManagerLocked() {
mActivityTaskManagerInternal.setDeviceOwnerUid(getDeviceOwnerUidLocked());
}
@GuardedBy("mData")
+ private void pushProfileOwnerUidsToActivityTaskManagerLocked() {
+ mActivityTaskManagerInternal.setProfileOwnerUids(getProfileOwnerUidsLocked());
+ }
+
+ @GuardedBy("mData")
private void pushToActivityManagerLocked() {
mActivityManagerInternal.setDeviceOwnerUid(getDeviceOwnerUidLocked());
@@ -196,6 +202,11 @@
}
}
+ @GuardedBy("mData")
+ Set<Integer> getProfileOwnerUidsLocked() {
+ return mData.mProfileOwners.keySet();
+ }
+
String getDeviceOwnerPackageName() {
synchronized (mData) {
return mData.mDeviceOwner != null ? mData.mDeviceOwner.packageName : null;
@@ -263,7 +274,7 @@
}
notifyChangeLocked();
- pushToActivityTaskManagerLocked();
+ pushDeviceOwnerUidToActivityTaskManagerLocked();
}
}
@@ -282,7 +293,7 @@
mUserManagerInternal.setDeviceManaged(false);
}
notifyChangeLocked();
- pushToActivityTaskManagerLocked();
+ pushDeviceOwnerUidToActivityTaskManagerLocked();
}
}
@@ -302,6 +313,7 @@
mUserManagerInternal.setUserManaged(userId, true);
}
notifyChangeLocked();
+ pushProfileOwnerUidsToActivityTaskManagerLocked();
}
}
@@ -317,6 +329,7 @@
mUserManagerInternal.setUserManaged(userId, false);
}
notifyChangeLocked();
+ pushProfileOwnerUidsToActivityTaskManagerLocked();
}
}
@@ -328,6 +341,7 @@
ownerInfo.isOrganizationOwnedDevice);
mData.mProfileOwners.put(userId, newOwnerInfo);
notifyChangeLocked();
+ pushProfileOwnerUidsToActivityTaskManagerLocked();
}
}
@@ -345,7 +359,7 @@
mData.mDeviceOwner.packageName, previousDeviceOwnerType);
}
notifyChangeLocked();
- pushToActivityTaskManagerLocked();
+ pushDeviceOwnerUidToActivityTaskManagerLocked();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 63b250d..37d4f95 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -457,6 +457,7 @@
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+ break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 7e5bb0b..7e48407 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -42,6 +42,7 @@
import com.android.internal.util.function.QuadFunction;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParserException;
@@ -53,6 +54,9 @@
import java.util.Set;
final class PolicyDefinition<V> {
+
+ static final String TAG = "PolicyDefinition";
+
private static final int POLICY_FLAG_NONE = 0;
// Only use this flag if a policy can not be applied locally.
@@ -596,22 +600,40 @@
mPolicyKey.saveToXml(serializer);
}
+ @Nullable
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
// TODO: can we avoid casting?
PolicyKey policyKey = readPolicyKeyFromXml(parser);
+ if (policyKey == null) {
+ Slogf.wtf(TAG, "Error parsing PolicyDefinition, PolicyKey is null.");
+ return null;
+ }
PolicyDefinition<V> genericPolicyDefinition =
(PolicyDefinition<V>) POLICY_DEFINITIONS.get(policyKey.getIdentifier());
+ if (genericPolicyDefinition == null) {
+ Slogf.wtf(TAG, "Unknown generic policy key: " + policyKey);
+ return null;
+ }
return genericPolicyDefinition.createPolicyDefinition(policyKey);
}
+ @Nullable
static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
// TODO: can we avoid casting?
PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser);
+ if (policyKey == null) {
+ Slogf.wtf(TAG, "Error parsing PolicyKey, GenericPolicyKey is null");
+ return null;
+ }
PolicyDefinition<PolicyValue<V>> genericPolicyDefinition =
(PolicyDefinition<PolicyValue<V>>) POLICY_DEFINITIONS.get(
policyKey.getIdentifier());
+ if (genericPolicyDefinition == null) {
+ Slogf.wtf(TAG, "Error parsing PolicyKey, Unknown generic policy key: " + policyKey);
+ return null;
+ }
return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index dd4c6af..599c4a7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,11 +19,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.util.Log;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParserException;
@@ -224,6 +224,7 @@
}
}
+ @Nullable
static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
@@ -245,33 +246,55 @@
switch (adminPolicyTag) {
case TAG_ENFORCING_ADMIN_ENTRY:
admin = EnforcingAdmin.readFromXml(parser);
+ if (admin == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_ENFORCING_ADMIN_ENTRY, "
+ + "EnforcingAdmin is null");
+ }
break;
case TAG_POLICY_VALUE_ENTRY:
value = policyDefinition.readPolicyValueFromXml(parser);
+ if (value == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_POLICY_VALUE_ENTRY, "
+ + "PolicyValue is null");
+ }
break;
}
}
if (admin != null) {
policiesSetByAdmins.put(admin, value);
} else {
- Log.e(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY");
+ Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY, EnforcingAdmin "
+ + "is null");
}
break;
case TAG_POLICY_DEFINITION_ENTRY:
policyDefinition = PolicyDefinition.readFromXml(parser);
+ if (policyDefinition == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
+ + "PolicyDefinition is null");
+ }
break;
case TAG_RESOLVED_VALUE_ENTRY:
+ if (policyDefinition == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
+ + "policyDefinition is null");
+ break;
+ }
currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
+ if (currentResolvedPolicy == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
+ + "currentResolvedPolicy is null");
+ }
break;
default:
- Log.e(TAG, "Unknown tag: " + tag);
+ Slogf.wtf(TAG, "Unknown tag: " + tag);
}
}
if (policyDefinition != null) {
return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
} else {
- Log.e("PolicyState", "Error parsing policyState");
+ Slogf.wtf(TAG, "Error parsing policyState, policyDefinition is null");
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 1fe4b57..06f11be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -110,6 +110,12 @@
currentVersion = 4;
}
+ if (currentVersion == 4) {
+ Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+ initializeEffectiveKeepProfilesRunning(allUsersData);
+ currentVersion = 5;
+ }
+
writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
}
@@ -214,6 +220,16 @@
ownerAdmin.suspendedPackages.size(), ownerPackage, ownerUserId));
}
+ private void initializeEffectiveKeepProfilesRunning(
+ SparseArray<DevicePolicyData> allUsersData) {
+ DevicePolicyData systemUserData = allUsersData.get(UserHandle.USER_SYSTEM);
+ if (systemUserData == null) {
+ return;
+ }
+ systemUserData.mEffectiveKeepProfilesRunning = false;
+ Slog.i(LOG_TAG, "Keep profile running effective state set to false");
+ }
+
private OwnersData loadOwners(int[] allUsers) {
OwnersData ownersData = new OwnersData(mPathProvider);
ownersData.load(allUsers);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index c040b19..05780eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -582,6 +582,49 @@
}
@Test
+ public void testModifyingInternalFlags() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setExpedited(true)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+
+ assertEquals(0, job.getInternalFlags());
+
+ // Add single flag
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
+ assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION, job.getInternalFlags());
+
+ // Add multiple flags
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
+ assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ,
+ job.getInternalFlags());
+
+ // Add flag that's already set
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
+ assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ,
+ job.getInternalFlags());
+
+ // Remove multiple
+ job.removeInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION
+ | JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertEquals(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ, job.getInternalFlags());
+
+ // Remove one that isn't set
+ job.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertEquals(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ, job.getInternalFlags());
+
+ // Remove final flag.
+ job.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
+ assertEquals(0, job.getInternalFlags());
+ }
+
+ @Test
public void testShouldTreatAsUserInitiated() {
JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setUserInitiated(false)
@@ -619,6 +662,9 @@
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
+
+ rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
}
@Test
@@ -641,6 +687,9 @@
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
+
+ rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
+ assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 3c77a35..527bc5b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -62,18 +62,15 @@
super(null, null, null, null, 0, null, 0, 0,
mock(BiometricLogger.class), mock(BiometricContext.class));
}
-
- @Override
- public boolean isInterruptable() {
- return true;
- }
}
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Mock
- private InterruptableMonitor<FakeHal> mClientMonitor;
+ private InterruptableMonitor<FakeHal> mInterruptableClientMonitor;
+ @Mock
+ private BaseClientMonitor mNonInterruptableClientMonitor;
@Mock
private ClientMonitorCallback mClientCallback;
@Mock
@@ -84,149 +81,159 @@
ArgumentCaptor<ClientMonitorCallback> mStartedCallbackCaptor;
private Handler mHandler;
- private BiometricSchedulerOperation mOperation;
+ private BiometricSchedulerOperation mInterruptableOperation;
+ private BiometricSchedulerOperation mNonInterruptableOperation;
private boolean mIsDebuggable;
@Before
public void setUp() {
mHandler = new Handler(TestableLooper.get(this).getLooper());
mIsDebuggable = false;
- mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback,
- () -> mIsDebuggable);
+ mInterruptableOperation = new BiometricSchedulerOperation(mInterruptableClientMonitor,
+ mClientCallback, () -> mIsDebuggable);
+ mNonInterruptableOperation = new BiometricSchedulerOperation(mNonInterruptableClientMonitor,
+ mClientCallback, () -> mIsDebuggable);
+
+ when(mInterruptableClientMonitor.isInterruptable()).thenReturn(true);
+ when(mNonInterruptableClientMonitor.isInterruptable()).thenReturn(false);
}
@Test
public void testStartWithCookie() {
final int cookie = 200;
- when(mClientMonitor.getCookie()).thenReturn(cookie);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- assertThat(mOperation.isReadyToStart(mOnStartCallback)).isEqualTo(cookie);
- assertThat(mOperation.isStarted()).isFalse();
- assertThat(mOperation.isCanceling()).isFalse();
- assertThat(mOperation.isFinished()).isFalse();
- verify(mClientMonitor).waitForCookie(any());
+ assertThat(mInterruptableOperation.isReadyToStart(mOnStartCallback)).isEqualTo(cookie);
+ assertThat(mInterruptableOperation.isStarted()).isFalse();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
+ verify(mInterruptableClientMonitor).waitForCookie(any());
- final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
assertThat(started).isTrue();
- verify(mClientMonitor).start(mStartedCallbackCaptor.capture());
- mStartedCallbackCaptor.getValue().onClientStarted(mClientMonitor);
- assertThat(mOperation.isStarted()).isTrue();
+ verify(mInterruptableClientMonitor).start(mStartedCallbackCaptor.capture());
+ mStartedCallbackCaptor.getValue().onClientStarted(mInterruptableClientMonitor);
+ assertThat(mInterruptableOperation.isStarted()).isTrue();
}
@Test
public void testNoStartWithoutCookie() {
final int goodCookie = 20;
final int badCookie = 22;
- when(mClientMonitor.getCookie()).thenReturn(goodCookie);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(goodCookie);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- assertThat(mOperation.isReadyToStart(mOnStartCallback)).isEqualTo(goodCookie);
- final boolean started = mOperation.startWithCookie(mOnStartCallback, badCookie);
+ assertThat(mInterruptableOperation.isReadyToStart(mOnStartCallback)).isEqualTo(goodCookie);
+ final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback,
+ badCookie);
assertThat(started).isFalse();
- assertThat(mOperation.isStarted()).isFalse();
- assertThat(mOperation.isCanceling()).isFalse();
- assertThat(mOperation.isFinished()).isFalse();
- verify(mClientMonitor).waitForCookie(any());
- verify(mClientMonitor, never()).start(any());
+ assertThat(mInterruptableOperation.isStarted()).isFalse();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
+ verify(mInterruptableClientMonitor).waitForCookie(any());
+ verify(mInterruptableClientMonitor, never()).start(any());
}
@Test
public void testSecondStartWithCookieCrashesWhenDebuggable() {
final int cookie = 5;
mIsDebuggable = true;
- when(mClientMonitor.getCookie()).thenReturn(cookie);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
assertThat(started).isTrue();
assertThrows(IllegalStateException.class,
- () -> mOperation.startWithCookie(mOnStartCallback, cookie));
+ () -> mInterruptableOperation.startWithCookie(mOnStartCallback, cookie));
}
@Test
public void testSecondStartWithCookieFailsNicelyWhenNotDebuggable() {
final int cookie = 5;
mIsDebuggable = false;
- when(mClientMonitor.getCookie()).thenReturn(cookie);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(cookie);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ final boolean started = mInterruptableOperation.startWithCookie(mOnStartCallback, cookie);
assertThat(started).isTrue();
- final boolean startedAgain = mOperation.startWithCookie(mOnStartCallback, cookie);
+ final boolean startedAgain = mInterruptableOperation.startWithCookie(mOnStartCallback,
+ cookie);
assertThat(startedAgain).isFalse();
}
@Test
public void startsWhenReadyAndHalAvailable() {
- when(mClientMonitor.getCookie()).thenReturn(0);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.start(mOnStartCallback);
- verify(mClientMonitor).start(mStartedCallbackCaptor.capture());
- mStartedCallbackCaptor.getValue().onClientStarted(mClientMonitor);
+ mInterruptableOperation.start(mOnStartCallback);
+ verify(mInterruptableClientMonitor).start(mStartedCallbackCaptor.capture());
+ mStartedCallbackCaptor.getValue().onClientStarted(mInterruptableClientMonitor);
- assertThat(mOperation.isStarted()).isTrue();
- assertThat(mOperation.isCanceling()).isFalse();
- assertThat(mOperation.isFinished()).isFalse();
+ assertThat(mInterruptableOperation.isStarted()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
- verify(mClientCallback).onClientStarted(eq(mClientMonitor));
- verify(mOnStartCallback).onClientStarted(eq(mClientMonitor));
+ verify(mClientCallback).onClientStarted(eq(mInterruptableClientMonitor));
+ verify(mOnStartCallback).onClientStarted(eq(mInterruptableClientMonitor));
verify(mClientCallback, never()).onClientFinished(any(), anyBoolean());
verify(mOnStartCallback, never()).onClientFinished(any(), anyBoolean());
- mStartedCallbackCaptor.getValue().onClientFinished(mClientMonitor, true);
+ mStartedCallbackCaptor.getValue().onClientFinished(mInterruptableClientMonitor,
+ true);
- assertThat(mOperation.isFinished()).isTrue();
- assertThat(mOperation.isCanceling()).isFalse();
- verify(mClientMonitor).destroy();
- verify(mOnStartCallback).onClientFinished(eq(mClientMonitor), eq(true));
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ verify(mInterruptableClientMonitor).destroy();
+ verify(mOnStartCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(true));
}
@Test
public void startFailsWhenReadyButHalNotAvailable() {
- when(mClientMonitor.getCookie()).thenReturn(0);
- when(mClientMonitor.getFreshDaemon()).thenReturn(null);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(null);
- mOperation.start(mOnStartCallback);
- verify(mClientMonitor, never()).start(any());
+ mInterruptableOperation.start(mOnStartCallback);
+ verify(mInterruptableClientMonitor, never()).start(any());
- assertThat(mOperation.isStarted()).isFalse();
- assertThat(mOperation.isCanceling()).isFalse();
- assertThat(mOperation.isFinished()).isTrue();
+ assertThat(mInterruptableOperation.isStarted()).isFalse();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
- verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor));
- verify(mOnStartCallback, never()).onClientStarted(eq(mClientMonitor));
- verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false));
- verify(mOnStartCallback).onClientFinished(eq(mClientMonitor), eq(false));
+ verify(mClientCallback, never()).onClientStarted(eq(mInterruptableClientMonitor));
+ verify(mOnStartCallback, never()).onClientStarted(eq(mInterruptableClientMonitor));
+ verify(mClientCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(false));
+ verify(mOnStartCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(false));
}
@Test
public void secondStartCrashesWhenDebuggable() {
mIsDebuggable = true;
- when(mClientMonitor.getCookie()).thenReturn(0);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mOperation.start(mOnStartCallback);
+ final boolean started = mInterruptableOperation.start(mOnStartCallback);
assertThat(started).isTrue();
- assertThrows(IllegalStateException.class, () -> mOperation.start(mOnStartCallback));
+ assertThrows(IllegalStateException.class, () -> mInterruptableOperation.start(
+ mOnStartCallback));
}
@Test
public void secondStartFailsNicelyWhenNotDebuggable() {
mIsDebuggable = false;
- when(mClientMonitor.getCookie()).thenReturn(0);
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(0);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- final boolean started = mOperation.start(mOnStartCallback);
+ final boolean started = mInterruptableOperation.start(mOnStartCallback);
assertThat(started).isTrue();
- final boolean startedAgain = mOperation.start(mOnStartCallback);
+ final boolean startedAgain = mInterruptableOperation.start(mOnStartCallback);
assertThat(startedAgain).isFalse();
}
@@ -234,77 +241,78 @@
public void doesNotStartWithCookie() {
// This class only throws exceptions when debuggable.
mIsDebuggable = true;
- when(mClientMonitor.getCookie()).thenReturn(9);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(9);
assertThrows(IllegalStateException.class,
- () -> mOperation.start(mock(ClientMonitorCallback.class)));
+ () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
}
@Test
public void cannotRestart() {
// This class only throws exceptions when debuggable.
mIsDebuggable = true;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.start(mOnStartCallback);
+ mInterruptableOperation.start(mOnStartCallback);
assertThrows(IllegalStateException.class,
- () -> mOperation.start(mock(ClientMonitorCallback.class)));
+ () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
}
@Test
public void abortsNotRunning() {
// This class only throws exceptions when debuggable.
mIsDebuggable = true;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.abort();
+ mInterruptableOperation.abort();
- assertThat(mOperation.isFinished()).isTrue();
- verify(mClientMonitor).unableToStart();
- verify(mClientMonitor).destroy();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
+ verify(mInterruptableClientMonitor).unableToStart();
+ verify(mInterruptableClientMonitor).destroy();
assertThrows(IllegalStateException.class,
- () -> mOperation.start(mock(ClientMonitorCallback.class)));
+ () -> mInterruptableOperation.start(mock(ClientMonitorCallback.class)));
}
@Test
public void abortCrashesWhenDebuggableIfOperationIsRunning() {
mIsDebuggable = true;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.start(mOnStartCallback);
+ mInterruptableOperation.start(mOnStartCallback);
- assertThrows(IllegalStateException.class, () -> mOperation.abort());
+ assertThrows(IllegalStateException.class, () -> mInterruptableOperation.abort());
}
@Test
public void abortFailsNicelyWhenNotDebuggableIfOperationIsRunning() {
mIsDebuggable = false;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.start(mOnStartCallback);
+ mInterruptableOperation.start(mOnStartCallback);
- mOperation.abort();
+ mInterruptableOperation.abort();
}
@Test
public void cancel() {
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- mOperation.start(mOnStartCallback);
- verify(mClientMonitor).start(mStartedCallbackCaptor.capture());
- mStartedCallbackCaptor.getValue().onClientStarted(mClientMonitor);
- mOperation.cancel(mHandler, cancelCb);
+ mInterruptableOperation.start(mOnStartCallback);
+ verify(mInterruptableClientMonitor).start(mStartedCallbackCaptor.capture());
+ mStartedCallbackCaptor.getValue().onClientStarted(mInterruptableClientMonitor);
+ mInterruptableOperation.cancel(mHandler, cancelCb);
- assertThat(mOperation.isCanceling()).isTrue();
- verify(mClientMonitor).cancel();
- verify(mClientMonitor, never()).destroy();
+ assertThat(mInterruptableOperation.isCanceling()).isTrue();
+ verify(mInterruptableClientMonitor).cancel();
+ verify(mInterruptableClientMonitor, never()).destroy();
- mStartedCallbackCaptor.getValue().onClientFinished(mClientMonitor, true);
+ mStartedCallbackCaptor.getValue().onClientFinished(mInterruptableClientMonitor,
+ true);
- assertThat(mOperation.isFinished()).isTrue();
- assertThat(mOperation.isCanceling()).isFalse();
- verify(mClientMonitor).destroy();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ verify(mInterruptableClientMonitor).destroy();
// should be unused since the operation was started
verify(cancelCb, never()).onClientStarted(any());
@@ -313,61 +321,84 @@
@Test
public void cancelWithoutStarting() {
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- mOperation.cancel(mHandler, cancelCb);
+ mInterruptableOperation.cancel(mHandler, cancelCb);
- assertThat(mOperation.isCanceling()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isTrue();
ArgumentCaptor<ClientMonitorCallback> cbCaptor =
ArgumentCaptor.forClass(ClientMonitorCallback.class);
- verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
+ verify(mInterruptableClientMonitor).cancelWithoutStarting(cbCaptor.capture());
- cbCaptor.getValue().onClientFinished(mClientMonitor, true);
- verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true));
- verify(mClientMonitor, never()).start(any());
- verify(mClientMonitor, never()).cancel();
- verify(mClientMonitor).destroy();
+ cbCaptor.getValue().onClientFinished(mInterruptableClientMonitor, true);
+ verify(cancelCb).onClientFinished(eq(mInterruptableClientMonitor), eq(true));
+ verify(mInterruptableClientMonitor, never()).start(any());
+ verify(mInterruptableClientMonitor, never()).cancel();
+ verify(mInterruptableClientMonitor).destroy();
}
@Test
public void cancelCrashesWhenDebuggableIfOperationIsFinished() {
mIsDebuggable = true;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.abort();
- assertThat(mOperation.isFinished()).isTrue();
+ mInterruptableOperation.abort();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- assertThrows(IllegalStateException.class, () -> mOperation.cancel(mHandler, cancelCb));
+ assertThrows(IllegalStateException.class, () -> mInterruptableOperation.cancel(mHandler,
+ cancelCb));
}
@Test
public void cancelFailsNicelyWhenNotDebuggableIfOperationIsFinished() {
mIsDebuggable = false;
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.abort();
- assertThat(mOperation.isFinished()).isTrue();
+ mInterruptableOperation.abort();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
- mOperation.cancel(mHandler, cancelCb);
+ mInterruptableOperation.cancel(mHandler, cancelCb);
}
@Test
- public void markCanceling() {
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ public void markCanceling_interruptableClient() {
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.markCanceling();
+ mInterruptableOperation.markCanceling();
- assertThat(mOperation.isMarkedCanceling()).isTrue();
- assertThat(mOperation.isCanceling()).isFalse();
- assertThat(mOperation.isFinished()).isFalse();
- verify(mClientMonitor, never()).start(any());
- verify(mClientMonitor, never()).cancel();
- verify(mClientMonitor, never()).cancelWithoutStarting(any());
- verify(mClientMonitor, never()).unableToStart();
- verify(mClientMonitor, never()).destroy();
+ assertThat(mInterruptableOperation.isMarkedCanceling()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mInterruptableOperation.isFinished()).isFalse();
+ verify(mInterruptableClientMonitor, never()).start(any());
+ verify(mInterruptableClientMonitor, never()).cancel();
+ verify(mInterruptableClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mInterruptableClientMonitor, never()).unableToStart();
+ verify(mInterruptableClientMonitor, never()).destroy();
+ }
+
+ @Test
+ public void markCanceling_nonInterruptableClient() {
+ mNonInterruptableOperation.markCanceling();
+
+ assertThat(mNonInterruptableOperation.isMarkedCanceling()).isFalse();
+ assertThat(mNonInterruptableOperation.isCanceling()).isFalse();
+ assertThat(mNonInterruptableOperation.isFinished()).isFalse();
+ verify(mNonInterruptableClientMonitor, never()).start(any());
+ verify(mNonInterruptableClientMonitor, never()).cancel();
+ verify(mNonInterruptableClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mNonInterruptableClientMonitor, never()).destroy();
+ }
+
+ @Test
+ public void markCancelingForWatchdog() {
+ mNonInterruptableOperation.markCancelingForWatchdog();
+ mInterruptableOperation.markCancelingForWatchdog();
+
+ assertThat(mInterruptableOperation.isMarkedCanceling()).isTrue();
+ assertThat(mNonInterruptableOperation.isMarkedCanceling()).isTrue();
}
@Test
@@ -381,26 +412,26 @@
}
private void markCancellingAndStart(Integer withCookie) {
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
if (withCookie != null) {
- when(mClientMonitor.getCookie()).thenReturn(withCookie);
+ when(mInterruptableClientMonitor.getCookie()).thenReturn(withCookie);
}
- mOperation.markCanceling();
+ mInterruptableOperation.markCanceling();
final ClientMonitorCallback cb = mock(ClientMonitorCallback.class);
if (withCookie != null) {
- mOperation.startWithCookie(cb, withCookie);
+ mInterruptableOperation.startWithCookie(cb, withCookie);
} else {
- mOperation.start(cb);
+ mInterruptableOperation.start(cb);
}
- assertThat(mOperation.isFinished()).isTrue();
- verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
- verify(mClientMonitor, never()).start(any());
- verify(mClientMonitor, never()).cancel();
- verify(mClientMonitor, never()).cancelWithoutStarting(any());
- verify(mClientMonitor, never()).unableToStart();
- verify(mClientMonitor).destroy();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
+ verify(cb).onClientFinished(eq(mInterruptableClientMonitor), eq(true));
+ verify(mInterruptableClientMonitor, never()).start(any());
+ verify(mInterruptableClientMonitor, never()).cancel();
+ verify(mInterruptableClientMonitor, never()).cancelWithoutStarting(any());
+ verify(mInterruptableClientMonitor, never()).unableToStart();
+ verify(mInterruptableClientMonitor).destroy();
}
@Test
@@ -414,23 +445,23 @@
}
private void cancelWatchdog(boolean start) {
- when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
- mOperation.start(mOnStartCallback);
+ mInterruptableOperation.start(mOnStartCallback);
if (start) {
- verify(mClientMonitor).start(mStartedCallbackCaptor.capture());
- mStartedCallbackCaptor.getValue().onClientStarted(mClientMonitor);
+ verify(mInterruptableClientMonitor).start(mStartedCallbackCaptor.capture());
+ mStartedCallbackCaptor.getValue().onClientStarted(mInterruptableClientMonitor);
}
- mOperation.cancel(mHandler, mock(ClientMonitorCallback.class));
+ mInterruptableOperation.cancel(mHandler, mock(ClientMonitorCallback.class));
- assertThat(mOperation.isCanceling()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isTrue();
// omit call to onClientFinished and trigger watchdog
- mOperation.mCancelWatchdog.run();
+ mInterruptableOperation.mCancelWatchdog.run();
- assertThat(mOperation.isFinished()).isTrue();
- assertThat(mOperation.isCanceling()).isFalse();
- verify(mOnStartCallback).onClientFinished(eq(mClientMonitor), eq(false));
- verify(mClientMonitor).destroy();
+ assertThat(mInterruptableOperation.isFinished()).isTrue();
+ assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ verify(mOnStartCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(false));
+ verify(mInterruptableClientMonitor).destroy();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index 1d6846c..eb2ee35 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -76,7 +76,7 @@
public class PolicyVersionUpgraderTest extends DpmTestBase {
// NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
// to the new version.
- private static final int LATEST_TESTED_VERSION = 4;
+ private static final int LATEST_TESTED_VERSION = 5;
public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
private ComponentName mFakeAdmin;
@@ -313,6 +313,24 @@
}
@Test
+ public void testEffectiveKeepProfilesRunningSet() throws Exception {
+ writeVersionToXml(4);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(5);
+
+ assertThat(readVersionFromXml()).isAtLeast(5);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+ assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false");
+ }
+
+ @Test
public void isLatestVersionTested() {
assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index a7fe64a..be5e262 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.ContextWrapper;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -61,19 +60,9 @@
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
-
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 3df0449..5be3c8e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -37,8 +36,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,27 +55,21 @@
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- @Mock
- private AudioManager mAudioManager;
-
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy, Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
}
- @Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
@Override
boolean isAddressAllocated() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 61ab99b..7845c30 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -27,7 +27,6 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -39,7 +38,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -61,8 +59,6 @@
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- @Mock
- private AudioManager mAudioManager;
@Before
public void setUp() throws Exception {
@@ -70,14 +66,12 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy, Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
-
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
similarity index 71%
rename from services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
rename to services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index 9f295b8..bc09d4b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -24,16 +24,11 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
-import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -59,8 +54,6 @@
import org.junit.Before;
import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -68,20 +61,20 @@
import java.util.Collections;
/**
- * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
+ * Tests that absolute volume behavior (AVB) is enabled and disabled correctly, and that
* the device responds correctly to incoming <Report Audio Status> messages and API calls
- * from AudioService when AVC is active.
+ * from AudioService when AVB is active.
*
* This is an abstract base class. Concrete subclasses specify the type of the local device, and the
* type of the System Audio device. This allows each test to be run for multiple setups.
*
* We test the following pairs of (local device, System Audio device):
- * (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
- * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
- * (TV, Audio System): {@link TvToAudioSystemAvcTest}
+ * (Playback, TV): {@link PlaybackDeviceToTvAvbTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvbTest}
+ * (TV, Audio System): {@link TvToAudioSystemAvbTest}
*/
-public abstract class BaseAbsoluteVolumeControlTest {
- private HdmiControlService mHdmiControlService;
+public abstract class BaseAbsoluteVolumeBehaviorTest {
+ protected HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDevice mHdmiCecLocalDevice;
private FakeHdmiCecConfig mHdmiCecConfig;
@@ -90,19 +83,20 @@
private Context mContextSpy;
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- @Mock protected AudioManager mAudioManager;
- protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
+ protected FakeAudioFramework mAudioFramework;
+ protected AudioManagerWrapper mAudioManager;
+ protected AudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
protected TestLooper mTestLooper = new TestLooper();
protected FakeNativeWrapper mNativeWrapper;
- // Audio Status given by the System Audio device in its initial <Report Audio Status> that
- // triggers AVC being enabled
+ // Default Audio Status given by the System Audio device in its initial <Report Audio Status>
+ // that triggers AVB being enabled
private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
new AudioStatus(50, false);
- // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
- private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
+ // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVB
+ private static final VolumeInfo ENABLE_AVB_VOLUME_INFO =
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
.setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
@@ -110,6 +104,8 @@
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build();
+ private static final int EMPTY_FLAGS = 0;
+
protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
protected abstract int getPhysicalAddress();
@@ -126,17 +122,17 @@
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
+ mAudioFramework = new FakeAudioFramework();
+ mAudioManager = spy(mAudioFramework.getAudioManager());
+ mAudioDeviceVolumeManager = spy(mAudioFramework.getAudioDeviceVolumeManager());
+
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, EMPTY_FLAGS);
+ mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(getDeviceType()),
- mAudioDeviceVolumeManager) {
- @Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
-
+ mAudioManager, mAudioDeviceVolumeManager) {
@Override
protected void writeStringSystemProperty(String key, String value) {
// do nothing
@@ -186,22 +182,12 @@
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
mTestLooper.dispatchAll();
- // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
- doAnswer(invocation -> {
- setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
- return null;
- }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
-
- // Set starting volume behavior
- doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
- .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
-
// Audio service always plays STREAM_MUSIC on the device we need
- doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
- .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
+ mAudioFramework.setDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES,
+ Collections.singletonList(getAudioOutputDevice()));
// Max volume of STREAM_MUSIC
- doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 25);
// Receive messages from devices to make sure they're registered in HdmiCecNetwork
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
@@ -221,16 +207,6 @@
}
/**
- * Simulates the volume behavior of {@code device} being set to {@code behavior}.
- */
- protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
- @AudioManager.DeviceVolumeBehavior int behavior) {
- doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
- mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
- mTestLooper.dispatchAll();
- }
-
- /**
* Changes the setting for CEC volume.
*/
protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
@@ -278,16 +254,6 @@
}
/**
- * Has the device receive a <Report Audio Status> reporting the status in
- * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
- */
- protected void receiveInitialReportAudioStatus() {
- receiveReportAudioStatus(
- INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
- INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
- }
-
- /**
* Has the device receive a <Report Audio Status> message from the System Audio Device.
*/
protected void receiveReportAudioStatus(int volume, boolean mute) {
@@ -300,40 +266,34 @@
}
/**
- * Triggers all the conditions required to enable Absolute Volume Control.
+ * Triggers all the conditions required to enable absolute volume behavior.
*/
- protected void enableAbsoluteVolumeControl() {
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ protected void enableAbsoluteVolumeBehavior() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
enableSystemAudioModeIfNeeded();
- receiveInitialReportAudioStatus();
+ receiveReportAudioStatus(
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
- verifyAbsoluteVolumeEnabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
- /**
- * Verifies that AVC was enabled - that is the audio output device's volume behavior was last
- * set to absolute volume behavior.
- */
- protected void verifyAbsoluteVolumeEnabled() {
- InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
- inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
- eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
- inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
- eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
- }
+ protected void enableAdjustOnlyAbsoluteVolumeBehavior() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+ receiveReportAudioStatus(
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
- /**
- * Verifies that AVC was disabled - that is, the audio output device's volume behavior was
- * last set to something other than absolute volume behavior.
- */
- protected void verifyAbsoluteVolumeDisabled() {
- InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
- inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
- eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
- inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
- eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
}
protected void verifyGiveAudioStatusNeverSent() {
@@ -350,7 +310,8 @@
@Test
public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
@@ -365,7 +326,8 @@
@Test
public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
verifyGiveAudioStatusNeverSent();
@@ -376,7 +338,8 @@
@Test
public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
verifyGiveAudioStatusNeverSent();
@@ -388,7 +351,8 @@
@Test
public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
enableSystemAudioModeIfNeeded();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
verifyGiveAudioStatusNeverSent();
@@ -404,7 +368,9 @@
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
verifyGiveAudioStatusNeverSent();
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mTestLooper.dispatchAll();
verifyGiveAudioStatusSent();
}
@@ -413,7 +379,8 @@
// Only run when the System Audio device is an Audio System.
assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
verifyGiveAudioStatusNeverSent();
@@ -423,62 +390,97 @@
}
@Test
- public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
+ public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avbEnabled() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
enableSystemAudioModeIfNeeded();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
- setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
- // Verify that AVC was never enabled
- verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
- eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
- receiveInitialReportAudioStatus();
+ // AVB should not be enabled before receiving <Report Audio Status>
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
- verifyAbsoluteVolumeEnabled();
+ receiveReportAudioStatus(60, false);
+
+ // Check that absolute volume behavior was the last one adopted
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+
+ // Check that the volume and mute status received were included when setting AVB
+ verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()),
+ eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setVolumeIndex(60)
+ .setMuted(false)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build()),
+ any(), any(), anyBoolean());
}
@Test
- public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
- enableAbsoluteVolumeControl();
+ public void giveAudioStatusSent_reportAudioStatusVolumeOutOfBounds_avbNotEnabled() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ receiveReportAudioStatus(127, false);
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
+
+ @Test
+ public void avbEnabled_cecVolumeDisabled_avbDisabled() {
+ enableAbsoluteVolumeBehavior();
setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
- verifyAbsoluteVolumeDisabled();
+
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
@Test
- public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
- enableAbsoluteVolumeControl();
+ public void avbEnabled_setAudioVolumeLevelNotSupported_avbDisabled() {
+ enableAbsoluteVolumeBehavior();
receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
- verifyAbsoluteVolumeDisabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
@Test
- public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
- enableAbsoluteVolumeControl();
+ public void avbEnabled_setAudioVolumeLevelFeatureAborted_avbDisabled() {
+ enableAbsoluteVolumeBehavior();
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
mTestLooper.dispatchAll();
- verifyAbsoluteVolumeDisabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
@Test
- public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
+ public void avbEnabled_systemAudioModeDisabled_avbDisabled() {
// Only run when the System Audio device is an Audio System.
assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
- enableAbsoluteVolumeControl();
+ enableAbsoluteVolumeBehavior();
receiveSetSystemAudioMode(false);
- verifyAbsoluteVolumeDisabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
@Test
- public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+ public void avbEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
// Initial <Report Audio Status> has volume=50 and mute=false
- enableAbsoluteVolumeControl();
+ enableAbsoluteVolumeBehavior();
// New volume and mute status: sets both
receiveReportAudioStatus(20, true);
@@ -512,12 +514,20 @@
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
+ // Volume not within range [0, 100]: sets neither volume nor mute
+ receiveReportAudioStatus(127, true);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
+ clearInvocations(mAudioManager);
+
// If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
// volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
// still cause us to call setStreamVolume()
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
getAudioOutputDevice(),
- new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ new VolumeInfo.Builder(ENABLE_AVB_VOLUME_INFO)
.setVolumeIndex(20)
.build()
);
@@ -530,13 +540,13 @@
}
@Test
- public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
- enableAbsoluteVolumeControl();
+ public void avbEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
+ enableAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
getAudioOutputDevice(),
- ENABLE_AVC_VOLUME_INFO,
+ ENABLE_AVB_VOLUME_INFO,
AudioManager.ADJUST_RAISE,
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
);
@@ -554,14 +564,16 @@
}
@Test
- public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
- enableAbsoluteVolumeControl();
+ public void avbEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
+ enableAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
getAudioOutputDevice(),
- new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setVolumeIndex(20)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build()
);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java
new file mode 100644
index 0000000..4c12e436
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+
+import org.junit.Test;
+
+/**
+ * Base class for tests for absolute volume behavior on Playback devices. Contains tests that are
+ * relevant to Playback devices but not to TVs.
+ *
+ * Subclasses contain tests for the following pairs of (local device, System Audio device):
+ * (Playback, TV): {@link PlaybackDeviceToTvAvbTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvbTest}
+ */
+public abstract class BasePlaybackDeviceAvbTest extends BaseAbsoluteVolumeBehaviorTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDevicePlayback(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x1100;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+ }
+
+ /**
+ * Unlike TVs, Playback devices don't start the process for adopting adjust-only AVB
+ * if the System Audio device doesn't support <Set Audio Volume Level>
+ */
+ @Test
+ public void savlNotSupported_allOtherConditionsMet_giveAudioStatusNotSent() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 28ba4bb..9b65762 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -54,9 +54,13 @@
@Before
public void SetUp() {
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
+
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
HdmiControlService hdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.emptyList(), audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
void sendCecCommand(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index c4c5c2a..af4eab3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -31,7 +31,6 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -76,19 +75,11 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index b571f43..5070b08 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -97,10 +97,13 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 9c1b670..49023c6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -106,10 +106,13 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
deleted file mode 100644
index d33ef9b..0000000
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.hdmi;
-
-import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
-import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceVolumeManager;
-import android.media.AudioManager;
-import android.media.VolumeInfo;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
- */
-public class FakeAudioDeviceVolumeManagerWrapper implements
- AudioDeviceVolumeManagerWrapperInterface {
-
- private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
-
- public FakeAudioDeviceVolumeManagerWrapper() {
- mVolumeBehaviorListeners = new HashSet<>();
- }
-
- @Override
- public void addOnDeviceVolumeBehaviorChangedListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnDeviceVolumeBehaviorChangedListener listener)
- throws SecurityException {
- mVolumeBehaviorListeners.add(listener);
- }
-
- @Override
- public void removeOnDeviceVolumeBehaviorChangedListener(
- @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
- mVolumeBehaviorListeners.remove(listener);
- }
-
- @Override
- public void setDeviceAbsoluteVolumeBehavior(
- @NonNull AudioDeviceAttributes device,
- @NonNull VolumeInfo volume,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnAudioDeviceVolumeChangedListener vclistener,
- boolean handlesVolumeAdjustment) {
- // Notify all volume behavior listeners that the device adopted absolute volume behavior
- for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
- listener.onDeviceVolumeBehaviorChanged(device,
- AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
new file mode 100644
index 0000000..7294ba6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
@@ -0,0 +1,239 @@
+/*
+ * 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.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.VolumeInfo;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Contains a fake AudioManager and fake AudioDeviceVolumeManager.
+ * Stores the shared state for these managers, simulating a fake AudioService.
+ */
+public class FakeAudioFramework {
+
+ private final FakeAudioManagerWrapper mAudioManager = new FakeAudioManagerWrapper();
+ private final FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager =
+ new FakeAudioDeviceVolumeManagerWrapper();
+
+ private static final int DEFAULT_DEVICE_VOLUME_BEHAVIOR =
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
+ private final Map<AudioDeviceAttributes, Integer> mDeviceVolumeBehaviors = new HashMap<>();
+
+ private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners =
+ new HashSet<>();
+
+ private final Map<AudioAttributes, List<AudioDeviceAttributes>> mDevicesForAttributes =
+ new HashMap<>();
+
+ private static final int DEFAULT_VOLUME = 0;
+ private final Map<Integer, Integer> mStreamVolumes = new HashMap<>();
+
+ private static final int DEFAULT_MAX_VOLUME = 100;
+ private final Map<Integer, Integer> mStreamMaxVolumes = new HashMap<>();
+
+ private static final boolean DEFAULT_MUTE_STATUS = false;
+ private final Map<Integer, Boolean> mStreamMuteStatuses = new HashMap<>();
+
+ public FakeAudioFramework() {
+ }
+
+ /**
+ * Returns a fake AudioManager whose methods affect this object's internal state.
+ */
+ public FakeAudioManagerWrapper getAudioManager() {
+ return mAudioManager;
+ }
+
+ public class FakeAudioManagerWrapper implements AudioManagerWrapper {
+ @Override
+ public void adjustStreamVolume(int streamType, int direction,
+ @AudioManager.PublicVolumeFlags int flags) {
+ switch (direction) {
+ case AudioManager.ADJUST_MUTE:
+ mStreamMuteStatuses.put(streamType, true);
+ break;
+ case AudioManager.ADJUST_UNMUTE:
+ mStreamMuteStatuses.put(streamType, false);
+ break;
+ default:
+ // Other adjustments not implemented
+ }
+ }
+
+ @Override
+ public void setStreamVolume(int streamType, int index,
+ @AudioManager.PublicVolumeFlags int flags) {
+ mStreamVolumes.put(streamType, index);
+ }
+
+ @Override
+ public int getStreamVolume(int streamType) {
+ return mStreamVolumes.getOrDefault(streamType, DEFAULT_VOLUME);
+ }
+
+ @Override
+ public int getStreamMinVolume(int streamType) {
+ return 0;
+ }
+
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ return mStreamMaxVolumes.getOrDefault(streamType, DEFAULT_MAX_VOLUME);
+ }
+
+ @Override
+ public boolean isStreamMute(int streamType) {
+ return mStreamMuteStatuses.getOrDefault(streamType, DEFAULT_MUTE_STATUS);
+ }
+
+ @Override
+ public void setStreamMute(int streamType, boolean state) {
+ mStreamMuteStatuses.put(streamType, state);
+ }
+
+ @Override
+ public int setHdmiSystemAudioSupported(boolean on) {
+ return AudioSystem.DEVICE_NONE;
+ }
+
+ @Override
+ public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state) {
+ // Do nothing
+ }
+
+ @Override
+ public void setWiredDeviceConnectionState(int device, int state, String address,
+ String name) {
+ // Do nothing
+ }
+
+
+ @Override
+ @AudioManager.DeviceVolumeBehavior
+ public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
+ return mDeviceVolumeBehaviors.getOrDefault(device, DEFAULT_DEVICE_VOLUME_BEHAVIOR);
+ }
+
+ public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
+ setVolumeBehaviorHelper(device, deviceVolumeBehavior);
+ }
+
+ @Override
+ @NonNull
+ public List<AudioDeviceAttributes> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ return mDevicesForAttributes.getOrDefault(attributes, Collections.emptyList());
+ }
+ }
+
+ /**
+ * Returns a fake AudioDeviceVolumeManager whose methods affect this object's internal state.
+ */
+ public FakeAudioDeviceVolumeManagerWrapper getAudioDeviceVolumeManager() {
+ return mAudioDeviceVolumeManager;
+ }
+
+ public class FakeAudioDeviceVolumeManagerWrapper implements AudioDeviceVolumeManagerWrapper {
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mVolumeBehaviorListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
+ mVolumeBehaviorListeners.remove(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ setVolumeBehaviorHelper(device,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+ }
+ }
+
+ /**
+ * Allows tests to manipulate the return value of
+ * {@link FakeAudioManagerWrapper#getDevicesForAttributes}
+ */
+ public void setDevicesForAttributes(AudioAttributes attributes,
+ List<AudioDeviceAttributes> devices) {
+ mDevicesForAttributes.put(attributes, devices);
+ }
+
+ /**
+ * Allows tests to manipulate the return value of
+ * {@link FakeAudioManagerWrapper#getStreamMaxVolume}
+ */
+ public void setStreamMaxVolume(int streamType, int maxVolume) {
+ mStreamMaxVolumes.put(streamType, maxVolume);
+ }
+
+ /**
+ * Helper method for changing an audio device's volume behavior. Notifies listeners.
+ */
+ private void setVolumeBehaviorHelper(AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int newVolumeBehavior) {
+
+ int currentVolumeBehavior = mDeviceVolumeBehaviors.getOrDefault(
+ device, DEFAULT_DEVICE_VOLUME_BEHAVIOR);
+
+ mDeviceVolumeBehaviors.put(device, newVolumeBehavior);
+
+ if (newVolumeBehavior != currentVolumeBehavior) {
+ // Notify volume behavior listeners
+ for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
+ listener.onDeviceVolumeBehaviorChanged(device, newVolumeBehavior);
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index e3d9558..5e54d3b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -86,9 +86,11 @@
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()));
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index a7232fe..0870bac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -100,9 +100,11 @@
public void SetUp() {
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlServiceSpy = spy(new HdmiControlService(
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()));
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index f5c0f2a..a6e05dd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -54,23 +54,23 @@
@RunWith(JUnit4.class)
/** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */
public class HdmiCecLocalDeviceAudioSystemTest {
-
private static final HdmiCecMessage MESSAGE_REQUEST_SAD_LCPM =
HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
ADDR_TV, ADDR_AUDIO_SYSTEM, new int[] {Constants.AUDIO_CODEC_LPCM});
+ private static final int EMPTY_FLAGS = 0;
+
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
private FakeNativeWrapper mNativeWrapper;
private FakePowerManagerWrapper mPowerManager;
+ private FakeAudioFramework mAudioFramework;
+ private AudioManagerWrapper mAudioManager;
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- private int mMusicVolume;
- private int mMusicMaxVolume;
- private boolean mMusicMute;
private static final int SELF_PHYSICAL_ADDRESS = 0x2000;
private static final int HDMI_1_PHYSICAL_ADDRESS = 0x2100;
private static final int HDMI_2_PHYSICAL_ADDRESS = 0x2200;
@@ -88,66 +88,12 @@
mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mAudioFramework = new FakeAudioFramework();
+ mAudioManager = mAudioFramework.getAudioManager();
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- mLocalDeviceTypes,
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public int getStreamVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicVolume;
- default:
- return 0;
- }
- }
-
- @Override
- public boolean isStreamMute(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMute;
- default:
- return false;
- }
- }
-
- @Override
- public int getStreamMaxVolume(int streamType) {
- switch (streamType) {
- case STREAM_MUSIC:
- return mMusicMaxVolume;
- default:
- return 100;
- }
- }
-
- @Override
- public void adjustStreamVolume(
- int streamType, int direction, int flags) {
- switch (streamType) {
- case STREAM_MUSIC:
- if (direction == AudioManager.ADJUST_UNMUTE) {
- mMusicMute = false;
- } else if (direction == AudioManager.ADJUST_MUTE) {
- mMusicMute = true;
- }
- break;
- default:
- }
- }
-
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
-
+ mLocalDeviceTypes, mAudioManager,
+ mAudioFramework.getAudioDeviceVolumeManager()) {
@Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceInfo = device;
@@ -236,10 +182,12 @@
@Test
public void handleGiveAudioStatus_volume_10_mute_true() throws Exception {
- mMusicVolume = 10;
- mMusicMute = true;
- mMusicMaxVolume = 20;
- int scaledVolume = VolumeControlAction.scaleToCecVolume(10, mMusicMaxVolume);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 10, EMPTY_FLAGS);
+ mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_MUTE,
+ EMPTY_FLAGS);
+ mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 20);
+ int scaledVolume = VolumeControlAction.scaleToCecVolume(10,
+ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildReportAudioStatus(
ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true);
@@ -303,7 +251,7 @@
@Test
@Ignore("b/120845532")
public void handleSetSystemAudioMode_setOn_orignalOff() throws Exception {
- mMusicMute = true;
+ mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
HdmiCecMessage messageSet =
HdmiCecMessageBuilder.buildSetSystemAudioMode(ADDR_TV, ADDR_AUDIO_SYSTEM, true);
HdmiCecMessage messageGive =
@@ -326,13 +274,13 @@
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
- assertThat(mMusicMute).isFalse();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
}
@Test
@Ignore("b/120845532")
public void handleSystemAudioModeRequest_turnOffByTv() throws Exception {
- assertThat(mMusicMute).isFalse();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
// Check if feature correctly turned off
HdmiCecMessage messageGive =
HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -354,7 +302,7 @@
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
- assertThat(mMusicMute).isTrue();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
}
@Test
@@ -368,7 +316,7 @@
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
- assertThat(mMusicMute).isTrue();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
}
@Test
@@ -463,13 +411,13 @@
public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
- mMusicMute = false;
+ mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode();
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
- assertThat(mMusicMute).isFalse();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isFalse();
assertThat(mNativeWrapper.getResultMessages()).isEmpty();
}
@@ -477,13 +425,13 @@
public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
- mMusicMute = false;
+ mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
HdmiCecMessage expectedMessage =
HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode();
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
- assertThat(mMusicMute).isTrue();
+ assertThat(mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)).isTrue();
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
@@ -705,8 +653,6 @@
@Test
public void giveAudioStatus_volumeEnabled() {
- mMusicVolume = 50;
- mMusicMaxVolume = 100;
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
@@ -733,8 +679,6 @@
@Test
public void giveAudioStatus_volumeDisabled() {
- mMusicVolume = 50;
- mMusicMaxVolume = 100;
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
@@ -761,8 +705,6 @@
@Test
public void reportAudioStatus_volumeEnabled() {
- mMusicVolume = 50;
- mMusicMaxVolume = 100;
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_ENABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
@@ -786,8 +728,6 @@
@Test
public void reportAudioStatus_volumeDisabled() {
- mMusicVolume = 50;
- mMusicMaxVolume = 100;
mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(
HdmiControlManager.VOLUME_CONTROL_DISABLED);
mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index beba9c6..40c762c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -31,7 +31,6 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -46,7 +45,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -88,8 +86,6 @@
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
- @Mock
- protected AudioManager mAudioManager;
@Before
public void setUp() {
@@ -98,10 +94,12 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
void wakeUp() {
@@ -110,11 +108,6 @@
}
@Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
-
- @Override
void pauseActiveMediaSessions() {
mActiveMediaSessionsPaused = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 9c5c0d4..9882670 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -33,6 +33,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -53,8 +54,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
@@ -128,18 +127,17 @@
private boolean isControlEnabled;
private int mPowerStatus;
- @Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManager;
@Before
public void SetUp() {
- MockitoAnnotations.initMocks(this);
-
Context context = InstrumentationRegistry.getTargetContext();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+ mAudioManager = spy(audioFramework.getAudioManager());
mHdmiControlService =
new HdmiControlService(context, Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ mAudioManager, audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return isControlEnabled;
@@ -171,11 +169,6 @@
void wakeUp() {
mWakeupMessageReceived = true;
}
-
- @Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
};
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index d2b1bdd..d52b7ea 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -33,9 +33,9 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
@@ -55,8 +55,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
@@ -122,20 +120,21 @@
}
}
- @Mock
- private AudioManager mAudioManager;
+ private FakeAudioFramework mAudioFramework;
+ private AudioManagerWrapper mAudioManager;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ mAudioFramework = new FakeAudioFramework();
+ mAudioManager = spy(mAudioFramework.getAudioManager());
+
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ mAudioManager, mAudioFramework.getAudioDeviceVolumeManager()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -167,11 +166,6 @@
}
@Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
-
- @Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListeners.add(new DeviceEventListener(device, status));
}
@@ -967,7 +961,7 @@
@Test
public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
- when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+ mAudioFramework.setStreamMaxVolume(AudioManager.STREAM_MUSIC, 25);
// Max volume of STREAM_MUSIC is retrieved on boot
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 81cfdbe..bdf3a5f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -58,9 +58,11 @@
@Before
public void setUp() throws Exception {
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
HdmiControlService mHdmiControlService = new HdmiControlService(
InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper());
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager());
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index d341153..1ad9ce0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -67,8 +67,11 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
+
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListenerStatuses.add(status);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 55e8b20..c002067 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -64,9 +64,11 @@
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(contextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index fd6eb92..0e6b412 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -49,7 +49,6 @@
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
-import android.media.AudioManager;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
@@ -64,9 +63,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.Mock;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
@@ -96,19 +93,17 @@
private HdmiPortInfo[] mHdmiPortInfo;
private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
- @Mock protected AudioManager mAudioManager;
-
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, mLocalDeviceTypes,
- new FakeAudioDeviceVolumeManagerWrapper()));
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -171,7 +166,6 @@
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mHdmiControlServiceSpy.setAudioManager(mAudioManager);
mHdmiControlServiceSpy.setEarcSupported(true);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
index c3aec84..185f90f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -35,7 +36,6 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioDescriptor;
import android.media.AudioDeviceAttributes;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -49,7 +49,6 @@
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -78,8 +77,7 @@
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
- @Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManager;
@Captor
ArgumentCaptor<AudioDeviceAttributes> mAudioAttributesCaptor;
@@ -91,10 +89,13 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+ mAudioManager = spy(audioFramework.getAudioManager());
+
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ mAudioManager, audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
@@ -114,11 +115,6 @@
boolean isPowerStandby() {
return false;
}
-
- @Override
- AudioManager getAudioManager() {
- return mAudioManager;
- }
};
mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index b0e8ca7..1172a87 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -31,7 +31,6 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -88,20 +87,11 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
-
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
similarity index 72%
rename from services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
rename to services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
index 6418602..43ab804 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
@@ -16,10 +16,12 @@
package com.android.server.hdmi;
+import static com.google.common.truth.Truth.assertThat;
+
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
-import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -33,33 +35,13 @@
import java.util.Arrays;
/**
- * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * Tests for absolute volume behavior where the local device is a Playback device and the
* System Audio device is an Audio System.
*/
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
-public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
-
- @Override
- protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
- return new HdmiCecLocalDevicePlayback(hdmiControlService);
- }
-
- @Override
- protected int getPhysicalAddress() {
- return 0x1100;
- }
-
- @Override
- protected int getDeviceType() {
- return HdmiDeviceInfo.DEVICE_PLAYBACK;
- }
-
- @Override
- protected AudioDeviceAttributes getAudioOutputDevice() {
- return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
- }
+public class PlaybackDeviceToAudioSystemAvbTest extends BasePlaybackDeviceAvbTest {
@Override
protected int getSystemAudioDeviceLogicalAddress() {
@@ -72,17 +54,18 @@
}
/**
- * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
+ * AVB is disabled if the Audio System disables System Audio mode, and the TV has unknown
* support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
* <Set Audio Volume Level> and sends <Report Audio Status>.
*/
@Test
public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
- enableAbsoluteVolumeControl();
+ enableAbsoluteVolumeBehavior();
- // Audio System disables System Audio Mode. AVC should be disabled.
+ // Audio System disables System Audio Mode. AVB should be disabled.
receiveSetSystemAudioMode(false);
- verifyAbsoluteVolumeDisabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
// TV reports support for <Set Audio Volume Level>
mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
@@ -102,6 +85,7 @@
false));
mTestLooper.dispatchAll();
- verifyAbsoluteVolumeEnabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
rename to services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
index 504c3bc..9b343e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
@@ -16,12 +16,14 @@
package com.android.server.hdmi;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.clearInvocations;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
-import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -34,33 +36,13 @@
import java.util.Collections;
/**
- * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * Tests for absolute volume behavior where the local device is a Playback device and the
* System Audio device is a TV.
*/
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
-public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
-
- @Override
- protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
- return new HdmiCecLocalDevicePlayback(hdmiControlService);
- }
-
- @Override
- protected int getPhysicalAddress() {
- return 0x1100;
- }
-
- @Override
- protected int getDeviceType() {
- return HdmiDeviceInfo.DEVICE_PLAYBACK;
- }
-
- @Override
- protected AudioDeviceAttributes getAudioOutputDevice() {
- return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
- }
+public class PlaybackDeviceToTvAvbTest extends BasePlaybackDeviceAvbTest {
@Override
protected int getSystemAudioDeviceLogicalAddress() {
@@ -73,17 +55,18 @@
}
/**
- * AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
+ * AVB is disabled when an Audio System with unknown support for <Set Audio Volume Level>
* becomes the System Audio device. It is enabled once the Audio System reports that it
* supports <Set Audio Volume Level> and sends <Report Audio Status>.
*/
@Test
public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
- enableAbsoluteVolumeControl();
+ enableAbsoluteVolumeBehavior();
- // Audio System enables System Audio Mode. AVC should be disabled.
+ // Audio System enables System Audio Mode. AVB should be disabled.
receiveSetSystemAudioMode(true);
- verifyAbsoluteVolumeDisabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
@@ -105,6 +88,7 @@
false));
mTestLooper.dispatchAll();
- verifyAbsoluteVolumeEnabled();
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 89743cd..9f0a44c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -30,7 +30,6 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -65,20 +64,11 @@
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
-
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 1c19341..043db1e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -95,9 +95,12 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService =
new HdmiControlService(context, Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 5b1bdf6..1bc99b6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -146,10 +146,13 @@
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context);
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isCecControlEnabled() {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index cac7815..a73f4aa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -80,9 +80,11 @@
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_PLAYBACK),
- new FakeAudioDeviceVolumeManagerWrapper()));
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -152,11 +154,11 @@
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
- @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ @DeviceFeatures.FeatureSupportStatus int savlSupport =
mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
.getDeviceFeatures().getSetAudioVolumeLevelSupport();
- assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED);
+ assertThat(savlSupport).isEqualTo(FEATURE_SUPPORTED);
assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -172,11 +174,11 @@
Constants.ABORT_UNRECOGNIZED_OPCODE));
mTestLooper.dispatchAll();
- @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ @DeviceFeatures.FeatureSupportStatus int savlSupport =
mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
.getDeviceFeatures().getSetAudioVolumeLevelSupport();
- assertThat(avcSupport).isEqualTo(FEATURE_NOT_SUPPORTED);
+ assertThat(savlSupport).isEqualTo(FEATURE_NOT_SUPPORTED);
assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
@@ -189,11 +191,11 @@
mPlaybackDevice.addAndStartAction(mAction);
mTestLooper.dispatchAll();
- @DeviceFeatures.FeatureSupportStatus int avcSupport =
+ @DeviceFeatures.FeatureSupportStatus int savlSupport =
mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV)
.getDeviceFeatures().getSetAudioVolumeLevelSupport();
- assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN);
+ assertThat(savlSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN);
assertThat(mTestCallback.getResult()).isEqualTo(
HdmiControlManager.RESULT_COMMUNICATION_FAILED);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index c40cd0e..c3beff7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -29,7 +29,6 @@
import android.content.ContextWrapper;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -68,20 +67,11 @@
Looper myLooper = mTestLooper.getLooper();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
mHdmiControlService = new HdmiControlService(mContextSpy,
Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
- new FakeAudioDeviceVolumeManagerWrapper()) {
- @Override
- AudioManager getAudioManager() {
- return new AudioManager() {
- @Override
- public void setWiredDeviceConnectionState(
- int type, int state, String address, String name) {
- // Do nothing.
- }
- };
- }
-
+ audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index b13ef4f..f801f88 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioManager;
import android.os.Looper;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -68,8 +67,11 @@
Context context = InstrumentationRegistry.getTargetContext();
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
HdmiControlService hdmiControlService = new HdmiControlService(context,
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ Collections.emptyList(), audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
@Override
void sendCecCommand(
HdmiCecMessage command, @Nullable SendMessageCallback callback) {
@@ -100,36 +102,6 @@
}
@Override
- AudioManager getAudioManager() {
- return new AudioManager() {
-
- @Override
- public int setHdmiSystemAudioSupported(boolean on) {
- return 0;
- }
-
- @Override
- public int getStreamVolume(int streamType) {
- return 0;
- }
-
- @Override
- public boolean isStreamMute(int streamType) {
- return false;
- }
-
- @Override
- public int getStreamMaxVolume(int streamType) {
- return 100;
- }
-
- @Override
- public void adjustStreamVolume(
- int streamType, int direction, int flags) {}
- };
- }
-
- @Override
boolean isPowerStandby() {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
new file mode 100644
index 0000000..079ef2e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for absolute volume behavior where the local device is a TV and the System Audio device
+ * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehaviorTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDeviceTv(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x0000;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_TV;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_AUDIO_SYSTEM;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ }
+
+ /**
+ * TVs start the process for adopting adjust-only AVB if the System Audio device doesn't
+ * support <Set Audio Volume Level>
+ */
+ @Test
+ public void savlNotSupported_allOtherConditionsMet_giveAudioStatusSent() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ verifyGiveAudioStatusNeverSent();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void savlNotSupported_systemAudioDeviceSendsReportAudioStatus_adjustOnlyAvbEnabled() {
+ mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+
+ // Adjust-only AVB should not be enabled before receiving <Report Audio Status>
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+
+ receiveReportAudioStatus(20, false);
+
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+
+ verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ eq(getAudioOutputDevice()),
+ eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setVolumeIndex(20)
+ .setMuted(false)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build()),
+ any(), any(), anyBoolean());
+ }
+
+
+ @Test
+ public void avbEnabled_savlNotSupported_receiveReportAudioStatus_switchToAdjustOnlyAvb() {
+ enableAbsoluteVolumeBehavior();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+
+ receiveReportAudioStatus(40, true);
+
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+
+ verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ eq(getAudioOutputDevice()),
+ eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setVolumeIndex(40)
+ .setMuted(true)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build()),
+ any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void avbEnabled_savlFeatureAborted_receiveReportAudioStatus_switchToAdjustOnlyAvb() {
+ enableAbsoluteVolumeBehavior();
+
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
+ mTestLooper.dispatchAll();
+
+ receiveReportAudioStatus(40, true);
+
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+
+ verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
+ eq(getAudioOutputDevice()),
+ eq(new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setVolumeIndex(40)
+ .setMuted(true)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build()),
+ any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void adjustOnlyAvbEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+ enableAdjustOnlyAbsoluteVolumeBehavior();
+
+ // New volume and mute status: sets both
+ receiveReportAudioStatus(20, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New volume only: sets volume only
+ receiveReportAudioStatus(32, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New mute status only: sets mute only
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // Repeat of earlier message: sets neither volume nor mute
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+
+ // Volume not within range [0, 100]: sets neither volume nor mute
+ receiveReportAudioStatus(127, true);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
+ }
+
+ @Test
+ public void adjustOnlyAvbEnabled_audioDeviceVolumeAdjusted_sendsUcpAndGiveAudioStatus() {
+ enableAdjustOnlyAbsoluteVolumeBehavior();
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build(),
+ AudioManager.ADJUST_RAISE,
+ AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+ enableAdjustOnlyAbsoluteVolumeBehavior();
+
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setVolumeIndex(20)
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build()
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
deleted file mode 100644
index 41c0e0d..0000000
--- a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.hdmi;
-
-import android.hardware.hdmi.HdmiDeviceInfo;
-import android.media.AudioDeviceAttributes;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Tests for Absolute Volume Control where the local device is a TV and the System Audio device
- * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
- */
-@SmallTest
-@Presubmit
-@RunWith(JUnit4.class)
-public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
-
- @Override
- protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
- return new HdmiCecLocalDeviceTv(hdmiControlService);
- }
-
- @Override
- protected int getPhysicalAddress() {
- return 0x0000;
- }
-
- @Override
- protected int getDeviceType() {
- return HdmiDeviceInfo.DEVICE_TV;
- }
-
- @Override
- protected AudioDeviceAttributes getAudioOutputDevice() {
- return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
- }
-
- @Override
- protected int getSystemAudioDeviceLogicalAddress() {
- return Constants.ADDR_AUDIO_SYSTEM;
- }
-
- @Override
- protected int getSystemAudioDeviceType() {
- return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 95fc0fa..2671e77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -645,7 +645,7 @@
runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -659,7 +659,7 @@
"disallowed_unsupportedUsecase_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -673,7 +673,7 @@
"disallowed_callingUidProcessStateTop_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -687,7 +687,7 @@
"disallowed_realCallingUidProcessStateTop_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -701,7 +701,7 @@
"disallowed_hasForegroundActivities_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- true, false, false, false, false, false, false);
+ true, false, false, false, false, false, false, false);
}
/**
@@ -715,7 +715,7 @@
"disallowed_pinned_singleinstance_aborted", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, true, false);
+ false, false, false, false, false, false, true, false);
}
/**
@@ -729,7 +729,7 @@
runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", false,
Process.ROOT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -743,7 +743,7 @@
runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", false,
Process.SYSTEM_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -757,7 +757,7 @@
runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
Process.NFC_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -772,7 +772,7 @@
"disallowed_callingUidHasVisibleWindow_notAborted", false,
UNIMPORTANT_UID, true, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -788,7 +788,7 @@
"disallowed_realCallingUidHasVisibleWindow_abortedInU", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, true, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -803,7 +803,7 @@
"disallowed_callerIsRecents_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, true, false, false, false, false, false);
+ false, true, false, false, false, false, false, false);
}
/**
@@ -818,7 +818,7 @@
"disallowed_callerIsAllowed_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, true, false, false, false, false);
+ false, false, true, false, false, false, false, false);
}
/**
@@ -834,7 +834,7 @@
false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, true, false, false, false);
+ false, false, false, true, false, false, false, false);
}
/**
@@ -850,7 +850,23 @@
"disallowed_callingPackageNameIsDeviceOwner_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, true, false, false);
+ false, false, false, false, true, false, false, false);
+ }
+
+ /**
+ * This test ensures that supported usecases aren't aborted when background starts are
+ * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
+ * this case the caller is a affiliated profile owner.
+ */
+ @Test
+ public void
+ testBackgroundActivityStartsDisallowed_isAffiliatedProfileOwnerNotAborted() {
+ doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+ runAndVerifyBackgroundActivityStartsSubtest(
+ "disallowed_callingUidIsAffiliatedProfileOwner_notAborted", false,
+ UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
+ false, false, false, false, false, true, false, false);
}
/**
@@ -865,7 +881,7 @@
"disallowed_callerHasSystemExemptAppOpNotAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, true);
+ false, false, false, false, false, false, false, true);
}
/**
@@ -881,7 +897,7 @@
"disallowed_callingPackageNameIsIme_notAborted", false,
CURRENT_IME_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, false, false, false, false, false, false);
+ false, false, false, false, false, false, false, false);
}
/**
@@ -902,7 +918,7 @@
"allowed_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
- false, true, false, false, false, false, false);
+ false, true, false, false, false, false, false, false);
verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
"", // activity name
BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
@@ -933,7 +949,7 @@
"allowed_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
Process.SYSTEM_UID, true, PROCESS_STATE_BOUND_TOP,
- false, true, false, false, false, false, false);
+ false, true, false, false, false, false, false, false);
verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
DEFAULT_COMPONENT_PACKAGE_NAME + "/" + DEFAULT_COMPONENT_PACKAGE_NAME,
BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT,
@@ -949,6 +965,7 @@
boolean callerIsTempAllowed,
boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges,
boolean isCallingUidDeviceOwner,
+ boolean isCallingUidAffiliatedProfileOwner,
boolean isPinnedSingleInstance,
boolean hasSystemExemptAppOp) {
// window visibility
@@ -982,6 +999,9 @@
callerIsInstrumentingWithBackgroundActivityStartPrivileges);
// callingUid is the device owner
doReturn(isCallingUidDeviceOwner).when(mAtm).isDeviceOwner(callingUid);
+ // callingUid is the affiliated profile owner
+ doReturn(isCallingUidAffiliatedProfileOwner).when(mAtm)
+ .isAffiliatedProfileOwner(callingUid);
// caller has OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop
doReturn(hasSystemExemptAppOp ? AppOpsManager.MODE_ALLOWED
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index a311726..3ca35ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -479,6 +479,8 @@
public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ doReturn(false).when(mActivity.mLetterboxUiController)
+ .isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
@@ -487,6 +489,19 @@
}
@Test
+ public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
+ throws Exception {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isCameraCompatSplitScreenAspectRatioAllowed();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true);
+ }
+
+ @Test
public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(false);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index f1e1a5a..dc5f6e9 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -1252,6 +1252,7 @@
mEventLogger.enqueue(new SessionEvent(Type.RESUME_FAILED,
modelData.getModelId(), String.valueOf(status))
.printLog(ALOGW, TAG));
+ modelData.setRequested(false);
callback.onResumeFailed(status);
} catch (RemoteException e) {
mEventLogger.enqueue(new SessionEvent(Type.RESUME_FAILED,
@@ -1300,6 +1301,7 @@
mEventLogger.enqueue(new SessionEvent(Type.PAUSE_FAILED,
modelData.getModelId(), String.valueOf(status))
.printLog(ALOGW, TAG));
+ modelData.setRequested(false);
callback.onPauseFailed(status);
} catch (RemoteException e) {
mEventLogger.enqueue(new SessionEvent(Type.PAUSE_FAILED,
@@ -1453,6 +1455,7 @@
}
synchronized void setNotLoaded() {
+ mRecognitionToken = null;
mModelState = MODEL_NOTLOADED;
}
@@ -1462,6 +1465,7 @@
synchronized void clearState() {
mModelState = MODEL_NOTLOADED;
+ mRecognitionToken = null;
mRecognitionConfig = null;
mRequested = false;
mCallback = null;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index cd29dac..3a65104 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -47,7 +47,6 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID;
-import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -743,7 +742,14 @@
void enforcePermissionsForDataDelivery() {
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
+ int result = PermissionChecker.checkPermissionForPreflight(
+ mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid,
+ mVoiceInteractorIdentity.packageName);
+ if (result != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Failed to obtain permission RECORD_AUDIO for identity "
+ + mVoiceInteractorIdentity);
+ }
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
mAppOpsManager.noteOpNoThrow(hotwordOp,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
@@ -770,7 +776,7 @@
throw new SecurityException(
TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
permission,
- SoundTriggerSessionPermissionsDecorator.toString(identity)));
+ identity));
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
deleted file mode 100644
index 0ef2f06..0000000
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionBinderProxy.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.voiceinteraction;
-
-import android.hardware.soundtrigger.SoundTrigger;
-import android.os.RemoteException;
-
-import com.android.internal.app.IHotwordRecognitionStatusCallback;
-import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-
-/**
- * A remote object that simply proxies calls to a real {@link IVoiceInteractionSoundTriggerSession}
- * implementation. This design pattern allows us to add decorators to the core implementation
- * (simply wrapping a binder object does not work).
- */
-final class SoundTriggerSessionBinderProxy extends IVoiceInteractionSoundTriggerSession.Stub {
-
- private final IVoiceInteractionSoundTriggerSession mDelegate;
-
- SoundTriggerSessionBinderProxy(IVoiceInteractionSoundTriggerSession delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public SoundTrigger.ModuleProperties getDspModuleProperties() throws RemoteException {
- return mDelegate.getDspModuleProperties();
- }
-
- @Override
- public int startRecognition(int i, String s,
- IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback,
- SoundTrigger.RecognitionConfig recognitionConfig, boolean b) throws RemoteException {
- return mDelegate.startRecognition(i, s, iHotwordRecognitionStatusCallback,
- recognitionConfig, b);
- }
-
- @Override
- public int stopRecognition(int i,
- IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback)
- throws RemoteException {
- return mDelegate.stopRecognition(i, iHotwordRecognitionStatusCallback);
- }
-
- @Override
- public int setParameter(int i, int i1, int i2) throws RemoteException {
- return mDelegate.setParameter(i, i1, i2);
- }
-
- @Override
- public int getParameter(int i, int i1) throws RemoteException {
- return mDelegate.getParameter(i, i1);
- }
-
- @Override
- public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException {
- return mDelegate.queryParameter(i, i1);
- }
-
- @Override
- public void detach() throws RemoteException {
- mDelegate.detach();
- }
-}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
deleted file mode 100644
index 0f8a945..0000000
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerSessionPermissionsDecorator.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.voiceinteraction;
-
-import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
-import static android.Manifest.permission.RECORD_AUDIO;
-
-import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.PermissionChecker;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.media.permission.Identity;
-import android.media.permission.PermissionUtil;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import com.android.internal.app.IHotwordRecognitionStatusCallback;
-import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-
-/**
- * Decorates {@link IVoiceInteractionSoundTriggerSession} with permission checks for {@link
- * android.Manifest.permission#RECORD_AUDIO} and
- * {@link android.Manifest.permission#CAPTURE_AUDIO_HOTWORD}.
- * <p>
- * Does not implement {@link #asBinder()} as it's intended to be wrapped by an
- * {@link IVoiceInteractionSoundTriggerSession.Stub} object.
- */
-final class SoundTriggerSessionPermissionsDecorator implements
- IVoiceInteractionSoundTriggerSession {
- static final String TAG = "SoundTriggerSessionPermissionsDecorator";
-
- private final IVoiceInteractionSoundTriggerSession mDelegate;
- private final Context mContext;
- private final Identity mOriginatorIdentity;
-
- SoundTriggerSessionPermissionsDecorator(IVoiceInteractionSoundTriggerSession delegate,
- Context context, Identity originatorIdentity) {
- mDelegate = delegate;
- mContext = context;
- mOriginatorIdentity = originatorIdentity;
- }
-
- @Override
- public SoundTrigger.ModuleProperties getDspModuleProperties() throws RemoteException {
- // No permission needed here (the app must have the Assistant Role to retrieve the session).
- return mDelegate.getDspModuleProperties();
- }
-
- @Override
- public int startRecognition(int i, String s,
- IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback,
- SoundTrigger.RecognitionConfig recognitionConfig, boolean b) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "startRecognition");
- }
- if (!isHoldingPermissions()) {
- return SoundTrigger.STATUS_PERMISSION_DENIED;
- }
- return mDelegate.startRecognition(i, s, iHotwordRecognitionStatusCallback,
- recognitionConfig, b);
- }
-
- @Override
- public int stopRecognition(int i,
- IHotwordRecognitionStatusCallback iHotwordRecognitionStatusCallback)
- throws RemoteException {
- // Stopping a model does not require special permissions. Having a handle to the session is
- // sufficient.
- return mDelegate.stopRecognition(i, iHotwordRecognitionStatusCallback);
- }
-
- @Override
- public int setParameter(int i, int i1, int i2) throws RemoteException {
- if (!isHoldingPermissions()) {
- return SoundTrigger.STATUS_PERMISSION_DENIED;
- }
- return mDelegate.setParameter(i, i1, i2);
- }
-
- @Override
- public int getParameter(int i, int i1) throws RemoteException {
- // No permission needed here (the app must have the Assistant Role to retrieve the session).
- return mDelegate.getParameter(i, i1);
- }
-
- @Override
- public SoundTrigger.ModelParamRange queryParameter(int i, int i1) throws RemoteException {
- // No permission needed here (the app must have the Assistant Role to retrieve the session).
- return mDelegate.queryParameter(i, i1);
- }
-
- @Override
- public IBinder asBinder() {
- throw new UnsupportedOperationException(
- "This object isn't intended to be used as a Binder.");
- }
-
- @Override
- public void detach() {
- try {
- mDelegate.detach();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
- // TODO: Share this code with SoundTriggerMiddlewarePermission.
- private boolean isHoldingPermissions() {
- try {
- enforcePermissionForPreflight(mContext, mOriginatorIdentity, RECORD_AUDIO);
- enforcePermissionForPreflight(mContext, mOriginatorIdentity, CAPTURE_AUDIO_HOTWORD);
- return true;
- } catch (SecurityException e) {
- Slog.e(TAG, e.toString());
- return false;
- }
- }
-
- /**
- * Throws a {@link SecurityException} if originator permanently doesn't have the given
- * permission.
- * Soft (temporary) denials are considered OK for preflight purposes.
- *
- * @param context A {@link Context}, used for permission checks.
- * @param identity The identity to check.
- * @param permission The identifier of the permission we want to check.
- */
- static void enforcePermissionForPreflight(@NonNull Context context,
- @NonNull Identity identity, @NonNull String permission) {
- final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
- permission);
- switch (status) {
- case PermissionChecker.PERMISSION_GRANTED:
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- return;
- case PermissionChecker.PERMISSION_HARD_DENIED:
- throw new SecurityException(
- TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
- permission, toString(identity)));
- default:
- throw new RuntimeException("Unexpected permission check result.");
- }
- }
-
- static String toString(Identity identity) {
- return "{uid=" + identity.uid
- + " pid=" + identity.pid
- + " packageName=" + identity.packageName
- + " attributionTag=" + identity.attributionTag
- + "}";
- }
-
- // Temporary hack for using the same status code as SoundTrigger, so we don't change behavior.
- // TODO: Reuse SoundTrigger code so we don't need to do this.
- private static final int TEMPORARY_PERMISSION_DENIED = 3;
-}
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index 52ff90f..b1a7d81 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -59,6 +59,9 @@
public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
/** @hide **/
+ public static final String DISPLAY_NAME_KEY = "DisplayName";
+
+ /** @hide **/
public static final String CALLER_PID_KEY = "CallerPid";
/** @hide **/