Merge "Add boolean method for knowing the buildVariableFamily result" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index bd00c03..b897c1a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -4783,6 +4783,10 @@
* Returns whether the app holds the {@link Manifest.permission.RUN_BACKUP_JOBS} permission.
*/
private boolean hasRunBackupJobsPermission(@NonNull String packageName, int packageUid) {
+ // This permission is currently hidden so always return false for now (see b/331272951)
+ return false;
+
+ /**
if (packageName == null) {
Slog.wtfStack(TAG,
"Expected a non-null package name when calling hasRunBackupJobsPermission");
@@ -4793,6 +4797,7 @@
android.Manifest.permission.RUN_BACKUP_JOBS,
PermissionChecker.PID_UNKNOWN, packageUid, packageName)
== PermissionChecker.PERMISSION_GRANTED;
+ */
}
/**
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 edd86e3..d643768 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
@@ -1213,7 +1213,8 @@
return ACTIVE_INDEX;
}
- final boolean isEligibleAsBackupJob = job.getTriggerContentUris() != null
+ final boolean isEligibleAsBackupJob = false // this exemption is being disabled for now.
+ && job.getTriggerContentUris() != null
&& job.getRequiredNetwork() != null
&& !job.hasLateConstraint()
&& mJobSchedulerInternal.hasRunBackupJobsPermission(sourcePackageName, sourceUid);
diff --git a/core/api/current.txt b/core/api/current.txt
index ee13fe4..b8c2d90 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -285,7 +285,6 @@
field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
- field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -5464,15 +5463,11 @@
method public int getDeferralPolicy();
method @Nullable public String getDeliveryGroupMatchingKey();
method public int getDeliveryGroupPolicy();
- method @FlaggedApi("android.app.bcast_event_timestamps") public long getEventTriggerTimestampMillis();
- method @FlaggedApi("android.app.bcast_event_timestamps") public long getRemoteEventTriggerTimestampMillis();
method public boolean isShareIdentityEnabled();
method @NonNull public static android.app.BroadcastOptions makeBasic();
method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
- method @FlaggedApi("android.app.bcast_event_timestamps") public void setEventTriggerTimestampMillis(long);
- method @FlaggedApi("android.app.bcast_event_timestamps") public void setRemoteEventTriggerTimestampMillis(long);
method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
method @NonNull public android.os.Bundle toBundle();
field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0
@@ -37289,7 +37284,6 @@
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
- field @FlaggedApi("android.app.app_restrictions_api") public static final String ACTION_BACKGROUND_RESTRICTIONS_SETTINGS = "android.settings.BACKGROUND_RESTRICTIONS_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
field public static final String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
@@ -37344,7 +37338,6 @@
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
- field @FlaggedApi("android.provider.backup_tasks_settings_screen") public static final String ACTION_REQUEST_RUN_BACKUP_JOBS = "android.settings.REQUEST_RUN_BACKUP_JOBS";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 702a47e..5547028 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2178,8 +2178,15 @@
package android.app.contextualsearch {
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
+ ctor public CallbackToken();
+ method public int describeContents();
+ method public void getContextualSearchState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
+ }
+
@FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
- method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ca70f03..0d46688 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -803,6 +803,14 @@
}
+package android.app.contextualsearch {
+
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
+ method @NonNull public android.os.IBinder getToken();
+ }
+
+}
+
package android.app.job {
public class JobParameters implements android.os.Parcelable {
@@ -1551,7 +1559,7 @@
public static class BiometricPrompt.Builder {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
- method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
+ method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowedSensorIds(@NonNull java.util.List<java.lang.Integer>);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0c54351..7725561 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5672,9 +5672,11 @@
}
/**
- * Return if a given profile is in the foreground.
+ * Returns whether the given user, or its parent (if the user is a profile), is in the
+ * foreground.
* @param userHandle UserHandle to check
- * @return Returns the boolean result.
+ * @return whether the user is the foreground user or, if it is a profile, whether its parent
+ * is the foreground user
* @hide
*/
@RequiresPermission(anyOf = {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 60d622d..4db3727 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -18,8 +18,6 @@
import static android.app.ActivityOptions.BackgroundActivityStartMode;
-import android.annotation.CurrentTimeMillisLong;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -69,8 +67,6 @@
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
private @DeferralPolicy int mDeferralPolicy;
- private @CurrentTimeMillisLong long mEventTriggerTimestampMillis;
- private @CurrentTimeMillisLong long mRemoteEventTriggerTimestampMillis;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -196,18 +192,6 @@
"android:broadcast.idForResponseEvent";
/**
- * Corresponds to {@link #setEventTriggerTimestampMillis(long)}.
- */
- private static final String KEY_EVENT_TRIGGER_TIMESTAMP =
- "android:broadcast.eventTriggerTimestamp";
-
- /**
- * Corresponds to {@link #setRemoteEventTriggerTimestampMillis(long)}.
- */
- private static final String KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP =
- "android:broadcast.remoteEventTriggerTimestamp";
-
- /**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -359,8 +343,6 @@
mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
- mEventTriggerTimestampMillis = opts.getLong(KEY_EVENT_TRIGGER_TIMESTAMP);
- mRemoteEventTriggerTimestampMillis = opts.getLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
@@ -807,60 +789,6 @@
}
/**
- * Set the timestamp for the event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * <p> For instance, if this broadcast is for a push message, then this timestamp
- * could correspond to when the device received the message.
- *
- * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that
- * correspond to the event that triggered this broadcast.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public void setEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) {
- mEventTriggerTimestampMillis = timestampMillis;
- }
-
- /**
- * Return the timestamp for the event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously
- * set using {@link #setEventTriggerTimestampMillis(long)}.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public @CurrentTimeMillisLong long getEventTriggerTimestampMillis() {
- return mEventTriggerTimestampMillis;
- }
-
- /**
- * Set the timestamp for the remote event, if any, that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * <p> For instance, if this broadcast is for a push message, then this timestamp
- * could correspond to when the message originated remotely.
- *
- * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that
- * correspond to the remote event that triggered this broadcast.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public void setRemoteEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) {
- mRemoteEventTriggerTimestampMillis = timestampMillis;
- }
-
- /**
- * Return the timestamp for the remote event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously
- * set using {@link #setRemoteEventTriggerTimestampMillis(long)}}.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public @CurrentTimeMillisLong long getRemoteEventTriggerTimestampMillis() {
- return mRemoteEventTriggerTimestampMillis;
- }
-
- /**
* Sets deferral policy for this broadcast that specifies how this broadcast
* can be deferred for delivery at some future point.
*/
@@ -1195,12 +1123,6 @@
if (mIdForResponseEvent != 0) {
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
}
- if (mEventTriggerTimestampMillis > 0) {
- b.putLong(KEY_EVENT_TRIGGER_TIMESTAMP, mEventTriggerTimestampMillis);
- }
- if (mRemoteEventTriggerTimestampMillis > 0) {
- b.putLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP, mRemoteEventTriggerTimestampMillis);
- }
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java
new file mode 100644
index 0000000..0bbd1e5
--- /dev/null
+++ b/core/java/android/app/contextualsearch/CallbackToken.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contextualsearch;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.contextualsearch.flags.Flags;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Used to share a single use token with the contextual search handling activity via the launch
+ * extras bundle.
+ * The caller can then use this token to get {@link ContextualSearchState} by calling
+ * {@link #getContextualSearchState}.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+@SystemApi
+public final class CallbackToken implements Parcelable {
+ private static final boolean DEBUG = true;
+ private static final String TAG = CallbackToken.class.getSimpleName();
+ private final IBinder mToken;
+
+ private boolean mTokenUsed = false;
+
+ public CallbackToken() {
+ mToken = new Binder();
+ }
+
+ private CallbackToken(Parcel in) {
+ mToken = in.readStrongBinder();
+ }
+
+ /**
+ * Returns the {@link ContextualSearchState} to the handler via the provided callback. The
+ * method can only be invoked to provide the {@link OutcomeReceiver} once and all subsequent
+ * invocations of this method will result in {@link OutcomeReceiver#onError} being called with
+ * an {@link IllegalAccessException}.
+ *
+ * @param executor The executor which will be used to invoke the callback.
+ * @param callback The callback which will be used to return {@link ContextualSearchState}
+ * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
+ * used to return errors via {@link OutcomeReceiver#onError}.
+ */
+ public void getContextualSearchState(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken);
+ if (mTokenUsed) {
+ callback.onError(new IllegalAccessException("Token already used."));
+ }
+ mTokenUsed = true;
+ try {
+ // Get the service from the system server.
+ IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+ IContextualSearchManager service = IContextualSearchManager.Stub.asInterface(b);
+ final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
+ // If the service is not null, hand over the call to the service.
+ if (service != null) {
+ service.getContextualSearchState(mToken, wrapper);
+ } else {
+ Log.w(TAG, "Failed to getContextualSearchState. Service null.");
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to call getContextualSearchState", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the token necessary for validating the caller of {@link #getContextualSearchState}.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ }
+
+ @NonNull
+ public static final Creator<CallbackToken> CREATOR = new Creator<>() {
+ @Override
+ public CallbackToken createFromParcel(Parcel in) {
+ return new CallbackToken(in);
+ }
+
+ @Override
+ public CallbackToken[] newArray(int size) {
+ return new CallbackToken[size];
+ }
+ };
+
+ private static class CallbackWrapper extends IContextualSearchCallback.Stub {
+ private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ContextualSearchState state) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.d(TAG, "onResult state:" + state);
+ mExecutor.execute(() -> mCallback.onResult(state));
+ });
+ }
+
+ @Override
+ public void onError(ParcelableException error) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.w(TAG, "onError", error);
+ mExecutor.execute(() -> mCallback.onError(error));
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 693de21..a894a0e 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -18,37 +18,26 @@
import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
-import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.contextualsearch.flags.Flags;
import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
-import android.os.OutcomeReceiver;
-import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.Executor;
/**
* {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
* configured Android devices.
* <p>
- * This class lets
- * <ul>
- * <li> a caller start contextual search by calling {@link #startContextualSearch} method.
- * <li> a handler request {@link ContextualSearchState} by calling the
- * {@link #getContextualSearchState} method.
- * </ul>
+ * This class lets a caller start contextual search by calling {@link #startContextualSearch}
+ * method.
*
* @hide
*/
@@ -92,7 +81,7 @@
/**
* Key to get the binder token from the extras of the activity launched by contextual search.
- * This token is needed to invoke {@link #getContextualSearchState} method.
+ * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method.
* Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
*/
public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
@@ -174,57 +163,4 @@
e.rethrowFromSystemServer();
}
}
-
- /**
- * Returns the {@link ContextualSearchState} to the handler via the provided callback.
- *
- * @param token The caller is expected to get the token from the launch extras of the handling
- * activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key.
- * <br>
- * <b>Note</b> This token is for one time use only. Subsequent uses will invoke
- * callback's {@link OutcomeReceiver#onError}.
- * @param executor The executor which will be used to invoke the callback.
- * @param callback The callback which will be used to return {@link ContextualSearchState}
- * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
- * used to return errors via {@link OutcomeReceiver#onError}.
- */
- public void getContextualSearchState(@NonNull IBinder token,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
- if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token);
- try {
- final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
- mService.getContextualSearchState(token, wrapper);
- } catch (RemoteException e) {
- if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e);
- e.rethrowFromSystemServer();
- }
- }
-
- private static class CallbackWrapper extends IContextualSearchCallback.Stub {
- private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
- private final Executor mExecutor;
-
- CallbackWrapper(@NonNull Executor callbackExecutor,
- @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
- mCallback = callback;
- mExecutor = callbackExecutor;
- }
-
- @Override
- public void onResult(ContextualSearchState state) {
- Binder.withCleanCallingIdentity(() -> {
- if (DEBUG) Log.d(TAG, "onResult state:" + state);
- mExecutor.execute(() -> mCallback.onResult(state));
- });
- }
-
- @Override
- public void onError(ParcelableException error) {
- Binder.withCleanCallingIdentity(() -> {
- if (DEBUG) Log.w(TAG, "onError", error);
- mExecutor.execute(() -> mCallback.onError(error));
- });
- }
- }
}
diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java
index 5c04bc89..f01ae55 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchState.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.java
@@ -32,6 +32,10 @@
* {@link ContextualSearchState} contains additional data a contextual search handler can request
* via {@link ContextualSearchManager#getContextualSearchState} method.
*
+ * It provides the caller of {@link ContextualSearchManager#getContextualSearchState} with an
+ * {@link AssistStructure}, {@link AssistContent} and a {@link Bundle} extras containing any
+ * relevant data added by th system server. When invoked via the Launcher, this bundle is empty.
+ *
* @hide
*/
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
@@ -41,6 +45,12 @@
private final @Nullable AssistStructure mStructure;
private final @Nullable AssistContent mContent;
+ /**
+ * {@link ContextualSearchState} contains non-essential data which can be requested by the
+ * Contextual Search activity.
+ * The activity can request an instance of {@link ContextualSearchState} by calling
+ * {@link CallbackToken#getContextualSearchState} and passing a valid token as an argument.
+ */
public ContextualSearchState(@Nullable AssistStructure structure,
@Nullable AssistContent content, @NonNull Bundle extras) {
mStructure = structure;
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
index 1735a71..9b0b8b7 100644
--- a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -1,6 +1,5 @@
package android.app.contextualsearch;
-
import android.app.contextualsearch.IContextualSearchCallback;
/**
* @hide
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5e00b7a..2c26389 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1086,7 +1086,7 @@
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.registerDevicePresenceListenerService(deviceAddress,
+ mService.legacyStartObservingDevicePresence(deviceAddress,
mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1128,7 +1128,7 @@
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.unregisterDevicePresenceListenerService(deviceAddress,
+ mService.legacyStopObservingDevicePresence(deviceAddress,
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1328,7 +1328,7 @@
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceAppeared(int associationId) {
try {
- mService.notifyDeviceAppeared(associationId);
+ mService.notifySelfManagedDeviceAppeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1350,7 +1350,7 @@
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceDisappeared(int associationId) {
try {
- mService.notifyDeviceDisappeared(associationId);
+ mService.notifySelfManagedDeviceDisappeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 57d59e5..1b00f90e 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -59,12 +59,16 @@
int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
@@ -93,9 +97,11 @@
@EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
- void notifyDeviceAppeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceAppeared(int associationId);
- void notifyDeviceDisappeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceDisappeared(int associationId);
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
@@ -135,10 +141,4 @@
byte[] getBackupPayload(int userId);
void applyRestoredPayload(in byte[] payload, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d9614d1..90b7869 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -24,7 +24,7 @@
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
-import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import android.annotation.CallbackExecutor;
import android.annotation.DrawableRes;
@@ -529,7 +529,7 @@
/**
* Remove {@link Builder#setAllowBackgroundAuthentication(boolean)} once
- * FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE is enabled.
+ * FLAG_ALLOW_PRIVATE_PROFILE is enabled.
*
* @param allow If true, allows authentication when the calling package is not in the
* foreground. This is set to false by default.
@@ -538,7 +538,7 @@
* @return This builder
* @hide
*/
- @FlaggedApi(FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE)
+ @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
@TestApi
@NonNull
@RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 58aafbc..ec67212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -498,6 +498,7 @@
// Overrides the policy for adjusting screen brightness and state while dozing.
public int dozeScreenState;
public float dozeScreenBrightness;
+ public int dozeScreenStateReason;
public DisplayPowerRequest() {
policy = POLICY_BRIGHT;
@@ -508,6 +509,7 @@
blockScreenOn = false;
dozeScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
dozeScreenState = Display.STATE_UNKNOWN;
+ dozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
}
public DisplayPowerRequest(DisplayPowerRequest other) {
@@ -529,6 +531,7 @@
boostScreenBrightness = other.boostScreenBrightness;
dozeScreenBrightness = other.dozeScreenBrightness;
dozeScreenState = other.dozeScreenState;
+ dozeScreenStateReason = other.dozeScreenStateReason;
}
@Override
@@ -551,7 +554,8 @@
&& lowPowerMode == other.lowPowerMode
&& boostScreenBrightness == other.boostScreenBrightness
&& floatEquals(dozeScreenBrightness, other.dozeScreenBrightness)
- && dozeScreenState == other.dozeScreenState;
+ && dozeScreenState == other.dozeScreenState
+ && dozeScreenStateReason == other.dozeScreenStateReason;
}
private boolean floatEquals(float f1, float f2) {
@@ -575,7 +579,9 @@
+ ", lowPowerMode=" + lowPowerMode
+ ", boostScreenBrightness=" + boostScreenBrightness
+ ", dozeScreenBrightness=" + dozeScreenBrightness
- + ", dozeScreenState=" + Display.stateToString(dozeScreenState);
+ + ", dozeScreenState=" + Display.stateToString(dozeScreenState)
+ + ", dozeScreenStateReason="
+ + Display.stateReasonToString(dozeScreenStateReason);
}
public static String policyToString(int policy) {
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index fec67b9..df353e5 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -136,11 +136,12 @@
*
* @param screenState The overridden screen state, or {@link Display#STATE_UNKNOWN}
* to disable the override.
+ * @param reason The reason for overriding the screen state.
* @param screenBrightness The overridden screen brightness, or
* {@link PowerManager#BRIGHTNESS_DEFAULT} to disable the override.
*/
public abstract void setDozeOverrideFromDreamManager(
- int screenState, int screenBrightness);
+ int screenState, @Display.StateReason int reason, int screenBrightness);
/**
* Used by sidekick manager to tell the power manager if it shouldn't change the display state
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c3b646a..c74d50a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -696,6 +696,8 @@
* Output: When a package data uri is passed as input, the activity result is set to
* {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
* the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+ *
+ * @hide
*/
@FlaggedApi(Flags.FLAG_BACKUP_TASKS_SETTINGS_SCREEN)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1569,23 +1571,6 @@
"android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
/**
- * Activity Action: Show screen for controlling any background restrictions imposed on
- * an app. If the system returns true for
- * {@link android.app.ActivityManager#isBackgroundRestricted()}, and the app is not able to
- * satisfy user requests due to being restricted in the background, then this intent can be
- * used to request the user to unrestrict the app.
- * <p>
- * Input: The Intent's data URI must specify the application package name
- * to be shown, with the "package" scheme, such as "package:com.my.app".
- * <p>
- * Output: Nothing.
- */
- @FlaggedApi(android.app.Flags.FLAG_APP_RESTRICTIONS_API)
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_BACKGROUND_RESTRICTIONS_SETTINGS =
- "android.settings.BACKGROUND_RESTRICTIONS_SETTINGS";
-
- /**
* Activity Action: Open the advanced power usage details page of an associated app.
* <p>
* Input: Intent's data URI set with an application name, using the
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 4060b24..6dcbc8e 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -263,7 +263,9 @@
private boolean mCanDoze;
private boolean mDozing;
private boolean mWindowless;
+ private boolean mPreviewMode;
private int mDozeScreenState = Display.STATE_UNKNOWN;
+ private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
private boolean mDebug = false;
@@ -647,12 +649,13 @@
}
/**
- * Marks this dream as keeping the screen bright while dreaming.
+ * Marks this dream as keeping the screen bright while dreaming. In preview mode, the screen
+ * is always allowed to dim and overrides the value specified here.
*
* @param screenBright True to keep the screen bright while dreaming.
*/
public void setScreenBright(boolean screenBright) {
- if (mScreenBright != screenBright) {
+ if (mScreenBright != screenBright && !mPreviewMode) {
mScreenBright = screenBright;
int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
applyWindowFlags(mScreenBright ? flag : 0, flag);
@@ -661,7 +664,7 @@
/**
* Returns whether this dream keeps the screen bright while dreaming.
- * Defaults to false, allowing the screen to dim if necessary.
+ * Defaults to true, preventing the screen from dimming.
*
* @see #setScreenBright(boolean)
*/
@@ -748,7 +751,9 @@
if (mDozing) {
try {
- mDreamManager.startDozing(mDreamToken, mDozeScreenState, mDozeScreenBrightness);
+ mDreamManager.startDozing(
+ mDreamToken, mDozeScreenState, mDozeScreenStateReason,
+ mDozeScreenBrightness);
} catch (RemoteException ex) {
// system server died
}
@@ -808,6 +813,19 @@
}
/**
+ * Same as {@link #setDozeScreenState(int, int)}, but with no screen state reason specified.
+ *
+ * <p>Use {@link #setDozeScreenState(int, int)} whenever possible to allow properly accounting
+ * for the screen state reason.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setDozeScreenState(int state) {
+ setDozeScreenState(state, Display.STATE_REASON_UNKNOWN);
+ }
+
+ /**
* Sets the screen state to use while dozing.
* <p>
* The value of this property determines the power state of the primary display
@@ -841,13 +859,15 @@
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
* {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
* for the default behavior.
+ * @param reason the reason for setting the specified screen state.
*
* @hide For use by system UI components only.
*/
@UnsupportedAppUsage
- public void setDozeScreenState(int state) {
+ public void setDozeScreenState(int state, @Display.StateReason int reason) {
if (mDozeScreenState != state) {
mDozeScreenState = state;
+ mDozeScreenStateReason = reason;
updateDoze();
}
}
@@ -1280,6 +1300,11 @@
mDreamToken = dreamToken;
mCanDoze = canDoze;
+ mPreviewMode = isPreviewMode;
+ if (mPreviewMode) {
+ // Allow screen to dim when in preview mode.
+ mScreenBright = false;
+ }
// This is not a security check to prevent malicious dreams but a guard rail to stop
// third-party dreams from being windowless and not working well as a result.
if (mWindowless && !mCanDoze && !isCallerSystemUi()) {
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index c489c58..e45384f 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -40,7 +40,7 @@
boolean isDreamingOrInPreview();
boolean canStartDreaming(boolean isScreenOn);
void finishSelf(in IBinder token, boolean immediate);
- void startDozing(in IBinder token, int screenState, int screenBrightness);
+ void startDozing(in IBinder token, int screenState, int reason, int screenBrightness);
void stopDozing(in IBinder token);
void forceAmbientDisplayEnabled(boolean enabled);
ComponentName[] getDreamComponentsForUser(int userId);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1f2b4fa..4475418 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -503,6 +503,79 @@
*/
public static final int STATE_ON_SUSPEND = ViewProtoEnums.DISPLAY_STATE_ON_SUSPEND; // 6
+ /**
+ * The cause of the display state change is unknown.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_UNKNOWN = ViewProtoEnums.DISPLAY_STATE_REASON_UNKNOWN;
+
+ /**
+ * The default power and display policy caused the display state.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_DEFAULT_POLICY =
+ ViewProtoEnums.DISPLAY_STATE_REASON_DEFAULT_POLICY;
+
+ /**
+ * The display state was changed due to acquiring a draw wake lock.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_DRAW_WAKE_LOCK =
+ ViewProtoEnums.DISPLAY_STATE_REASON_DRAW_WAKE_LOCK;
+
+ /**
+ * The display state was changed due to display offloading.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_OFFLOAD = ViewProtoEnums.DISPLAY_STATE_REASON_OFFLOAD;
+
+ /**
+ * The display state was changed due to a tilt event.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_TILT = ViewProtoEnums.DISPLAY_STATE_REASON_TILT;
+
+ /**
+ * The display state was changed due to the dream manager.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_DREAM_MANAGER =
+ ViewProtoEnums.DISPLAY_STATE_REASON_DREAM_MANAGER;
+
+ /**
+ * The display state was changed due to a {@link KeyEvent}.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_KEY = ViewProtoEnums.DISPLAY_STATE_REASON_KEY;
+
+ /**
+ * The display state was changed due to a {@link MotionEvent}.
+ *
+ * @hide
+ */
+ public static final int STATE_REASON_MOTION = ViewProtoEnums.DISPLAY_STATE_REASON_MOTION;
+
+ /** @hide */
+ @IntDef(prefix = {"STATE_REASON_"}, value = {
+ STATE_REASON_UNKNOWN,
+ STATE_REASON_DEFAULT_POLICY,
+ STATE_REASON_DRAW_WAKE_LOCK,
+ STATE_REASON_OFFLOAD,
+ STATE_REASON_TILT,
+ STATE_REASON_DREAM_MANAGER,
+ STATE_REASON_KEY,
+ STATE_REASON_MOTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StateReason {}
+
/* The color mode constants defined below must be kept in sync with the ones in
* system/core/include/system/graphics-base.h */
@@ -1996,6 +2069,30 @@
}
}
+ /** @hide */
+ public static String stateReasonToString(@StateReason int reason) {
+ switch (reason) {
+ case STATE_REASON_UNKNOWN:
+ return "UNKNOWN";
+ case STATE_REASON_DEFAULT_POLICY:
+ return "DEFAULT_POLICY";
+ case STATE_REASON_DRAW_WAKE_LOCK:
+ return "DRAW_WAKE_LOCK";
+ case STATE_REASON_OFFLOAD:
+ return "OFFLOAD";
+ case STATE_REASON_TILT:
+ return "TILT";
+ case STATE_REASON_DREAM_MANAGER:
+ return "DREAM_MANAGER";
+ case STATE_REASON_KEY:
+ return "KEY";
+ case STATE_REASON_MOTION:
+ return "MOTION";
+ default:
+ return Integer.toString(reason);
+ }
+ }
+
/**
* Returns true if display updates may be suspended while in the specified
* display power state. In SUSPEND states, updates are absolutely forbidden.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 85d3688..eb9be18 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1066,11 +1066,6 @@
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
- // Used to allow developers to opt out Toolkit dVRR feature.
- // This feature allows device to adjust refresh rate
- // as needed and can be useful for power saving.
- // Should not enable the dVRR feature if the value is false.
- private boolean mIsFrameRatePowerSavingsBalanced = true;
// Used to check if there is a conflict between different frame rate voting.
// Take 24 and 30 as an example, 24 is not a divisor of 30.
// We consider there is a conflict.
@@ -12777,7 +12772,10 @@
*/
@VisibleForTesting
public boolean isFrameRatePowerSavingsBalanced() {
- return mIsFrameRatePowerSavingsBalanced;
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return mWindowAttributes.isFrameRatePowerSavingsBalanced();
+ }
+ return true;
}
/**
@@ -12789,19 +12787,9 @@
return mIsFrameRateConflicted;
}
- /**
- * Set the value of mIsFrameRatePowerSavingsBalanced
- * Can be used to checked if toolkit dVRR feature is enabled.
- */
- public void setFrameRatePowerSavingsBalanced(boolean enabled) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- mIsFrameRatePowerSavingsBalanced = enabled;
- }
- }
-
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- // return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced;
+ // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
return false;
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 51229a7..1ebced5 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -334,7 +334,12 @@
private boolean mOverlayWithDecorCaptionEnabled = true;
private boolean mCloseOnSwipeEnabled = false;
- private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ /**
+ * To check if toolkitSetFrameRateReadOnly flag is enabled
+ *
+ * @hide
+ */
+ protected static boolean sToolkitSetFrameRateReadOnlyFlagValue =
android.view.flags.Flags.toolkitSetFrameRateReadOnly();
// The current window attributes.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e3caf70..f643bd4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -345,7 +345,7 @@
*
* @hide
*/
- private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 20_000;
/**
* Application that hosts the remote views.
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index d4a5bbd..07d6acb 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -124,6 +124,41 @@
* @return True if the same id always refers to the same object.
*/
public boolean hasStableIds();
+
+ /**
+ * @hide
+ */
+ default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
+ RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+ .Builder().build();
+ Parcel capSizeTestParcel = Parcel.obtain();
+ capSizeTestParcel.allowSquashing();
+
+ try {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ onDataSetChanged();
+
+ itemsBuilder.setHasStableIds(hasStableIds());
+ final int numOfEntries = getCount();
+
+ for (int i = 0; i < numOfEntries; i++) {
+ final long currentItemId = getItemId(i);
+ final RemoteViews currentView = getViewAt(i);
+ currentView.writeToParcel(capSizeTestParcel, 0);
+ if (capSizeTestParcel.dataSize() > capSize) {
+ break;
+ }
+ itemsBuilder.addItem(currentItemId, currentView);
+ }
+
+ items = itemsBuilder.build();
+ } finally {
+ // Recycle the parcel
+ capSizeTestParcel.recycle();
+ }
+ return items;
+ }
}
/**
@@ -232,33 +267,11 @@
public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
.Builder().build();
- Parcel capSizeTestParcel = Parcel.obtain();
-
try {
- RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
- new RemoteViews.RemoteCollectionItems.Builder();
- mFactory.onDataSetChanged();
-
- itemsBuilder.setHasStableIds(mFactory.hasStableIds());
- final int numOfEntries = mFactory.getCount();
-
- for (int i = 0; i < numOfEntries; i++) {
- final long currentItemId = mFactory.getItemId(i);
- final RemoteViews currentView = mFactory.getViewAt(i);
- currentView.writeToParcel(capSizeTestParcel, 0);
- if (capSizeTestParcel.dataSize() > capSize) {
- break;
- }
- itemsBuilder.addItem(currentItemId, currentView);
- }
-
- items = itemsBuilder.build();
+ items = mFactory.getRemoteCollectionItems(capSize);
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
- } finally {
- // Recycle the parcel
- capSizeTestParcel.recycle();
}
return items;
}
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index 590e88b..2d1cbb5 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -8,7 +8,7 @@
}
flag {
- name: "magnification_always_draw_fullscreen_border"
+ name: "always_draw_magnification_fullscreen_border"
namespace: "accessibility"
description: "Always draw fullscreen orange border in fullscreen magnification"
bug: "291891390"
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 9868ceb..02cb53e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -365,8 +365,6 @@
private boolean mUseDecorContext = false;
- private boolean mIsFrameRatePowerSavingsBalanced = true;
-
/** @see ViewRootImpl#mActivityConfigCallback */
private ActivityConfigCallback mActivityConfigCallback;
@@ -2216,7 +2214,6 @@
void onViewRootImplSet(ViewRootImpl viewRoot) {
viewRoot.setActivityConfigCallback(mActivityConfigCallback);
viewRoot.getOnBackInvokedDispatcher().updateContext(getContext());
- viewRoot.setFrameRatePowerSavingsBalanced(mIsFrameRatePowerSavingsBalanced);
mProxyOnBackInvokedDispatcher.setActualDispatcher(viewRoot.getOnBackInvokedDispatcher());
applyDecorFitsSystemWindows();
}
@@ -2566,8 +2563,11 @@
requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
if (a.hasValue(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced)) {
- mIsFrameRatePowerSavingsBalanced =
- a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced, true);
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ setFrameRatePowerSavingsBalanced(
+ a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced,
+ true));
+ }
}
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 1a86363..b801a69 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -85,24 +85,9 @@
ScopedLocalRef<jobject> android_view_MotionEvent_obtainAsCopy(JNIEnv* env,
const MotionEvent& event) {
- ScopedLocalRef<jobject> eventObj(env,
- env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
- gMotionEventClassInfo.obtain));
- if (env->ExceptionCheck() || !eventObj.get()) {
- ALOGE("An exception occurred while obtaining a motion event.");
- LOGE_EX(env);
- env->ExceptionClear();
- return ScopedLocalRef<jobject>(env);
- }
-
- MotionEvent* destEvent = android_view_MotionEvent_getNativePtr(env, eventObj.get());
- if (!destEvent) {
- destEvent = new MotionEvent();
- android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
- }
-
+ std::unique_ptr<MotionEvent> destEvent = std::make_unique<MotionEvent>();
destEvent->copyFrom(&event, true);
- return eventObj;
+ return android_view_MotionEvent_obtainFromNative(env, std::move(destEvent));
}
ScopedLocalRef<jobject> android_view_MotionEvent_obtainFromNative(
@@ -117,6 +102,8 @@
LOGE_EX(env);
LOG_ALWAYS_FATAL("An exception occurred while obtaining a Java motion event.");
}
+ MotionEvent* oldEvent = android_view_MotionEvent_getNativePtr(env, eventObj.get());
+ delete oldEvent;
android_view_MotionEvent_setNativePtr(env, eventObj, event.release());
return eventObj;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6431db9..cfe6f73 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7986,6 +7986,7 @@
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
<p>Protection level: signature|privileged|appop
+ @hide
-->
<permission android:name="android.permission.RUN_BACKUP_JOBS"
android:protectionLevel="signature|privileged|appop"/>
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index fa364e0..d95a039 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1067,18 +1067,32 @@
/**
* Test the IsFrameRatePowerSavingsBalanced values are properly set
*/
- @UiThreadTest
@Test
@Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
- ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
- sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true);
- viewRootImpl.setFrameRatePowerSavingsBalanced(false);
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), false);
- viewRootImpl.setFrameRatePowerSavingsBalanced(true);
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true);
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRoot = view.getViewRootImpl();
+ final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+ assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true);
+ assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
+ attrs.isFrameRatePowerSavingsBalanced());
+
+ sInstrumentation.runOnMainSync(() -> {
+ attrs.setFrameRatePowerSavingsBalanced(false);
+ viewRoot.setLayoutParams(attrs, false);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes;
+ assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), false);
+ assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
+ newAttrs.isFrameRatePowerSavingsBalanced());
+ });
}
/**
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index b9841ff..82251b8 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -182,7 +182,8 @@
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
+ wmlp.setFrameRatePowerSavingsBalanced(
+ mPhoneWindow.getAttributes().isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.addView(decorView, wmlp);
@@ -203,7 +204,8 @@
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
+ wmlp.setFrameRatePowerSavingsBalanced(
+ mPhoneWindow.getAttributes().isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.addView(decorView, wmlp);
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce79..d6d74e8 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -122,6 +122,9 @@
"-Wunused",
"-Wunreachable-code",
],
+
+ // TODO(b/330503129) Workaround build breakage.
+ lto_O0: true,
}
cc_library_shared {
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index cf5059c..7caa9e4 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -44,4 +44,7 @@
"-Wunreachable-code",
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
+
+ // TODO(b/330503129) Workaround LTO build breakage.
+ lto_O0: true,
}
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 654e8cc..c6861bf 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -171,7 +171,7 @@
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
- mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+ mTimestamp = frame.getLong(KEY_POLLING_LOOP_TIMESTAMP);
mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT)
&& frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 3fb91522..7bb08d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -17,6 +17,8 @@
package com.android.credentialmanager.common.ui
import android.content.Context
+import android.util.Log
+import com.android.credentialmanager.common.Constants
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -29,7 +31,8 @@
private const val SET_ADJUST_VIEW_BOUNDS_METHOD_NAME = "setAdjustViewBounds"
private const val SET_MAX_HEIGHT_METHOD_NAME = "setMaxHeight"
private const val SET_BACKGROUND_RESOURCE_METHOD_NAME = "setBackgroundResource"
- private const val BULLET_POINT = "\u2022"
+ private const val SEPARATOR = " " + "\u2022" + " "
+
// TODO(jbabs): RemoteViews#setViewPadding renders this as 8dp on the display. Debug why.
private const val END_ITEMS_PADDING = 28
@@ -43,20 +46,14 @@
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_presentation_layout
val remoteViews = RemoteViews(context.packageName, layoutId)
- if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
- return remoteViews
- }
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
- val secondaryText =
- if (credentialEntryInfo.displayName != null
- && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
- (credentialEntryInfo.userName + " " + BULLET_POINT + " "
- + credentialEntryInfo.credentialTypeDisplayName
- + " " + BULLET_POINT + " " + credentialEntryInfo.providerDisplayName)
- else (credentialEntryInfo.credentialTypeDisplayName + " " + BULLET_POINT + " "
- + credentialEntryInfo.providerDisplayName)
- remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ val secondaryText = getSecondaryText(credentialEntryInfo)
+ if (secondaryText.isNullOrBlank()) {
+ Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null")
+ } else {
+ remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ }
remoteViews.setImageViewIcon(android.R.id.icon1, icon);
remoteViews.setBoolean(
android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
@@ -88,6 +85,26 @@
return remoteViews
}
+ /**
+ * Computes the secondary text for dropdown presentation based on available fields.
+ *
+ * <p> Format for secondary text is [username] . [credentialType] . [providerDisplayName]
+ * If display name and username are the same, we do not display username
+ * If credential type is missing as in the case with SiwG, we just display
+ * providerDisplayName. Both credential type and provider display name should not be empty.
+ */
+ private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? {
+ return listOf(if (credentialEntryInfo.displayName != null
+ && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
+ (credentialEntryInfo.userName) else null,
+ credentialEntryInfo.credentialTypeDisplayName,
+ credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() }
+ .let { itemsToDisplay ->
+ if (itemsToDisplay.isEmpty()) null
+ else itemsToDisplay.joinToString(separator = SEPARATOR)
+ }
+ }
+
fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_bottom_sheet
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index b151a53..4e4c22f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -600,14 +600,25 @@
}
Setting oldState = mSettings.get(name);
+ String previousOwningPackage = (oldState != null) ? oldState.packageName : null;
+ // If the old state doesn't exist, no need to handle the owning package change
+ final boolean owningPackageChanged = previousOwningPackage != null
+ && !previousOwningPackage.equals(packageName);
+
String oldValue = (oldState != null) ? oldState.value : null;
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
String newDefaultValue = makeDefault ? value : oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(packageName,
- oldValue == null ? name.length() : 0 /* deltaKeySize */,
- oldValue, value, oldDefaultValue, newDefaultValue);
- checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+ int newSizeForCurrentPackage = getNewMemoryUsagePerPackageLocked(packageName,
+ /* deltaKeyLength= */ (oldState == null || owningPackageChanged) ? name.length() : 0,
+ /* oldValue= */ owningPackageChanged ? null : oldValue,
+ /* newValue= */ value,
+ /* oldDefaultValue= */ owningPackageChanged ? null : oldDefaultValue,
+ /* newDefaultValue = */ newDefaultValue);
+ // Only check the memory usage for the current package. Even if the owning package
+ // has changed, the previous owning package will only have a reduced memory usage, so
+ // there is no need to check its memory usage.
+ checkNewMemoryUsagePerPackageLocked(packageName, newSizeForCurrentPackage);
Setting newState;
@@ -629,7 +640,17 @@
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, newSize);
+ updateMemoryUsagePerPackageLocked(packageName, newSizeForCurrentPackage);
+
+ if (owningPackageChanged) {
+ int newSizeForPreviousPackage = getNewMemoryUsagePerPackageLocked(previousOwningPackage,
+ /* deltaKeyLength= */ -name.length(),
+ /* oldValue= */ oldValue,
+ /* newValue= */ null,
+ /* oldDefaultValue= */ oldDefaultValue,
+ /* newDefaultValue = */ null);
+ updateMemoryUsagePerPackageLocked(previousOwningPackage, newSizeForPreviousPackage);
+ }
scheduleWriteIfNeededLocked();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index e0e31d7..5db97c6 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -585,9 +585,9 @@
* Character.BYTES;
assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
- // Test system package
+ // Let system package take over testKey1 which is no longer subject to memory usage counting
settingsState.insertSettingLocked(testKey1, testValue1, null, true, SYSTEM_PACKAGE);
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
assertEquals(0, settingsState.getMemoryUsage(SYSTEM_PACKAGE));
@@ -599,7 +599,7 @@
} catch (IllegalStateException ex) {
assertTrue(ex.getMessage().contains("You are adding too many system settings"));
}
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
// Test invalid key
try {
@@ -609,7 +609,7 @@
} catch (IllegalStateException ex) {
assertTrue(ex.getMessage().contains("You are adding too many system settings"));
}
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
}
@Test
@@ -902,4 +902,49 @@
assertEquals("false", s.getValue());
}
}
+
+ @Test
+ public void testMemoryUsagePerPackage_SameSettingUsedByDifferentPackages() {
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ final String testKey1 = SETTING_NAME;
+ final String testKey2 = SETTING_NAME + "_2";
+ final String testValue1 = Strings.repeat("A", 100);
+ final String testValue2 = Strings.repeat("A", 50);
+ final String package1 = "p1";
+ final String package2 = "p2";
+
+ settingsState.insertSettingLocked(testKey1, testValue1, null, false, package1);
+ settingsState.insertSettingLocked(testKey2, testValue1, null, true, package2);
+ // Package1's usage should be remain the same Package2 owns a different setting
+ int expectedMemUsageForPackage1 = (testKey1.length() + testValue1.length())
+ * Character.BYTES;
+ int expectedMemUsageForPackage2 = (testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+
+ settingsState.insertSettingLocked(testKey1, testValue2, null, false, package2);
+ // Package1's usage should be cleared because the setting is taken over by another package
+ expectedMemUsageForPackage1 = 0;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ // Package2 now owns two settings
+ expectedMemUsageForPackage2 = (testKey1.length() + testValue2.length()
+ + testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */)
+ * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, package1);
+ // Package1 now owns setting1
+ expectedMemUsageForPackage1 = (testKey1.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ // Package2 now only own setting2
+ expectedMemUsageForPackage2 = (testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e1608c4..503779c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -110,6 +110,8 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -154,6 +156,7 @@
derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
var isButtonToEditWidgetsShowing by remember { mutableStateOf(false) }
+ val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false)
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -178,7 +181,7 @@
viewModel.setSelectedKey(key)
}
}
- .thenIf(!viewModel.isEditMode) {
+ .thenIf(!viewModel.isEditMode && !isEmptyState) {
Modifier.pointerInput(
gridState,
contentOffset,
@@ -219,26 +222,33 @@
.motionEventSpy { onMotionEvent(viewModel) }
},
) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
+ if (!viewModel.isEditMode && isEmptyState) {
+ EmptyStateCta(
+ contentPadding = contentPadding,
+ viewModel = viewModel,
+ )
+ } else {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
+ }
// TODO(b/326060686): Remove this once keyguard indication area can persist over hub
if (viewModel is CommunalViewModel) {
@@ -460,6 +470,67 @@
}
}
+/**
+ * The empty state displays a fullscreen call-to-action (CTA) tile when no widgets are available.
+ */
+@Composable
+private fun EmptyStateCta(
+ contentPadding: PaddingValues,
+ viewModel: BaseCommunalViewModel,
+) {
+ val colors = LocalAndroidColorScheme.current
+ Card(
+ modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, colors.primaryFixedDim),
+ shape = RoundedCornerShape(size = 80.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(horizontal = 110.dp),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(R.string.title_for_empty_state_cta),
+ style = MaterialTheme.typography.displaySmall,
+ textAlign = TextAlign.Center,
+ color = colors.secondaryFixed,
+ )
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ Button(
+ modifier = Modifier.height(56.dp),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryFixed,
+ contentColor = colors.onPrimaryFixed,
+ ),
+ onClick = {
+ viewModel.onOpenWidgetEditor(
+ shouldOpenWidgetPickerOnStart = true,
+ )
+ },
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription =
+ stringResource(R.string.label_for_button_in_empty_state_cta),
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(Modifier.width(ButtonDefaults.IconSpacing))
+ Text(
+ text = stringResource(R.string.label_for_button_in_empty_state_cta),
+ style = MaterialTheme.typography.titleSmall,
+ )
+ }
+ }
+ }
+ }
+}
+
@Composable
private fun LockStateIcon(
isUnlocked: Boolean,
@@ -514,7 +585,7 @@
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
+ val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing)
Button(
onClick = onOpenWidgetPicker,
colors = filledButtonColors(),
@@ -669,8 +740,6 @@
is CommunalContentModel.WidgetContent.DisabledWidget ->
DisabledWidgetPlaceholder(model, viewModel, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
- is CommunalContentModel.CtaTileInEditMode ->
- CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -756,45 +825,6 @@
}
}
-/** Presents a CTA tile at the end of the hub in edit mode, to add more widgets. */
-@Composable
-private fun CtaTileInEditModeContent(
- modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
-) {
- if (onOpenWidgetPicker == null) {
- throw IllegalArgumentException("onOpenWidgetPicker should not be null.")
- }
- val colors = LocalAndroidColorScheme.current
- Card(
- modifier = modifier,
- colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(1.dp, colors.primary),
- shape = RoundedCornerShape(200.dp),
- onClick = onOpenWidgetPicker,
- ) {
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement =
- Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Icon(
- imageVector = Icons.Outlined.Widgets,
- contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
- tint = colors.primary,
- modifier = Modifier.size(Dimensions.IconSize),
- )
- Text(
- text = stringResource(R.string.cta_label_to_open_widget_picker),
- style = MaterialTheme.typography.titleLarge,
- color = colors.primary,
- textAlign = TextAlign.Center,
- )
- }
- }
-}
-
@Composable
private fun WidgetContent(
viewModel: BaseCommunalViewModel,
@@ -1045,7 +1075,6 @@
val ToolbarPaddingHorizontal = 16.dp
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
- val ToolbarButtonSpaceBetween = 8.dp
val ButtonPadding =
PaddingValues(
vertical = ToolbarButtonPaddingVertical,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 1adb335..e7bfee3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -34,10 +34,12 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -235,6 +237,7 @@
}
/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
+@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalFoundationApi
@Composable
fun LazyGridItemScope.DraggableItem(
@@ -269,7 +272,11 @@
Box(modifier) {
Box(draggingModifier) { content(dragging) }
AnimatedVisibility(
- modifier = Modifier.matchParentSize(),
+ modifier =
+ Modifier.matchParentSize()
+ // Do not consume motion events in the highlighted item and pass them down to
+ // the content.
+ .pointerInteropFilter { false },
visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
enter = fadeIn(),
exit = fadeOut()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 5349acd..4c656b0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,7 +1,5 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import com.android.compose.animation.scene.transitions
import com.android.systemui.bouncer.ui.composable.Bouncer
@@ -32,7 +30,6 @@
* Please keep the list sorted alphabetically.
*/
val SceneContainerTransitions = transitions {
- defaultSwipeSpec = spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)
// Scene transitions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index a5707e1..8e9d769 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -817,6 +817,13 @@
}
@Test
+ fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
+ testScope.runTest {
+ underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
+ verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true)
+ }
+
+ @Test
fun navigateToCommunalWidgetSettings_startsActivity() =
testScope.runTest {
underTest.navigateToCommunalWidgetSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index a7e98ea..9aebc30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -149,13 +149,11 @@
val communalContent by collectLastValue(underTest.communalContent)
// Only Widgets and CTA tile are shown.
- assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.size).isEqualTo(2)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
- assertThat(communalContent?.get(2))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@Test
@@ -195,24 +193,20 @@
val communalContent by collectLastValue(underTest.communalContent)
// Widgets and CTA tile are shown.
- assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.size).isEqualTo(2)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
- assertThat(communalContent?.get(2))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
underTest.onDeleteWidget(widgets.get(0).appWidgetId)
// Only one widget and CTA tile remain.
- assertThat(communalContent?.size).isEqualTo(2)
+ assertThat(communalContent?.size).isEqualTo(1)
val item = communalContent?.get(0)
val appWidgetId =
if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
- assertThat(communalContent?.get(1))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b8..5be765d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -194,6 +194,41 @@
}
@Test
+ fun isEmptyState_isTrue_noWidgetButActiveLiveContent() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ widgetRepository.setCommunalWidgets(emptyList())
+ // UMO playing
+ mediaRepository.mediaActive()
+ smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+
+ val isEmptyState by collectLastValue(underTest.isEmptyState)
+ assertThat(isEmptyState).isTrue()
+ }
+
+ @Test
+ fun isEmptyState_isFalse_withWidgets() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ widgetRepository.setCommunalWidgets(
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 1,
+ providerInfo = providerInfo,
+ )
+ ),
+ )
+ mediaRepository.mediaInactive()
+ smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+
+ val isEmptyState by collectLastValue(underTest.isEmptyState)
+ assertThat(isEmptyState).isFalse()
+ }
+
+ @Test
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a9151e8..2195849 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1119,15 +1119,15 @@
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
- <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
+ <!-- Text for call-to-action button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
<string name="cta_tile_button_to_open_widget_editor">Customize</string>
- <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
+ <!-- Text for call-to-action button that dismisses the tile on click. [CHAR LIMIT=50] -->
<string name="cta_tile_button_to_dismiss">Dismiss</string>
- <!-- Label for CTA tile to edit the glanceable hub [CHAR LIMIT=100] -->
+ <!-- Label for call-to-action tile to edit the glanceable hub [CHAR LIMIT=100] -->
<string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
- <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
+ <!-- Label for call-to-action tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
- <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
+ <!-- Text for the popup to be displayed after dismissing the call-to-action tile. [CHAR LIMIT=50] -->
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
<!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
<string name="button_to_configure_widgets_text">Customize widgets</string>
@@ -1141,6 +1141,10 @@
<string name="hub_mode_add_widget_button_text">Add widget</string>
<!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
<string name="hub_mode_editing_exit_button_text">Done</string>
+ <!-- Label for the button in the empty state call-to-action tile that will open the widget picker. [CHAR LIMIT=NONE] -->
+ <string name="label_for_button_in_empty_state_cta">Add widgets</string>
+ <!-- Title for the empty state call-to-action when no widgets are available in the hub. [CHAR LIMIT=NONE] -->
+ <string name="title_for_empty_state_cta">Get quick access to your favorite app widgets without unlocking your tablet.</string>
<!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
<string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
<!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 455b192..6462d02 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -987,6 +987,11 @@
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
+
+ <!--
+ TODO(b/309578419): Make the activity handle insets properly and then remove this.
+ -->
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="Theme.VolumePanelActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 52025b1..ada984d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -264,8 +264,11 @@
}
/** Show the widget editor Activity. */
- fun showWidgetEditor(preselectedKey: String? = null) {
- editWidgetsActivityStarter.startActivity(preselectedKey)
+ fun showWidgetEditor(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ ) {
+ editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index fc8d658..7061227 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -91,13 +91,6 @@
override val size = CommunalContentSize.HALF
}
- /** A CTA tile in the glanceable hub edit model which remains visible in the grid. */
- class CtaTileInEditMode : CommunalContentModel {
- override val key: String = KEY.CTA_TILE_IN_EDIT_MODE_KEY
- // Same as widget size.
- override val size = CommunalContentSize.HALF
- }
-
class Tutorial(
id: Int,
override var size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index c913300..531f1987 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -87,6 +87,9 @@
/** Whether the popup message triggered by dismissing the CTA tile is showing. */
open val isPopupOnDismissCtaShowing: Flow<Boolean> = flowOf(false)
+ /** Whether the communal hub is empty with no widget available. */
+ open val isEmptyState: Flow<Boolean> = flowOf(false)
+
/** Hide the popup message triggered by dismissing the CTA tile. */
open fun onHidePopupAfterDismissCta() {}
@@ -103,7 +106,10 @@
open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
- open fun onOpenWidgetEditor(preselectedKey: String? = null) {}
+ open fun onOpenWidgetEditor(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ ) {}
/** Called as the UI requests to dismiss the CTA tile. */
open fun onDismissCtaTile() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index afa7c37..b3002cd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -43,7 +43,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -66,11 +65,9 @@
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent
- .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
- .onEach { models ->
- logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
- }
+ communalInteractor.widgetContent.onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 6e69ed7..c73d738 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -41,8 +41,10 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -83,6 +85,12 @@
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ override val isEmptyState: Flow<Boolean> =
+ communalInteractor.widgetContent
+ .map { it.isEmpty() }
+ .distinctUntilChanged()
+ .onEach { logger.d("isEmptyState: $it") }
+
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
@@ -112,8 +120,10 @@
}
}
- override fun onOpenWidgetEditor(preselectedKey: String?) =
- communalInteractor.showWidgetEditor(preselectedKey)
+ override fun onOpenWidgetEditor(
+ preselectedKey: String?,
+ shouldOpenWidgetPickerOnStart: Boolean,
+ ) = communalInteractor.showWidgetEditor(preselectedKey, shouldOpenWidgetPickerOnStart)
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index ba18f01..902133d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -60,12 +60,15 @@
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
+ const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
}
private val logger = Logger(logBuffer, "EditWidgetsActivity")
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
+ private var shouldOpenWidgetPickerOnStart = false
+
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
@@ -112,6 +115,9 @@
window.setDecorFitsSystemWindows(false)
val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
+ shouldOpenWidgetPickerOnStart =
+ intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false)
+
communalViewModel.setSelectedKey(preselectedKey)
setContent {
@@ -162,6 +168,11 @@
override fun onStart() {
super.onStart()
+ if (shouldOpenWidgetPickerOnStart) {
+ onOpenWidgetPicker()
+ shouldOpenWidgetPickerOnStart = false
+ }
+
logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index d1843af..76be005 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -18,13 +18,17 @@
import android.content.Context
import android.content.Intent
+import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_OPEN_WIDGET_PICKER_ON_START
import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
interface EditWidgetsActivityStarter {
- fun startActivity(preselectedKey: String? = null)
+ fun startActivity(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ )
}
class EditWidgetsActivityStarterImpl
@@ -34,11 +38,14 @@
private val activityStarter: ActivityStarter,
) : EditWidgetsActivityStarter {
- override fun startActivity(preselectedKey: String?) {
+ override fun startActivity(preselectedKey: String?, shouldOpenWidgetPickerOnStart: Boolean) {
activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .apply { preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) } },
+ .apply {
+ preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) }
+ putExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, shouldOpenWidgetPickerOnStart)
+ },
/* onlyProvisioned = */ true,
/* dismissShade = */ true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 182798e..53aee5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2520,18 +2520,8 @@
String message = "";
switch (msg.what) {
case SHOW:
- // There is a potential race condition when SysUI starts up. CentralSurfaces
- // must invoke #registerCentralSurfaces on this class before any messages can be
- // processed. If this happens, repost the message with a small delay and try
- // again.
- if (mCentralSurfaces == null) {
- message = "DELAYING SHOW";
- Message newMsg = mHandler.obtainMessage(SHOW, (Bundle) msg.obj);
- mHandler.sendMessageDelayed(newMsg, 100);
- } else {
- message = "SHOW";
- handleShow((Bundle) msg.obj);
- }
+ message = "SHOW";
+ handleShow((Bundle) msg.obj);
break;
case HIDE:
message = "HIDE";
@@ -2618,18 +2608,8 @@
Trace.endSection();
break;
case SYSTEM_READY:
- // There is a potential race condition when SysUI starts up. CentralSurfaces
- // must invoke #registerCentralSurfaces on this class before any messages can be
- // processed. If this happens, repost the message with a small delay and try
- // again.
- if (mCentralSurfaces == null) {
- message = "DELAYING SYSTEM_READY";
- Message newMsg = mHandler.obtainMessage(SYSTEM_READY);
- mHandler.sendMessageDelayed(newMsg, 100);
- } else {
- message = "SYSTEM_READY";
- handleSystemReady();
- }
+ message = "SYSTEM_READY";
+ handleSystemReady();
break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index 82c28ff..65b42e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -74,6 +74,14 @@
* exit bouncer.
*/
fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ when (event.action) {
+ KeyEvent.ACTION_DOWN -> {
+ val device = event.getDevice()
+ if (device != null && device.isFullKeyboard() && device.isExternal()) {
+ powerInteractor.onUserTouch()
+ }
+ }
+ }
when (event.keyCode) {
KeyEvent.KEYCODE_BACK ->
if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 4cb342b..1eea556 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -19,14 +19,16 @@
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.CoreStartable
-import com.android.systemui.biometrics.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -70,8 +72,9 @@
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
- Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
- PixelFormat.TRANSLUCENT
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT,
)
.apply {
title = "AlternateBouncerView"
@@ -113,8 +116,36 @@
}
windowManager.get().removeView(alternateBouncerView)
+ alternateBouncerView!!.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler)
+ alternateBouncerView = null
}
+ private val onAttachAddBackGestureHandler =
+ object : View.OnAttachStateChangeListener {
+ private val onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
+ onBackRequested()
+ }
+
+ override fun onViewAttachedToWindow(view: View) {
+ view
+ .findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_OVERLAY,
+ onBackInvokedCallback,
+ )
+ }
+
+ override fun onViewDetachedFromWindow(view: View) {
+ view
+ .findOnBackInvokedDispatcher()
+ ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+ }
+
+ fun onBackRequested() {
+ alternateBouncerDependencies.get().viewModel.hideAlternateBouncer()
+ }
+ }
+
private fun addViewToWindowManager() {
if (alternateBouncerView?.isAttachedToWindow == true) {
return
@@ -125,6 +156,7 @@
as ConstraintLayout
windowManager.get().addView(alternateBouncerView, layoutParams)
+ alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler)
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a4b32a7..c93ef65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -69,8 +69,6 @@
import android.util.Log;
import android.util.MathUtils;
import android.view.HapticFeedbackConstants;
-import android.view.InputDevice;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -169,7 +167,6 @@
import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
@@ -357,7 +354,6 @@
private final QuickSettingsControllerImpl mQsController;
private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
- private final KeyHandler mKeyHandler = new KeyHandler();
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -746,7 +742,6 @@
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
@@ -818,7 +813,6 @@
mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener());
mView.setOnTouchListener(getTouchHandler());
- mView.setOnKeyListener(getKeyHandler());
mView.setOnConfigurationChangedListener(config -> loadDimens());
mResources = mView.getResources();
@@ -903,7 +897,6 @@
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- shadeTransitionController.setShadeViewController(this);
dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
quickSettingsController.setExpansionHeightListener(this::onQsSetExpansionHeightCalled);
quickSettingsController.setQsStateUpdateListener(this::onQsStateUpdated);
@@ -3586,11 +3579,6 @@
return mTouchHandler;
}
- @VisibleForTesting
- KeyHandler getKeyHandler() {
- return mKeyHandler;
- }
-
@Override
public boolean closeUserSwitcherIfOpen() {
if (mKeyguardUserSwitcherController != null) {
@@ -5245,21 +5233,6 @@
}
}
- /** Handles KeyEvents for the Shade. */
- public final class KeyHandler implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- final InputDevice d = event.getDevice();
- // Trigger user activity if the event comes from a full external keyboard
- if (d != null && d.isFullKeyboard() && d.isExternal()) {
- mCentralSurfaces.userActivity();
- }
- }
- return false;
- }
- }
-
private final class HeadsUpNotificationViewControllerImpl implements
HeadsUpTouchHelper.HeadsUpNotificationViewController {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 3a0e167..7525184 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -62,7 +62,6 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.SystemBarUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.classifier.Classifier;
@@ -80,7 +79,6 @@
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -133,9 +131,7 @@
private final FrameLayout mQsFrame;
private final QsFrameTranslateController mQsFrameTranslateController;
- private final ShadeTransitionController mShadeTransitionController;
private final PulseExpansionHandler mPulseExpansionHandler;
- private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final LightBarController mLightBarController;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@@ -147,7 +143,6 @@
private final KeyguardBypassController mKeyguardBypassController;
private final NotificationRemoteInputManager mRemoteInputManager;
private VelocityTracker mQsVelocityTracker;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ScrimController mScrimController;
private final MediaDataManager mMediaDataManager;
private final MediaHierarchyManager mMediaHierarchyManager;
@@ -309,10 +304,8 @@
Lazy<NotificationPanelViewController> panelViewControllerLazy,
NotificationPanelView panelView,
QsFrameTranslateController qsFrameTranslateController,
- ShadeTransitionController shadeTransitionController,
PulseExpansionHandler pulseExpansionHandler,
NotificationRemoteInputManager remoteInputManager,
- ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
LightBarController lightBarController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
@@ -322,7 +315,6 @@
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
KeyguardStateController keyguardStateController,
KeyguardBypassController keyguardBypassController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
ScrimController scrimController,
MediaDataManager mediaDataManager,
MediaHierarchyManager mediaHierarchyManager,
@@ -353,7 +345,6 @@
mSplitShadeStateController = splitShadeStateController;
mSplitShadeEnabled = mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
mQsFrameTranslateController = qsFrameTranslateController;
- mShadeTransitionController = shadeTransitionController;
mPulseExpansionHandler = pulseExpansionHandler;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
@@ -361,7 +352,6 @@
}
});
mRemoteInputManager = remoteInputManager;
- mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLightBarController = lightBarController;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -371,7 +361,6 @@
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mKeyguardStateController = keyguardStateController;
mKeyguardBypassController = keyguardBypassController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mScrimController = scrimController;
mMediaDataManager = mediaDataManager;
mMediaHierarchyManager = mediaHierarchyManager;
@@ -2180,7 +2169,6 @@
}
});
mLockscreenShadeTransitionController.setQS(mQs);
- mShadeTransitionController.setQs(mQs);
mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
mQs.setScrollListener(mQsScrollListener);
updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index e40bcd5..df5ff5a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -56,11 +56,6 @@
return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
}
- /** Removes an expansion listener. */
- fun removeExpansionListener(listener: ShadeExpansionListener) {
- expansionListeners.remove(listener)
- }
-
/** Adds a listener that will be notified when the panel state has changed. */
@Deprecated("Use ShadeInteractor instead")
fun addStateListener(listener: ShadeStateListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 1fb0fb6..60810a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -28,6 +28,7 @@
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -47,12 +48,14 @@
private val controller: SplitShadeStateController,
private val shadeController: ShadeController,
private val shadeHeaderController: ShadeHeaderController,
+ private val scrimShadeTransitionController: ScrimShadeTransitionController,
) : CoreStartable {
override fun start() {
hydrateShadeMode()
logTouchesTo(touchLog)
initHeaderController()
+ scrimShadeTransitionController.init()
}
private fun initHeaderController() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index abb69f6..151e289 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -17,27 +17,55 @@
package com.android.systemui.shade.transition
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.phone.ScrimController
+import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Controls the scrim properties during the shade expansion transition on non-lockscreen. */
@SysUISingleton
class ScrimShadeTransitionController
@Inject
constructor(
- dumpManager: DumpManager,
+ @Application private val applicationScope: CoroutineScope,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
+ private val dumpManager: DumpManager,
private val scrimController: ScrimController,
) {
-
private var lastExpansionFraction: Float? = null
private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
private var currentPanelState: Int? = null
- init {
+ fun init() {
+ if (SceneContainerFlag.isEnabled) {
+ applicationScope.launch {
+ panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
+ onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(
+ fraction = panelExpansion,
+ expanded = panelExpansion > 0f,
+ tracking = true,
+ dragDownPxAmount = 0f,
+ )
+ )
+ }
+ }
+ } else {
+ val currentState =
+ shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+ onPanelExpansionChanged(currentState)
+ shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
+ }
dumpManager.registerDumpable(
ScrimShadeTransitionController::class.java.simpleName,
this::dump
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
deleted file mode 100644
index 3a5c5e1..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade.transition
-
-import android.content.Context
-import android.content.res.Configuration
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.qs.QS
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.shade.PanelState
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.shade.panelStateToString
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import dagger.Lazy
-import java.io.PrintWriter
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-/** Controls the shade expansion transition on non-lockscreen. */
-@SysUISingleton
-class ShadeTransitionController
-@Inject
-constructor(
- @Application private val applicationScope: CoroutineScope,
- configurationController: ConfigurationController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
- dumpManager: DumpManager,
- private val context: Context,
- private val scrimShadeTransitionController: ScrimShadeTransitionController,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val splitShadeStateController: SplitShadeStateController,
- private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
-) {
-
- lateinit var shadeViewController: ShadeViewController
- lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
- lateinit var qs: QS
-
- private var inSplitShade = false
- private var currentPanelState: Int? = null
- private var lastShadeExpansionChangeEvent: ShadeExpansionChangeEvent? = null
-
- init {
- updateResources()
- configurationController.addCallback(
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- }
- )
- if (SceneContainerFlag.isEnabled) {
- applicationScope.launch {
- panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
- onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = panelExpansion,
- expanded = panelExpansion > 0f,
- tracking = true,
- dragDownPxAmount = 0f,
- )
- )
- }
- }
- } else {
- val currentState =
- shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
- onPanelExpansionChanged(currentState)
- shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
- }
- dumpManager.registerCriticalDumpable("ShadeTransitionController") { printWriter, _ ->
- dump(printWriter)
- }
- }
-
- private fun updateResources() {
- inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources)
- }
-
- private fun onPanelStateChanged(@PanelState state: Int) {
- currentPanelState = state
- scrimShadeTransitionController.onPanelStateChanged(state)
- }
-
- private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
- lastShadeExpansionChangeEvent = event
- scrimShadeTransitionController.onPanelExpansionChanged(event)
- }
-
- private fun dump(pw: PrintWriter) {
- pw.println(
- """
- ShadeTransitionController:
- inSplitShade: $inSplitShade
- isScreenUnlocked: ${isScreenUnlocked()}
- currentPanelState: ${currentPanelState?.panelStateToString()}
- lastPanelExpansionChangeEvent: $lastShadeExpansionChangeEvent
- qs.isInitialized: ${this::qs.isInitialized}
- npvc.isInitialized: ${this::shadeViewController.isInitialized}
- nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
- """
- .trimIndent()
- )
- }
-
- private fun isScreenUnlocked() =
- statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 80c3551..321b608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -71,6 +71,15 @@
@NonNull
public StatusBarNotification rebuildForCanceledSmartReplies(
NotificationEntry entry) {
+ return rebuildWithExistingReplies(entry);
+ }
+
+ /**
+ * Rebuilds to include any previously-added remote input replies.
+ * For when the app cancels a notification that has already been lifetime extended.
+ */
+ @NonNull
+ public StatusBarNotification rebuildWithExistingReplies(NotificationEntry entry) {
return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */,
false /* showSpinner */, null /* mimeType */, null /* uri */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 28fff15..fe59d73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -127,6 +127,15 @@
mSmartReplyController.stopSending(entry)
mNotifUpdater.onInternalNotificationUpdate(newSbn,
"Extending lifetime of notification with smart reply")
+ } else {
+ // The app may have re-cancelled a notification after it had already
+ // been lifetime extended.
+ // Rebuild the notification with the replies it already had to ensure
+ // those replies continue to be displayed.
+ val newSbn = mRebuilder.rebuildWithExistingReplies(entry)
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification that has already been " +
+ "lifetime extended.")
}
} else {
// Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index e4db4c7..ac2a0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -16,12 +16,14 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -67,6 +69,17 @@
invalidateList("onSensitiveStateChanged")
}
+ private val screenshareSecretFilter = object : NotifFilter("ScreenshareSecretFilter") {
+ val NotificationEntry.isSecret
+ get() = channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
+ sbn.notification?.visibility == Notification.VISIBILITY_SECRET
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+ return screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive &&
+ entry.isSecret
+ }
+ }
+
override fun attach(pipeline: NotifPipeline) {
dynamicPrivacyController.addListener(this)
if (screenshareNotificationHiding()) {
@@ -75,6 +88,9 @@
}
pipeline.addOnBeforeRenderListListener(this)
pipeline.addPreRenderInvalidator(this)
+ if (screenshareNotificationHiding()) {
+ pipeline.addFinalizeFilter(screenshareSecretFilter)
+ }
}
override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 6836816..c4d9ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -207,11 +207,6 @@
override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
}
-class BubbleAppSuspendedSuppressor :
- VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") {
- override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended
-}
-
class BubbleNoMetadataSuppressor() :
VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
@@ -221,6 +216,11 @@
override fun shouldSuppress(entry: NotificationEntry) = !isValidMetadata(entry.bubbleMetadata)
}
+class AlertAppSuspendedSuppressor :
+ VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "app is suspended") {
+ override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended
+}
+
class AlertKeyguardVisibilitySuppressor(
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider
) : VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "hidden on keyguard") {
@@ -228,11 +228,11 @@
keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
-
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
-) : VisualInterruptionFilter(
+) :
+ VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
reason = "avalanche",
) {
@@ -261,7 +261,7 @@
return suppress
}
- private fun calculateState(entry: NotificationEntry): State {
+ private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
entry.sbn.notification.`when` > avalancheProvider.startTime
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 334e08d..a2f97bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -56,11 +56,11 @@
}
}
- fun logSuspendedAppBubble(entry: NotificationEntry) {
+ fun logNoAlertingAppSuspended(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
}, {
- "No bubble up: notification: app $str1 is suspended"
+ "No alerting: app is suspended: $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a655c72..d591114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -204,11 +204,6 @@
return false;
}
- if (entry.getRanking().isSuspended()) {
- mLogger.logSuspendedAppBubble(entry);
- return false;
- }
-
if (entry.getBubbleMetadata() == null
|| (entry.getBubbleMetadata().getShortcutId() == null
&& entry.getBubbleMetadata().getIntent() == null)) {
@@ -559,6 +554,13 @@
}
}
+ if (entry.getRanking().isSuspended()) {
+ if (log) {
+ mLogger.logNoAlertingAppSuspended(entry);
+ }
+ return false;
+ }
+
if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
if (log) mLogger.logNoAlertingNotificationHidden(entry);
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index dabb18b..375b6e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -46,23 +46,22 @@
class VisualInterruptionDecisionProviderImpl
@Inject
constructor(
- private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
- private val batteryController: BatteryController,
- deviceProvisionedController: DeviceProvisionedController,
- private val eventLog: EventLog,
- private val globalSettings: GlobalSettings,
- private val headsUpManager: HeadsUpManager,
- private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
- keyguardStateController: KeyguardStateController,
- private val logger: VisualInterruptionDecisionLogger,
- @Main private val mainHandler: Handler,
- private val powerManager: PowerManager,
- private val statusBarStateController: StatusBarStateController,
- private val systemClock: SystemClock,
- private val uiEventLogger: UiEventLogger,
- private val userTracker: UserTracker,
- private val avalancheProvider: AvalancheProvider
-
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val batteryController: BatteryController,
+ deviceProvisionedController: DeviceProvisionedController,
+ private val eventLog: EventLog,
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ keyguardStateController: KeyguardStateController,
+ private val logger: VisualInterruptionDecisionLogger,
+ @Main private val mainHandler: Handler,
+ private val powerManager: PowerManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val systemClock: SystemClock,
+ private val uiEventLogger: UiEventLogger,
+ private val userTracker: UserTracker,
+ private val avalancheProvider: AvalancheProvider
) : VisualInterruptionDecisionProvider {
init {
@@ -164,10 +163,10 @@
addFilter(PulseLockscreenVisibilityPrivateSuppressor())
addFilter(PulseLowImportanceSuppressor())
addFilter(BubbleNotAllowedSuppressor())
- addFilter(BubbleAppSuspendedSuppressor())
addFilter(BubbleNoMetadataSuppressor())
addFilter(HunGroupAlertBehaviorSuppressor())
addFilter(HunJustLaunchedFsiSuppressor())
+ addFilter(AlertAppSuspendedSuppressor())
addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
if (NotificationAvalancheSuppression.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index f76de04c..710cdcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -836,7 +836,6 @@
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
mLightRevealScrim = lightRevealScrim;
- // Based on teamfood flag, turn predictive back dispatch on at runtime.
if (predictiveBackSysui()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
@@ -3060,6 +3059,9 @@
public void onConfigChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
+ if (predictiveBackSysui()) {
+ mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
if (DEBUG) {
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
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 272b488..11ec417f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -306,28 +306,6 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testRaceCondition_doNotRegisterCentralSurfacesImmediately() {
- create(false);
-
- // GIVEN central surfaces is not registered with KeyguardViewMediator, but a call to enable
- // keyguard comes in
- mViewMediator.onSystemReady();
- mViewMediator.setKeyguardEnabled(true);
- TestableLooper.get(this).processAllMessages();
-
- // If this step has been reached, then system ui has not crashed. Now register
- // CentralSurfaces
- assertFalse(mViewMediator.isShowingAndNotOccluded());
- register();
- TestableLooper.get(this).moveTimeForward(100);
- TestableLooper.get(this).processAllMessages();
-
- // THEN keyguard is shown
- assertTrue(mViewMediator.isShowingAndNotOccluded());
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
@@ -1206,11 +1184,6 @@
}
private void createAndStartViewMediator(boolean orderUnlockAndWake) {
- create(orderUnlockAndWake);
- register();
- }
-
- private void create(boolean orderUnlockAndWake) {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
@@ -1262,9 +1235,7 @@
mKeyguardInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
- }
- private void register() {
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 24c9d6b..56e61e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -136,7 +136,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -307,7 +306,6 @@
@Mock protected NotificationListContainer mNotificationListContainer;
@Mock protected NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock protected ShadeTransitionController mShadeTransitionController;
@Mock protected QS mQs;
@Mock protected QSFragmentLegacy mQSFragment;
@Mock protected ViewGroup mQsHeader;
@@ -725,7 +723,6 @@
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
- mShadeTransitionController,
systemClock,
mKeyguardBottomAreaViewModel,
mKeyguardBottomAreaInteractor,
@@ -792,10 +789,8 @@
mNotificationPanelViewControllerLazy,
mView,
mQsFrameTranslateController,
- mShadeTransitionController,
expansionHandler,
mNotificationRemoteInputManager,
- mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
mLightBarController,
mNotificationStackScrollLayoutController,
@@ -805,7 +800,6 @@
mStatusBarTouchableRegionManager,
mKeyguardStateController,
mKeyguardBypassController,
- mUpdateMonitor,
mScrimController,
mMediaDataManager,
mMediaHierarchyManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index ee279d8..20d877e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -69,7 +69,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -134,7 +133,6 @@
@Mock protected ViewGroup mQsHeader;
@Mock protected ViewParent mPanelViewParent;
@Mock protected QsFrameTranslateController mQsFrameTranslateController;
- @Mock protected ShadeTransitionController mShadeTransitionController;
@Mock protected PulseExpansionHandler mPulseExpansionHandler;
@Mock protected NotificationRemoteInputManager mNotificationRemoteInputManager;
@Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -307,10 +305,8 @@
mPanelViewControllerLazy,
mPanelView,
mQsFrameTranslateController,
- mShadeTransitionController,
mPulseExpansionHandler,
mNotificationRemoteInputManager,
- mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
mLightBarController,
mNotificationStackScrollLayoutController,
@@ -320,7 +316,6 @@
mStatusBarTouchableRegionManager,
mKeyguardStateController,
mKeyguardBypassController,
- mKeyguardUpdateMonitor,
mScrimController,
mMediaDataManager,
mMediaHierarchyManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index a5bd2ae..dea905a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -1,12 +1,41 @@
package com.android.systemui.shade.transition
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.dump.DumpManager
-import com.android.systemui.shade.STATE_OPENING
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.FakeSceneDataSource
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+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
@@ -21,26 +50,140 @@
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var dumpManager: DumpManager
- private lateinit var controller: ScrimShadeTransitionController
+ private val shadeExpansionStateManager = ShadeExpansionStateManager()
+ private val kosmos = testKosmos()
+ private lateinit var testScope: TestScope
+ private lateinit var applicationScope: CoroutineScope
+ private lateinit var panelExpansionInteractor: PanelExpansionInteractor
+ private lateinit var deviceEntryRepository: FakeDeviceEntryRepository
+ private lateinit var deviceUnlockedInteractor: DeviceUnlockedInteractor
+ private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var fakeSceneDataSource: FakeSceneDataSource
+
+ private lateinit var underTest: ScrimShadeTransitionController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.ensureTestableResources()
- controller = ScrimShadeTransitionController(dumpManager, scrimController)
-
- controller.onPanelStateChanged(STATE_OPENING)
+ testScope = kosmos.testScope
+ applicationScope = kosmos.applicationCoroutineScope
+ panelExpansionInteractor = kosmos.panelExpansionInteractor
+ deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+ sceneInteractor = kosmos.sceneInteractor
+ fakeSceneDataSource = kosmos.fakeSceneDataSource
+ underTest = ScrimShadeTransitionController(
+ applicationScope,
+ shadeExpansionStateManager,
+ { panelExpansionInteractor },
+ dumpManager,
+ scrimController,
+ )
+ underTest.init()
}
@Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
+ underTest.onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ verify(scrimController).setRawPanelExpansionFraction(DEFAULT_EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ fun onPanelStateChanged_forwardsToScrimTransitionController() {
+ startLegacyPanelExpansion()
+
+ verify(scrimController).setRawPanelExpansionFraction(DEFAULT_EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun sceneChanges_forwardsToScrimTransitionController() =
+ testScope.runTest {
+ var rawExpansion: Float? = null
+ whenever(scrimController.setRawPanelExpansionFraction(any())).then {
+ (it.arguments[0] as Float?).also { rawExp -> rawExpansion = rawExp }
+ }
+ setUnlocked(true)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Gone)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ changeScene(Scenes.Gone, transitionState)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ Truth.assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ Truth.assertThat(rawExpansion)
+ .isEqualTo(0f)
+
+ changeScene(Scenes.Shade, transitionState) { progress ->
+ Truth.assertThat(rawExpansion)
+ .isEqualTo(progress)
+ }
+ }
+
+ private fun startLegacyPanelExpansion() {
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ DEFAULT_EXPANSION_EVENT.fraction,
+ DEFAULT_EXPANSION_EVENT.expanded,
+ DEFAULT_EXPANSION_EVENT.tracking,
+ DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
+ )
+ }
+
+ private fun TestScope.setUnlocked(isUnlocked: Boolean) {
+ val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
+ deviceEntryRepository.setUnlocked(isUnlocked)
+ runCurrent()
+
+ Truth.assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
+ }
+
+ private fun TestScope.changeScene(
+ toScene: SceneKey,
+ transitionState: MutableStateFlow<ObservableTransitionState>,
+ assertDuringProgress: ((progress: Float) -> Unit) = {},
+ ) {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = checkNotNull(currentScene),
+ toScene = toScene,
+ progress = progressFlow,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.2f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.6f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 1f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ transitionState.value = ObservableTransitionState.Idle(toScene)
+ fakeSceneDataSource.changeScene(toScene)
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ Truth.assertThat(currentScene).isEqualTo(toScene)
}
companion object {
- val EXPANSION_EVENT =
+ val DEFAULT_EXPANSION_EVENT =
ShadeExpansionChangeEvent(
fraction = 0.5f,
expanded = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
deleted file mode 100644
index 0a9541a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.shade.transition
-
-import android.platform.test.annotations.DisableFlags
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.FakeSceneDataSource
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.shade.STATE_OPENING
-import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-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.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ShadeTransitionControllerTest : SysuiTestCase() {
-
- @Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
-
- private lateinit var controller: ShadeTransitionController
-
- private val configurationController = FakeConfigurationController()
- private val shadeExpansionStateManager = ShadeExpansionStateManager()
- private val kosmos = testKosmos()
- private lateinit var testScope: TestScope
- private lateinit var applicationScope: CoroutineScope
- private lateinit var panelExpansionInteractor: PanelExpansionInteractor
- private lateinit var deviceEntryRepository: FakeDeviceEntryRepository
- private lateinit var deviceUnlockedInteractor: DeviceUnlockedInteractor
- private lateinit var sceneInteractor: SceneInteractor
- private lateinit var fakeSceneDataSource: FakeSceneDataSource
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- testScope = kosmos.testScope
- applicationScope = kosmos.applicationCoroutineScope
- panelExpansionInteractor = kosmos.panelExpansionInteractor
- deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- sceneInteractor = kosmos.sceneInteractor
- fakeSceneDataSource = kosmos.fakeSceneDataSource
-
- controller =
- ShadeTransitionController(
- applicationScope,
- configurationController,
- shadeExpansionStateManager,
- dumpManager,
- context,
- scrimShadeTransitionController,
- statusBarStateController,
- ResourcesSplitShadeStateController(),
- ) {
- panelExpansionInteractor
- }
- }
-
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
- fun onPanelStateChanged_forwardsToScrimTransitionController() {
- startLegacyPanelExpansion()
-
- verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
- verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
- }
-
- @Test
- @EnableSceneContainer
- fun sceneChanges_forwardsToScrimTransitionController() =
- testScope.runTest {
- var latestChangeEvent: ShadeExpansionChangeEvent? = null
- whenever(scrimShadeTransitionController.onPanelExpansionChanged(any())).thenAnswer {
- latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
- Unit
- }
- setUnlocked(true)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(Scenes.Gone)
- )
- sceneInteractor.setTransitionState(transitionState)
-
- changeScene(Scenes.Gone, transitionState)
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- assertThat(currentScene).isEqualTo(Scenes.Gone)
-
- assertThat(latestChangeEvent)
- .isEqualTo(
- ShadeExpansionChangeEvent(
- fraction = 0f,
- expanded = false,
- tracking = true,
- dragDownPxAmount = 0f,
- )
- )
-
- changeScene(Scenes.Shade, transitionState) { progress ->
- assertThat(latestChangeEvent)
- .isEqualTo(
- ShadeExpansionChangeEvent(
- fraction = progress,
- expanded = progress > 0,
- tracking = true,
- dragDownPxAmount = 0f,
- )
- )
- }
- }
-
- private fun startLegacyPanelExpansion() {
- shadeExpansionStateManager.onPanelExpansionChanged(
- DEFAULT_EXPANSION_EVENT.fraction,
- DEFAULT_EXPANSION_EVENT.expanded,
- DEFAULT_EXPANSION_EVENT.tracking,
- DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
- )
- }
-
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
- private fun TestScope.changeScene(
- toScene: SceneKey,
- transitionState: MutableStateFlow<ObservableTransitionState>,
- assertDuringProgress: ((progress: Float) -> Unit) = {},
- ) {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val progressFlow = MutableStateFlow(0f)
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = checkNotNull(currentScene),
- toScene = toScene,
- progress = progressFlow,
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 0.2f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 0.6f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 1f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- transitionState.value = ObservableTransitionState.Idle(toScene)
- fakeSceneDataSource.changeScene(toScene)
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- assertThat(currentScene).isEqualTo(toScene)
- }
-
- companion object {
- private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
- private val DEFAULT_EXPANSION_EVENT =
- ShadeExpansionChangeEvent(
- fraction = 0.5f,
- expanded = true,
- tracking = true,
- dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index 85b8b03..d3df48e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -98,6 +98,7 @@
`when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
`when`(rebuilder.rebuildForRemoteInputReply(any())).thenReturn(sbn)
`when`(rebuilder.rebuildForSendingSmartReply(any(), any())).thenReturn(sbn)
+ `when`(rebuilder.rebuildWithExistingReplies(any())).thenReturn(sbn)
}
val remoteInputActiveExtender get() = coordinator.mRemoteInputActiveExtender
@@ -208,13 +209,30 @@
it.onEntryUpdated(entry, true)
}
-
verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry)
verify(smartReplyController, times(1)).stopSending(entry)
}
@Test
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testRepeatedUpdateTriggersRebuild() {
+ // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+ val entry = NotificationEntryBuilder()
+ .setId(3)
+ .setTag("entry")
+ .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+ .build()
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+ collectionListeners.forEach {
+ it.onEntryUpdated(entry, true)
+ }
+
+ verify(rebuilder, times(1)).rebuildWithExistingReplies(entry)
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testLifetimeExtensionListenerClearsRemoteInputs() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
val entry = NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 018a571..b161f84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -16,7 +16,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
@@ -25,14 +29,17 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -44,6 +51,8 @@
import com.android.systemui.util.mockito.withArgCaptor
import dagger.BindsInstance
import dagger.Component
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -114,6 +123,49 @@
}
@Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun screenshareSecretFilter_flagDisabled_filterNoAdded() {
+ coordinator.attach(pipeline)
+
+ verify(pipeline, never()).addFinalizeFilter(any(NotifFilter::class.java))
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun screenshareSecretFilter_sensitiveInctive_noFiltersSecret() {
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive)
+ .thenReturn(false)
+
+ coordinator.attach(pipeline)
+ val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) }
+
+ val defaultNotification = createNotificationEntry("test", false, false)
+ val notificationWithSecretVisibility = createNotificationEntry("test", true, false)
+ val notificationOnSecretChannel = createNotificationEntry("test", false, true)
+
+ assertFalse(filter.shouldFilterOut(defaultNotification, 0))
+ assertFalse(filter.shouldFilterOut(notificationWithSecretVisibility, 0))
+ assertFalse(filter.shouldFilterOut(notificationOnSecretChannel, 0))
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun screenshareSecretFilter_sensitiveActive_filtersSecret() {
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ coordinator.attach(pipeline)
+ val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) }
+
+ val defaultNotification = createNotificationEntry("test", false, false)
+ val notificationWithSecretVisibility = createNotificationEntry("test", true, false)
+ val notificationOnSecretChannel = createNotificationEntry("test", false, true)
+
+ assertFalse(filter.shouldFilterOut(defaultNotification, 0))
+ assertTrue(filter.shouldFilterOut(notificationWithSecretVisibility, 0))
+ assertTrue(filter.shouldFilterOut(notificationOnSecretChannel, 0))
+ }
+
+ @Test
fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
val onBeforeRenderListListener =
@@ -638,6 +690,32 @@
override fun getRepresentativeEntry(): NotificationEntry = mockEntry
}
}
+
+ private fun createNotificationEntry(
+ packageName: String,
+ secretVisibility: Boolean = false,
+ secretChannelVisibility: Boolean = false,
+ ): NotificationEntry {
+ val notification = Notification()
+ if (secretVisibility) {
+ // Developer has marked notification as public
+ notification.visibility = Notification.VISIBILITY_SECRET
+ }
+ val notificationEntry =
+ NotificationEntryBuilder().setNotification(notification).setPkg(packageName).build()
+ val channel = NotificationChannel("1", "1", NotificationManager.IMPORTANCE_HIGH)
+ if (secretChannelVisibility) {
+ // User doesn't allow notifications at the channel level
+ channel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+ }
+ notificationEntry.setRanking(
+ RankingBuilder(notificationEntry.ranking)
+ .setChannel(channel)
+ .setVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE)
+ .build()
+ )
+ return notificationEntry
+ }
}
@CoordinatorScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 66a306e..24f6708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -327,6 +327,13 @@
}
@Test
+ fun testShouldNotPeek_appSuspended() {
+ ensurePeekState()
+ assertShouldNotBubble(buildPeekEntry { packageSuspended = true })
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldNotPeek_hiddenOnKeyguard() {
ensurePeekState({ keyguardShouldHideNotification = true })
assertShouldNotHeadsUp(buildPeekEntry())
@@ -412,6 +419,13 @@
}
@Test
+ fun testShouldNotPulse_appSuspended() {
+ ensurePulseState()
+ assertShouldNotHeadsUp(buildPulseEntry { packageSuspended = true })
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldNotPulse_hiddenOnKeyguard() {
ensurePulseState({ keyguardShouldHideNotification = true })
assertShouldNotHeadsUp(buildPulseEntry())
@@ -595,16 +609,16 @@
}
@Test
- fun testShouldNotBubble_hiddenOnKeyguard() {
- ensureBubbleState({ keyguardShouldHideNotification = true })
- assertShouldNotBubble(buildBubbleEntry())
+ fun testShouldNotBubble_appSuspended() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry { packageSuspended = true })
assertNoEventsLogged()
}
@Test
- fun testShouldNotBubble_bubbleAppSuspended() {
- ensureBubbleState()
- assertShouldNotBubble(buildBubbleEntry { packageSuspended = true })
+ fun testShouldNotBubble_hiddenOnKeyguard() {
+ ensureBubbleState({ keyguardShouldHideNotification = true })
+ assertShouldNotBubble(buildBubbleEntry())
assertNoEventsLogged()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index bbdb872..65e04f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.shade.ShadeHeaderController
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shadeController
+import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.splitShadeStateController
import com.android.systemui.util.mockito.mock
@@ -37,6 +38,7 @@
shadeRepository = shadeRepository,
controller = splitShadeStateController,
shadeHeaderController = mock<ShadeHeaderController>(),
+ scrimShadeTransitionController = mock<ScrimShadeTransitionController>(),
shadeController = shadeController
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 21cc8da..9403796 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3550,7 +3550,7 @@
&& userState.isMagnificationTwoFingerTripleTapEnabledLocked()));
final boolean createConnectionForCurrentCapability =
- com.android.window.flags.Flags.magnificationAlwaysDrawFullscreenBorder()
+ com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
|| (userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 0d5fd14..20bec59 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -587,7 +587,7 @@
@Override
public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
- if (Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
getMagnificationConnectionManager()
.onFullscreenMagnificationActivationChanged(displayId, activated);
}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 5e52e06..82e9a26 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -111,6 +111,11 @@
Slog.i(TAG, "applyRestoredPayload() userId=[" + userId + "], payload size=["
+ payload.length + "].");
+ if (payload.length == 0) {
+ Slog.i(TAG, "CDM backup payload was empty.");
+ return;
+ }
+
ByteBuffer buffer = ByteBuffer.wrap(payload);
// Make sure that payload version matches current version to ensure proper deserialization
@@ -120,15 +125,26 @@
return;
}
- // Read the bytes containing backed-up associations
- byte[] associationsPayload = new byte[buffer.getInt()];
- buffer.get(associationsPayload);
+ // Pre-load the bytes into memory before processing them to ensure payload mal-formatting
+ // error is caught early on.
+ final byte[] associationsPayload;
+ final byte[] requestsPayload;
+ try {
+ // Read the bytes containing backed-up associations
+ associationsPayload = new byte[buffer.getInt()];
+ buffer.get(associationsPayload);
+
+ // Read the bytes containing backed-up system data transfer requests user consent
+ requestsPayload = new byte[buffer.getInt()];
+ buffer.get(requestsPayload);
+ } catch (Exception bufferException) {
+ Slog.e(TAG, "CDM backup payload was mal-formatted.", bufferException);
+ return;
+ }
+
final Associations restoredAssociations = readAssociationsFromPayload(
associationsPayload, userId);
- // Read the bytes containing backed-up system data transfer requests user consent
- byte[] requestsPayload = new byte[buffer.getInt()];
- buffer.get(requestsPayload);
List<SystemDataTransferRequest> restoredRequestsForUser =
mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
deleted file mode 100644
index 0a41485..0000000
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ /dev/null
@@ -1,567 +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.companion;
-
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-import android.companion.CompanionDeviceService;
-import android.companion.DevicePresenceEvent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.hardware.power.Mode;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.os.PowerManagerInternal;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.PerUser;
-import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.presence.ObservableUuid;
-import com.android.server.companion.presence.ObservableUuidStore;
-import com.android.server.companion.utils.PackageUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Manages communication with companion applications via
- * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
- * the services, maintaining the connection (the binding), and invoking callback methods such as
- * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
- * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
- * application process.
- *
- * <p>
- * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
- * utilized by {@link CompanionDeviceManagerService}):
- * <ul>
- * <li> {@link #bindCompanionApplication(int, String, boolean)}
- * <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
- * <li> {@link #isCompanionApplicationBound(int, String)}
- * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
- * </ul>
- *
- * @see CompanionDeviceService
- * @see android.companion.ICompanionDeviceService
- * @see CompanionDeviceServiceConnector
- */
-@SuppressLint("LongLogTag")
-public class CompanionApplicationController {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_CompanionApplicationController";
-
- private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
-
- private final @NonNull Context mContext;
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
- private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
-
- private final PowerManagerInternal mPowerManagerInternal;
-
- @GuardedBy("mBoundCompanionApplications")
- private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
- mBoundCompanionApplications;
- @GuardedBy("mScheduledForRebindingCompanionApplications")
- private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
-
- CompanionApplicationController(Context context, AssociationStore associationStore,
- ObservableUuidStore observableUuidStore,
- CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
- PowerManagerInternal powerManagerInternal) {
- mContext = context;
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mDevicePresenceMonitor = companionDevicePresenceMonitor;
- mPowerManagerInternal = powerManagerInternal;
- mCompanionServicesRegister = new CompanionServicesRegister();
- mBoundCompanionApplications = new AndroidPackageMap<>();
- mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
- }
-
- void onPackagesChanged(@UserIdInt int userId) {
- mCompanionServicesRegister.invalidate(userId);
- }
-
- /**
- * CDM binds to the companion app.
- */
- public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
- boolean isSelfManaged) {
- if (DEBUG) {
- Log.i(TAG, "bind() u" + userId + "/" + packageName
- + " isSelfManaged=" + isSelfManaged);
- }
-
- final List<ComponentName> companionServices =
- mCompanionServicesRegister.forPackage(userId, packageName);
- if (companionServices.isEmpty()) {
- Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
- + "eligible CompanionDeviceService not found.\n"
- + "A CompanionDeviceService should declare an intent-filter for "
- + "\"android.companion.CompanionDeviceService\" action and require "
- + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
- return;
- }
-
- final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
- synchronized (mBoundCompanionApplications) {
- if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
- return;
- }
-
- for (int i = 0; i < companionServices.size(); i++) {
- boolean isPrimary = i == 0;
- serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
- companionServices.get(i), isSelfManaged, isPrimary));
- }
-
- mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
- }
-
- // Set listeners for both Primary and Secondary connectors.
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.setListener(this::onBinderDied);
- }
-
- // Now "bind" all the connectors: the primary one and the rest of them.
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.connect();
- }
- }
-
- /**
- * CDM unbinds the companion app.
- */
- public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
- if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
-
- final List<CompanionDeviceServiceConnector> serviceConnectors;
-
- synchronized (mBoundCompanionApplications) {
- serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
-
- if (serviceConnectors == null) {
- if (DEBUG) {
- Log.e(TAG, "unbindCompanionApplication(): "
- + "u" + userId + "/" + packageName + " is NOT bound");
- Log.d(TAG, "Stacktrace", new Throwable());
- }
- return;
- }
-
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.postUnbind();
- }
- }
-
- /**
- * @return whether the companion application is bound now.
- */
- public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (mBoundCompanionApplications) {
- return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
- }
- }
-
- private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
- CompanionDeviceServiceConnector serviceConnector) {
- Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
-
- if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
- if (DEBUG) {
- Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
- + serviceConnector.getComponentName());
- }
- return;
- }
-
- if (serviceConnector.isPrimary()) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.setValueForPackage(
- userId, packageName, true);
- }
- }
-
- // Rebinding in 10 seconds.
- Handler.getMain().postDelayed(() ->
- onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
- REBIND_TIMEOUT);
- }
-
- private boolean isRebindingCompanionApplicationScheduled(
- @UserIdInt int userId, @NonNull String packageName) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- return mScheduledForRebindingCompanionApplications.containsValueForPackage(
- userId, packageName);
- }
- }
-
- private void onRebindingCompanionApplicationTimeout(
- @UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector) {
- // Re-mark the application is bound.
- if (serviceConnector.isPrimary()) {
- synchronized (mBoundCompanionApplications) {
- if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- List<CompanionDeviceServiceConnector> serviceConnectors =
- Collections.singletonList(serviceConnector);
- mBoundCompanionApplications.setValueForPackage(userId, packageName,
- serviceConnectors);
- }
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
- }
-
- serviceConnector.connect();
- }
-
- /**
- * Notify the app that the device appeared.
- *
- * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
- */
- @Deprecated
- public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
- + "/" + packageName);
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId() + "]");
-
- primaryServiceConnector.postOnDeviceAppeared(association);
- }
-
- /**
- * Notify the app that the device disappeared.
- *
- * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
- */
- @Deprecated
- public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
- + "/" + packageName);
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId() + "]");
-
- primaryServiceConnector.postOnDeviceDisappeared(association);
- }
-
- /**
- * Notify the app that the device appeared.
- */
- public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- final DevicePresenceEvent devicePresenceEvent =
- new DevicePresenceEvent(association.getId(), event, null);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
- + "u" + userId + "/" + packageName
- + " event=[ " + event + " ] is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId()
- + "] event=[" + event + "]");
-
- primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
- }
-
- /**
- * Notify the app that the device disappeared.
- */
- public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
- final int userId = uuid.getUserId();
- final ParcelUuid parcelUuid = uuid.getUuid();
- final String packageName = uuid.getPackageName();
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- final DevicePresenceEvent devicePresenceEvent =
- new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
- + "u" + userId + "/" + packageName
- + " event=[ " + event + " ] is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
- + packageName + "]" + "event= [" + event + "]");
-
- primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
- }
-
- void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Application Controller: \n");
-
- synchronized (mBoundCompanionApplications) {
- out.append(" Bound Companion Applications: ");
- if (mBoundCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mBoundCompanionApplications.dump(out);
- }
- }
-
- out.append(" Companion Applications Scheduled For Rebinding: ");
- if (mScheduledForRebindingCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mScheduledForRebindingCompanionApplications.dump(out);
- }
- }
-
- /**
- * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
- */
- private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector) {
-
- boolean isPrimary = serviceConnector.isPrimary();
- Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
-
- // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
- if (isPrimary) {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
-
- for (AssociationInfo association : associations) {
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
- break;
- }
- }
-
- synchronized (mBoundCompanionApplications) {
- mBoundCompanionApplications.removePackage(userId, packageName);
- }
- }
-
- // Second: schedule rebinding if needed.
- final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
-
- if (shouldScheduleRebind) {
- scheduleRebinding(userId, packageName, serviceConnector);
- }
- }
-
- private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
- @UserIdInt int userId, @NonNull String packageName) {
- final List<CompanionDeviceServiceConnector> connectors;
- synchronized (mBoundCompanionApplications) {
- connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
- }
- return connectors != null ? connectors.get(0) : null;
- }
-
- private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
- // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
- // app is uninstalled.
- boolean stillAssociated = false;
- // Make sure to clean up the state for all the associations
- // that associate with this package.
- boolean shouldScheduleRebind = false;
- boolean shouldScheduleRebindForUuid = false;
- final List<ObservableUuid> uuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
- final int associationId = ai.getId();
- stillAssociated = true;
- if (ai.isSelfManaged()) {
- // Do not rebind if primary one is died for selfManaged application.
- if (isPrimary
- && mDevicePresenceMonitor.isDevicePresent(associationId)) {
- mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
- shouldScheduleRebind = false;
- }
- // Do not rebind if both primary and secondary services are died for
- // selfManaged application.
- shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
- } else if (ai.isNotifyOnDeviceNearby()) {
- // Always rebind for non-selfManaged devices.
- shouldScheduleRebind = true;
- }
- }
-
- for (ObservableUuid uuid : uuids) {
- if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
- shouldScheduleRebindForUuid = true;
- break;
- }
- }
-
- return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
- }
-
- private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
- @Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
- @UserIdInt int userId) {
- return super.forUser(userId);
- }
-
- synchronized @NonNull List<ComponentName> forPackage(
- @UserIdInt int userId, @NonNull String packageName) {
- return forUser(userId).getOrDefault(packageName, Collections.emptyList());
- }
-
- synchronized void invalidate(@UserIdInt int userId) {
- remove(userId);
- }
-
- @Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
- return PackageUtils.getCompanionServicesForUser(mContext, userId);
- }
- }
-
- /**
- * Associates an Android package (defined by userId + packageName) with a value of type T.
- */
- private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
-
- void setValueForPackage(
- @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
- Map<String, T> forUser = get(userId);
- if (forUser == null) {
- forUser = /* Map<String, T> */ new HashMap();
- put(userId, forUser);
- }
-
- forUser.put(packageName, value);
- }
-
- boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, ?> forUser = get(userId);
- return forUser != null && forUser.containsKey(packageName);
- }
-
- T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- return forUser != null ? forUser.get(packageName) : null;
- }
-
- T removePackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- if (forUser == null) return null;
- return forUser.remove(packageName);
- }
-
- void dump() {
- if (size() == 0) {
- Log.d(TAG, "<empty>");
- return;
- }
-
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- Log.d(TAG, "u" + userId + ": <empty>");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
- }
- }
- }
-
- private void dump(@NonNull PrintWriter out) {
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- out.append(" u").append(String.valueOf(userId)).append(": <empty>\n");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- out.append(" u").append(String.valueOf(userId)).append("\\")
- .append(packageName).append(" -> ")
- .append(value.toString()).append('\n');
- }
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 712162b..edf9fd1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,15 +20,10 @@
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -42,13 +37,10 @@
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
-import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
@@ -64,7 +56,6 @@
import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
-import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -79,7 +70,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -91,7 +81,6 @@
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -118,7 +107,8 @@
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -131,10 +121,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
@SuppressLint("LongLogTag")
@@ -146,10 +133,6 @@
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
- private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
- "debug.cdm.cdmservice.removal_time_window";
-
- private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private static final int MAX_CN_LENGTH = 500;
private final ActivityTaskManagerInternal mAtmInternal;
@@ -165,8 +148,8 @@
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final CompanionApplicationController mCompanionAppController;
+ private final DevicePresenceProcessor mDevicePresenceProcessor;
+ private final CompanionAppBinder mCompanionAppBinder;
private final CompanionTransportManager mTransportManager;
private final DisassociationProcessor mDisassociationProcessor;
private final CrossDeviceSyncController mCrossDeviceSyncController;
@@ -185,7 +168,7 @@
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(userManager, associationDiskStore);
+ mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mObservableUuidStore = new ObservableUuidStore();
@@ -196,18 +179,17 @@
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
- mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
- mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
+ mCompanionAppBinder = new CompanionAppBinder(context);
- mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+ mDevicePresenceProcessor = new DevicePresenceProcessor(context,
+ mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
- mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
+ mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+ mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -242,7 +224,7 @@
// delays (even in case of the Main Thread). It may be fine overall, but would require
// updating the tests (adding a delay there).
mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
- mDevicePresenceMonitor.init(context);
+ mDevicePresenceProcessor.init(context);
} else if (phase == PHASE_BOOT_COMPLETED) {
// Run the Inactive Association Removal job service daily.
InactiveAssociationsRemovalService.schedule(getContext());
@@ -271,7 +253,7 @@
// Notify and bind the app after the phone is unlocked.
final int userId = user.getUserIdentifier();
final Set<BluetoothDevice> blueToothDevices =
- mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+ mDevicePresenceProcessor.getPendingConnectedDevices().get(userId);
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
@@ -287,14 +269,14 @@
mAssociationStore.getActiveAssociationsByAddress(
bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
- mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
+ mDevicePresenceProcessor.onBluetoothCompanionDeviceConnected(ai.getId());
}
for (ObservableUuid observableUuid : observableUuids) {
if (deviceUuids.contains(observableUuid.getUuid())) {
Slog.i(TAG, "onUserUnlocked, UUID( "
+ observableUuid.getUuid() + " ) is connected");
- mDevicePresenceMonitor.onDevicePresenceEventByUuid(
+ mDevicePresenceProcessor.onDevicePresenceEventByUuid(
observableUuid, EVENT_BT_CONNECTED);
}
}
@@ -302,181 +284,6 @@
}
}
- @NonNull
- AssociationInfo getAssociationWithCallerChecks(
- @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
- userId, packageName, macAddress);
- association = sanitizeWithCallerChecks(getContext(), association);
- if (association != null) {
- return association;
- } else {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
- }
-
- @NonNull
- AssociationInfo getAssociationWithCallerChecks(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- association = sanitizeWithCallerChecks(getContext(), association);
- if (association != null) {
- return association;
- } else {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
- }
-
- private void onDeviceAppearedInternal(int associationId) {
- if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- if (!association.shouldBindWhenPresent()) return;
-
- bindApplicationIfNeeded(association);
-
- mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association);
- }
-
- private void onDeviceDisappearedInternal(int associationId) {
- if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
-
- if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association);
- }
- }
-
- private void onDevicePresenceEventInternal(int associationId, int event) {
- Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- switch (event) {
- case EVENT_BLE_APPEARED:
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- if (!association.shouldBindWhenPresent()) return;
-
- bindApplicationIfNeeded(association);
-
- mCompanionAppController.notifyCompanionDevicePresenceEvent(
- association, event);
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
- if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionDevicePresenceEvent(
- association, event);
- }
- // Check if there are other devices associated to the app that are present.
- if (shouldBindPackage(userId, packageName)) return;
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- break;
- default:
- Slog.e(TAG, "Event: " + event + "is not supported");
- break;
- }
- }
-
- private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
- Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
- + "for package=" + uuid.getPackageName() + " event=" + event);
- final String packageName = uuid.getPackageName();
- final int userId = uuid.getUserId();
-
- switch (event) {
- case EVENT_BT_CONNECTED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppController.bindCompanionApplication(
- userId, packageName, /*bindImportant*/ false);
-
- } else if (DEBUG) {
- Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
- }
-
- mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
-
- break;
- case EVENT_BT_DISCONNECTED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
-
- mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
- // Check if there are other devices associated to the app or the UUID to be
- // observed are present.
- if (shouldBindPackage(userId, packageName)) return;
-
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
-
- break;
- default:
- Slog.e(TAG, "Event: " + event + "is not supported");
- break;
- }
- }
-
- private void bindApplicationIfNeeded(AssociationInfo association) {
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- // Set bindImportant to true when the association is self-managed to avoid the target
- // service being killed.
- final boolean bindImportant = association.isSelfManaged();
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppController.bindCompanionApplication(
- userId, packageName, bindImportant);
- } else if (DEBUG) {
- Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
- }
- }
-
- /**
- * @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present OR the UUID to be observed by this package is
- * currently present).
- */
- private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
- final List<AssociationInfo> packageAssociations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo association : packageAssociations) {
- if (!association.shouldBindWhenPresent()) continue;
- if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
- }
-
- for (ObservableUuid uuid : observableUuids) {
- if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
- return true;
- }
- }
-
- return false;
- }
-
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) {
@@ -502,7 +309,7 @@
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
- mCompanionAppController.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -515,34 +322,15 @@
association.getPackageName());
}
- mCompanionAppController.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
}
- // Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
- final long currentTime = System.currentTimeMillis();
- long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
- if (removalWindow <= 0) {
- // 0 or negative values indicate that the sysprop was never set or should be ignored.
- removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
- }
-
- for (AssociationInfo association : mAssociationStore.getAssociations()) {
- if (!association.isSelfManaged()) continue;
-
- final boolean isInactive =
- currentTime - association.getLastTimeConnectedMs() >= removalWindow;
- if (!isInactive) continue;
-
- final int id = association.getId();
-
- Slog.i(TAG, "Removing inactive self-managed association id=" + id);
- mDisassociationProcessor.disassociate(id);
- }
+ mDisassociationProcessor.removeIdleSelfManagedAssociations();
}
public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@@ -679,24 +467,15 @@
@Deprecated
@Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
- Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
- + ", macAddress=" + deviceMacAddress);
-
requireNonNull(deviceMacAddress);
requireNonNull(packageName);
- final AssociationInfo association =
- getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress);
}
@Override
public void disassociate(int associationId) {
- Slog.i(TAG, "disassociate() associationId=" + associationId);
-
- final AssociationInfo association =
- getAssociationWithCallerChecks(associationId);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(associationId);
}
@Override
@@ -758,21 +537,25 @@
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void registerDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- registerDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
+ public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStartObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceProcessor.startObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void unregisterDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- unregisterDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
+ public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStopObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceProcessor.stopObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
@@ -780,7 +563,8 @@
public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
startObservingDevicePresence_enforcePermission();
- registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+
+ mDevicePresenceProcessor.startObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -788,80 +572,8 @@
public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
stopObservingDevicePresence_enforcePermission();
- registerDevicePresenceListener(request, packageName, userId, /* active */ false);
- }
- private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
- String packageName, int userId, boolean active) {
- enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
- enforceCallerIsSystemOr(userId, packageName);
-
- final int associationId = request.getAssociationId();
- final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
- associationId);
- final ParcelUuid uuid = request.getUuid();
-
- if (uuid != null) {
- enforceCallerCanObservingDevicePresenceByUuid(getContext());
- if (active) {
- startObservingDevicePresenceByUuid(uuid, packageName, userId);
- } else {
- stopObservingDevicePresenceByUuid(uuid, packageName, userId);
- }
- } else if (associationInfo == null) {
- throw new IllegalArgumentException("App " + packageName
- + " is not associated with device " + request.getAssociationId()
- + " for user " + userId);
- } else {
- processDevicePresenceListener(
- associationInfo, userId, packageName, active);
- }
- }
-
- private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
- int userId) {
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (ObservableUuid observableUuid : observableUuids) {
- if (observableUuid.getUuid().equals(uuid)) {
- Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
- + "has been already scheduled for observing");
- return;
- }
- }
-
- final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
- packageName, System.currentTimeMillis());
-
- mObservableUuidStore.writeObservableUuid(userId, observableUuid);
- }
-
- private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
- int userId) {
- final List<ObservableUuid> uuidsTobeObserved =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
- boolean isScheduledObserving = false;
-
- for (ObservableUuid observableUuid : uuidsTobeObserved) {
- if (observableUuid.getUuid().equals(uuid)) {
- isScheduledObserving = true;
- break;
- }
- }
-
- if (!isScheduledObserving) {
- Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
- + "has NOT been scheduled for observing yet");
- return;
- }
-
- mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
- mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
-
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
+ mDevicePresenceProcessor.stopObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -874,8 +586,7 @@
@Override
public boolean isPermissionTransferUserConsented(String packageName, int userId,
int associationId) {
- return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
- userId, associationId);
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId);
}
@Override
@@ -891,8 +602,7 @@
ParcelFileDescriptor fd) {
attachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
+ mTransportManager.attachSystemDataTransport(associationId, fd);
}
@Override
@@ -900,161 +610,61 @@
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
detachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
- }
-
- @Override
- public void enableSystemDataSync(int associationId, int flags) {
- getAssociationWithCallerChecks(associationId);
- mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags);
- }
-
- @Override
- public void disableSystemDataSync(int associationId, int flags) {
- getAssociationWithCallerChecks(associationId);
- mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags);
- }
-
- @Override
- public void enablePermissionsSync(int associationId) {
- getAssociationWithCallerChecks(associationId);
- mSystemDataTransferProcessor.enablePermissionsSync(associationId);
- }
-
- @Override
- public void disablePermissionsSync(int associationId) {
- getAssociationWithCallerChecks(associationId);
- mSystemDataTransferProcessor.disablePermissionsSync(associationId);
- }
-
- @Override
- public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- // TODO: temporary fix, will remove soon
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- return null;
- }
- getAssociationWithCallerChecks(associationId);
- return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ mTransportManager.detachSystemDataTransport(associationId);
}
@Override
@EnforcePermission(MANAGE_COMPANION_DEVICES)
public void enableSecureTransport(boolean enabled) {
enableSecureTransport_enforcePermission();
+
mTransportManager.enableSecureTransport(enabled);
}
@Override
- public void notifyDeviceAppeared(int associationId) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
-
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association with ID " + associationId
- + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
- + " self-managed associations.");
- }
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // timestamp.
- association = (new AssociationInfo.Builder(association))
- .setLastTimeConnected(System.currentTimeMillis())
- .build();
- mAssociationStore.updateAssociation(association);
-
- mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
-
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
- }
+ public void enableSystemDataSync(int associationId, int flags) {
+ mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags);
}
@Override
- public void notifyDeviceDisappeared(int associationId) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
+ public void disableSystemDataSync(int associationId, int flags) {
+ mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags);
+ }
- final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association with ID " + associationId
- + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
- + " self-managed associations.");
- }
+ @Override
+ public void enablePermissionsSync(int associationId) {
+ mSystemDataTransferProcessor.enablePermissionsSync(associationId);
+ }
- mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
+ @Override
+ public void disablePermissionsSync(int associationId) {
+ mSystemDataTransferProcessor.disablePermissionsSync(associationId);
+ }
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
- }
+ @Override
+ public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+ }
+
+ @Override
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceAppeared(int associationId) {
+ notifySelfManagedDeviceAppeared_enforcePermission();
+
+ mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, true);
+ }
+
+ @Override
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceDisappeared(int associationId) {
+ notifySelfManagedDeviceDisappeared_enforcePermission();
+
+ mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, false);
}
@Override
public boolean isCompanionApplicationBound(String packageName, int userId) {
- return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
- }
-
- private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
- boolean active) throws RemoteException {
- if (DEBUG) {
- Log.i(TAG, "registerDevicePresenceListenerActive()"
- + " active=" + active
- + " deviceAddress=" + deviceAddress);
- }
- final int userId = getCallingUserId();
- enforceCallerIsSystemOr(userId, packageName);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
- userId, packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- processDevicePresenceListener(association, userId, packageName, active);
- }
-
- private void processDevicePresenceListener(AssociationInfo association,
- int userId, String packageName, boolean active) {
- // If already at specified state, then no-op.
- if (active == association.isNotifyOnDeviceNearby()) {
- if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
- return;
- }
-
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // flag.
- association = (new AssociationInfo.Builder(association))
- .setNotifyOnDeviceNearby(active)
- .build();
- // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will
- // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when
- // an application sets/unsets the mNotifyOnDeviceNearby flag.
- mAssociationStore.updateAssociation(association);
-
- int associationId = association.getId();
- // If device is already present, then trigger callback.
- if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) {
- Slog.i(TAG, "Device is already present. Triggering callback.");
- if (mDevicePresenceMonitor.isBlePresent(associationId)
- || mDevicePresenceMonitor.isSimulatePresent(associationId)) {
- onDeviceAppearedInternal(associationId);
- onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
- } else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
- onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
- }
- }
-
- // If last listener is unregistered, then unbind application.
- if (!active && !shouldBindPackage(userId, packageName)) {
- if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application.");
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
+ return mCompanionAppBinder.isCompanionApplicationBound(userId, packageName);
}
@Override
@@ -1070,7 +680,8 @@
}
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
- createNewAssociation(userId, packageName, macAddressObj, null, null, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
+ null, null, null, false, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -1099,9 +710,7 @@
@Override
public void setAssociationTag(int associationId, String tag) {
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
- mAssociationStore.updateAssociation(association);
+ mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
}
@Override
@@ -1124,7 +733,7 @@
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
- mAssociationStore, mDevicePresenceMonitor, mTransportManager,
+ mAssociationStore, mDevicePresenceProcessor, mTransportManager,
mSystemDataTransferProcessor, mAssociationRequestsProcessor,
mBackupRestoreProcessor, mDisassociationProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
@@ -1139,21 +748,13 @@
}
mAssociationStore.dump(out);
- mDevicePresenceMonitor.dump(out);
- mCompanionAppController.dump(out);
+ mDevicePresenceProcessor.dump(out);
+ mCompanionAppBinder.dump(out);
mTransportManager.dump(out);
mSystemDataTransferRequestStore.dump(out);
}
}
- void createNewAssociation(@UserIdInt int userId, @NonNull String packageName,
- @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
- @Nullable String deviceProfile, boolean isSelfManaged) {
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged,
- /* callback */ null, /* resultReceiver */ null);
- }
-
/**
* Update special access for the association's package
*/
@@ -1169,8 +770,6 @@
return;
}
- Slog.i(TAG, "Updating special access for package=[" + packageInfo.packageName + "]...");
-
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -1280,29 +879,6 @@
}
};
- private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
- new CompanionDevicePresenceMonitor.Callback() {
- @Override
- public void onDeviceAppeared(int associationId) {
- onDeviceAppearedInternal(associationId);
- }
-
- @Override
- public void onDeviceDisappeared(int associationId) {
- onDeviceDisappearedInternal(associationId);
- }
-
- @Override
- public void onDevicePresenceEvent(int associationId, int event) {
- onDevicePresenceEventInternal(associationId, event);
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
- onDevicePresenceEventByUuidInternal(uuid, event);
- }
- };
-
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -1315,7 +891,7 @@
}
@Override
- public void onPackageModified(String packageName) {
+ public void onPackageModified(@NonNull String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
@@ -1325,25 +901,15 @@
}
};
- private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
- final Map<String, Set<Integer>> copy = new HashMap<>();
-
- for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
- final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
- copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
- }
-
- return Collections.unmodifiableMap(copy);
- }
-
private static <T> boolean containsEither(T[] array, T a, T b) {
return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
}
private class LocalService implements CompanionDeviceManagerServiceInternal {
+
@Override
public void removeInactiveSelfManagedAssociations() {
- CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
+ mDisassociationProcessor.removeIdleSelfManagedAssociations();
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index cdf832f..9d1250d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -27,8 +27,9 @@
* Companion Device Manager Local System Service Interface.
*/
public interface CompanionDeviceManagerServiceInternal {
+
/**
- * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
+ * Remove idle self-managed associations.
*/
void removeInactiveSelfManagedAssociations();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a7a73cb..a789384 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,8 +18,6 @@
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
-import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
-
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -38,7 +36,7 @@
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -51,7 +49,7 @@
private final CompanionDeviceManagerService mService;
private final DisassociationProcessor mDisassociationProcessor;
private final AssociationStore mAssociationStore;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceProcessor;
private final CompanionTransportManager mTransportManager;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -60,7 +58,7 @@
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- CompanionDevicePresenceMonitor devicePresenceMonitor,
+ DevicePresenceProcessor devicePresenceProcessor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
@@ -68,7 +66,7 @@
DisassociationProcessor disassociationProcessor) {
mService = service;
mAssociationStore = associationStore;
- mDevicePresenceMonitor = devicePresenceMonitor;
+ mDevicePresenceProcessor = devicePresenceProcessor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
@@ -85,7 +83,7 @@
if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
associationId = getNextIntArgRequired();
int event = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
return 0;
}
@@ -97,7 +95,7 @@
ObservableUuid observableUuid = new ObservableUuid(
userId, ParcelUuid.fromString(uuid), packageName,
System.currentTimeMillis());
- mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+ mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
return 0;
}
@@ -124,8 +122,9 @@
String address = getNextArgRequired();
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
- mService.createNewAssociation(userId, packageName, macAddress,
- /* displayName= */ deviceProfile, deviceProfile, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null);
}
break;
@@ -134,8 +133,13 @@
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
- mService.getAssociationWithCallerChecks(userId, packageName, address);
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationStore.getFirstAssociationByAddress(userId, packageName,
+ address);
+ if (association == null) {
+ out.println("Association doesn't exist.");
+ } else {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
}
break;
@@ -144,9 +148,7 @@
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
- mDisassociationProcessor.disassociate(association.getId());
- }
+ mDisassociationProcessor.disassociate(association.getId());
}
}
break;
@@ -157,12 +159,12 @@
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
break;
case "get-backup-payload": {
@@ -410,10 +412,9 @@
pw.println(" Remove an existing Association.");
pw.println(" disassociate-all USER_ID");
pw.println(" Remove all Associations for a user.");
- pw.println(" clear-association-memory-cache");
+ pw.println(" refresh-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
- pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
- pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" information from disk. USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a02d9f9..a18776e 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -145,7 +145,8 @@
/**
* Handle incoming {@link AssociationRequest}s, sent via
- * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
+ * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest,
+ * IAssociationRequestCallback, String, int)}
*/
public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@@ -212,7 +213,8 @@
// 2b.4. Send the PendingIntent back to the app.
try {
callback.onAssociationPending(pendingIntent);
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
}
/**
@@ -252,7 +254,8 @@
// forward it back to the application via the callback.
try {
callback.onFailure(e.getMessage());
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
return;
}
@@ -322,7 +325,8 @@
* Enable system data sync.
*/
public void enableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build();
mAssociationStore.updateAssociation(updated);
@@ -332,12 +336,23 @@
* Disable system data sync.
*/
public void disableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() & (~flags)).build();
mAssociationStore.updateAssociation(updated);
}
+ /**
+ * Set association tag.
+ */
+ public void setAssociationTag(int associationId, String tag) {
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+ association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ mAssociationStore.updateAssociation(association);
+ }
+
private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -396,14 +411,14 @@
// If the application already has a pending association request, that PendingIntent
// will be cancelled except application wants to cancel the request by the system.
return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(
- mContext, /*requestCode */ packageUid, intent,
- FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- ActivityOptions.makeBasic()
- .setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .toBundle(),
- UserHandle.CURRENT)
+ PendingIntent.getActivityAsUser(
+ mContext, /*requestCode */ packageUid, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT)
);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index edebb55..ae2b708 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,6 +18,7 @@
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -26,6 +27,7 @@
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.IOnAssociationsChangedListener;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Binder;
@@ -57,21 +59,22 @@
@SuppressLint("LongLogTag")
public class AssociationStore {
- @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+ @IntDef(prefix = {"CHANGE_TYPE_"}, value = {
CHANGE_TYPE_ADDED,
CHANGE_TYPE_REMOVED,
CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ChangeType {}
+ public @interface ChangeType {
+ }
public static final int CHANGE_TYPE_ADDED = 0;
public static final int CHANGE_TYPE_REMOVED = 1;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
- /** Listener for any changes to associations. */
+ /** Listener for any changes to associations. */
public interface OnChangeListener {
/**
* Called when there are association changes.
@@ -100,25 +103,30 @@
/**
* Called when an association is added.
*/
- default void onAssociationAdded(AssociationInfo association) {}
+ default void onAssociationAdded(AssociationInfo association) {
+ }
/**
* Called when an association is removed.
*/
- default void onAssociationRemoved(AssociationInfo association) {}
+ default void onAssociationRemoved(AssociationInfo association) {
+ }
/**
* Called when an association is updated.
*/
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
+ }
}
private static final String TAG = "CDM_AssociationStore";
- private final Object mLock = new Object();
-
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final AssociationDiskStore mDiskStore;
private final ExecutorService mExecutor;
+ private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mPersisted = false;
@GuardedBy("mLock")
@@ -132,10 +140,9 @@
private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
new RemoteCallbackList<>();
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
-
- public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
+ public AssociationStore(Context context, UserManager userManager,
+ AssociationDiskStore diskStore) {
+ mContext = context;
mUserManager = userManager;
mDiskStore = diskStore;
mExecutor = Executors.newSingleThreadExecutor();
@@ -202,7 +209,7 @@
synchronized (mLock) {
if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association with id=[" + id + "] already exists.");
+ Slog.e(TAG, "Association id=[" + id + "] already exists.");
return;
}
@@ -449,6 +456,26 @@
}
/**
+ * Get association by id with caller checks.
+ */
+ @NonNull
+ public AssociationInfo getAssociationWithCallerChecks(int associationId) {
+ AssociationInfo association = getAssociationById(associationId);
+ if (association == null) {
+ throw new IllegalArgumentException(
+ "getAssociationWithCallerChecks() Association id=[" + associationId
+ + "] doesn't exist.");
+ }
+ if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
+ association.getPackageName())) {
+ return association;
+ }
+
+ throw new IllegalArgumentException(
+ "The caller can't interact with the association id=[" + associationId + "].");
+ }
+
+ /**
* Register a local listener for association changes.
*/
public void registerLocalListener(@NonNull OnChangeListener listener) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index ec897791..acf683d 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -22,6 +22,8 @@
import static com.android.internal.util.CollectionUtils.any;
import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -30,21 +32,27 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
-import com.android.server.companion.CompanionApplicationController;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
- * A class response for Association removal.
+ * This class responsible for disassociation.
*/
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
private static final String TAG = "CDM_DisassociationProcessor";
+
+ private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
+ "debug.cdm.cdmservice.removal_time_window";
+ private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+
@NonNull
private final Context mContext;
@NonNull
@@ -52,11 +60,11 @@
@NonNull
private final PackageManagerInternal mPackageManagerInternal;
@NonNull
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceMonitor;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
- private final CompanionApplicationController mCompanionAppController;
+ private final CompanionAppBinder mCompanionAppController;
@NonNull
private final CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -66,8 +74,8 @@
@NonNull ActivityManager activityManager,
@NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
- @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
- @NonNull CompanionApplicationController applicationController,
+ @NonNull DevicePresenceProcessor devicePresenceMonitor,
+ @NonNull CompanionAppBinder applicationController,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull CompanionTransportManager companionTransportManager) {
mContext = context;
@@ -89,11 +97,7 @@
public void disassociate(int id) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
- final AssociationInfo association = mAssociationStore.getAssociationById(id);
- if (association == null) {
- Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
- return;
- }
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -118,12 +122,12 @@
return;
}
- // Association cleanup.
- mAssociationStore.removeAssociation(association.getId());
- mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
-
// Detach transport if exists
- mTransportManager.detachSystemDataTransport(packageName, userId, id);
+ mTransportManager.detachSystemDataTransport(id);
+
+ // Association cleanup.
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
+ mAssociationStore.removeAssociation(association.getId());
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -143,10 +147,28 @@
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (!shouldStayBound) {
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ mCompanionAppController.unbindCompanionApp(userId, packageName);
}
}
+ /**
+ * @deprecated Use {@link #disassociate(int)} instead.
+ */
+ @Deprecated
+ public void disassociate(int userId, String packageName, String macAddress) {
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, macAddress);
+
+ if (association == null) {
+ throw new IllegalArgumentException(
+ "Association for mac address=[" + macAddress + "] doesn't exist");
+ }
+
+ mAssociationStore.getAssociationWithCallerChecks(association.getId());
+
+ disassociate(association.getId());
+ }
+
@SuppressLint("MissingPermission")
private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
return Binder.withCleanCallingIdentity(() -> {
@@ -163,7 +185,7 @@
() -> mActivityManager.addOnUidImportanceListener(
mOnPackageVisibilityChangeListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
Slog.e(TAG, "Failed to start listening to uid importance changes.");
}
}
@@ -179,6 +201,34 @@
}
/**
+ * Remove idle self-managed associations.
+ */
+ public void removeIdleSelfManagedAssociations() {
+ Slog.i(TAG, "Removing idle self-managed associations.");
+
+ final long currentTime = System.currentTimeMillis();
+ long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
+ if (removalWindow <= 0) {
+ // 0 or negative values indicate that the sysprop was never set or should be ignored.
+ removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
+ }
+
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
+ + "].");
+ disassociate(id);
+ }
+ }
+
+ /**
* An OnUidImportanceListener class which watches the importance of the packages.
* In this class, we ONLY interested in the importance of the running process is greater than
* {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index f287315..b509e71 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -30,7 +30,7 @@
import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up idle self-managed associations.
+ * A Job Service responsible for clean up self-managed associations if it's idle for 90 days.
*
* The job will be executed only if the device is charging and in idle mode due to the application
* will be killed if association/role are revoked. See {@link DisassociationProcessor}
@@ -45,10 +45,10 @@
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(TAG, "Execute the Association Removal job");
- // Special policy for selfManaged that need to revoke associations if the device
- // does not connect for 90 days.
+
LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
.removeInactiveSelfManagedAssociations();
+
jobFinished(params, false);
return true;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index c5ca0bf..9069689 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,7 +31,6 @@
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
-import android.companion.DeviceNotAssociatedException;
import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
@@ -56,7 +55,6 @@
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
-import com.android.server.companion.utils.PermissionsUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -120,28 +118,10 @@
}
/**
- * Resolve the requested association, throwing if the caller doesn't have
- * adequate permissions.
- */
- @NonNull
- private AssociationInfo resolveAssociation(String packageName, int userId,
- int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
- if (association == null) {
- throw new DeviceNotAssociatedException("Association "
- + associationId + " is not associated with the app " + packageName
- + " for user " + userId);
- }
- return association;
- }
-
- /**
* Return whether the user has consented to the permission transfer for the association.
*/
- public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId,
- int associationId) {
- resolveAssociation(packageName, userId, associationId);
+ public boolean isPermissionTransferUserConsented(int associationId) {
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
@@ -167,7 +147,8 @@
return null;
}
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
@@ -207,7 +188,7 @@
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
// Check if the request has been consented by the user.
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
@@ -239,24 +220,20 @@
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(true);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
}
/**
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(false);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
}
/**
@@ -264,18 +241,17 @@
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- return Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- List<SystemDataTransferRequest> requests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- for (SystemDataTransferRequest request : requests) {
- if (request instanceof PermissionSyncRequest) {
- return (PermissionSyncRequest) request;
- }
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
+ .getUserId();
+ List<SystemDataTransferRequest> requests =
+ mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+ associationId);
+ for (SystemDataTransferRequest request : requests) {
+ if (request instanceof PermissionSyncRequest) {
+ return (PermissionSyncRequest) request;
}
- return null;
- });
+ }
+ return null;
}
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index c89ce11..9c37881 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -33,7 +33,7 @@
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index cb363a7..2d345c4 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -19,7 +19,7 @@
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import android.annotation.NonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
new file mode 100644
index 0000000..b6348ea
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.presence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.PerUser;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.utils.PackageUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages communication with companion applications via
+ * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
+ * the services, maintaining the connection (the binding), and invoking callback methods such as
+ * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
+ * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
+ *
+ * <p>
+ * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be
+ * utilized by {@link CompanionDeviceManagerService}):
+ * <ul>
+ * <li> {@link #bindCompanionApp(int, String, boolean, CompanionServiceConnector.Listener)}
+ * <li> {@link #unbindCompanionApp(int, String)}
+ * <li> {@link #isCompanionApplicationBound(int, String)}
+ * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
+ * </ul>
+ *
+ * @see CompanionDeviceService
+ * @see android.companion.ICompanionDeviceService
+ * @see CompanionServiceConnector
+ */
+@SuppressLint("LongLogTag")
+public class CompanionAppBinder {
+ private static final String TAG = "CDM_CompanionAppBinder";
+
+ private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final CompanionServicesRegister mCompanionServicesRegister;
+
+ @NonNull
+ @GuardedBy("mBoundCompanionApplications")
+ private final Map<Pair<Integer, String>, List<CompanionServiceConnector>>
+ mBoundCompanionApplications;
+ @NonNull
+ @GuardedBy("mScheduledForRebindingCompanionApplications")
+ private final Set<Pair<Integer, String>> mScheduledForRebindingCompanionApplications;
+
+ public CompanionAppBinder(@NonNull Context context) {
+ mContext = context;
+ mCompanionServicesRegister = new CompanionServicesRegister();
+ mBoundCompanionApplications = new HashMap<>();
+ mScheduledForRebindingCompanionApplications = new HashSet<>();
+ }
+
+ /**
+ * On package changed.
+ */
+ public void onPackagesChanged(@UserIdInt int userId) {
+ mCompanionServicesRegister.invalidate(userId);
+ }
+
+ /**
+ * CDM binds to the companion app.
+ */
+ public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName,
+ boolean isSelfManaged, CompanionServiceConnector.Listener listener) {
+ Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=["
+ + isSelfManaged + "]...");
+
+ final List<ComponentName> companionServices =
+ mCompanionServicesRegister.forPackage(userId, packageName);
+ if (companionServices.isEmpty()) {
+ Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
+ + "eligible CompanionDeviceService not found.\n"
+ + "A CompanionDeviceService should declare an intent-filter for "
+ + "\"android.companion.CompanionDeviceService\" action and require "
+ + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
+ return;
+ }
+
+ final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>();
+ synchronized (mBoundCompanionApplications) {
+ if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
+ Slog.w(TAG, "The package is ALREADY bound.");
+ return;
+ }
+
+ for (int i = 0; i < companionServices.size(); i++) {
+ boolean isPrimary = i == 0;
+ serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId,
+ companionServices.get(i), isSelfManaged, isPrimary));
+ }
+
+ mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors);
+ }
+
+ // Set listeners for both Primary and Secondary connectors.
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.setListener(listener);
+ }
+
+ // Now "bind" all the connectors: the primary one and the rest of them.
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.connect();
+ }
+ }
+
+ /**
+ * CDM unbinds the companion app.
+ */
+ public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) {
+ Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]...");
+
+ final List<CompanionServiceConnector> serviceConnectors;
+
+ synchronized (mBoundCompanionApplications) {
+ serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ if (serviceConnectors == null) {
+ Slog.e(TAG, "The package is not bound.");
+ return;
+ }
+
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.postUnbind();
+ }
+ }
+
+ /**
+ * @return whether the companion application is bound now.
+ */
+ public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ return mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName));
+ }
+ }
+
+ /**
+ * Remove bound apps for package.
+ */
+ public void removePackage(int userId, String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+ }
+
+ /**
+ * Schedule rebinding for the package.
+ */
+ public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
+ CompanionServiceConnector serviceConnector) {
+ Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
+
+ if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
+ Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
+ + serviceConnector.getComponentName());
+ return;
+ }
+
+ if (serviceConnector.isPrimary()) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName));
+ }
+ }
+
+ // Rebinding in 10 seconds.
+ Handler.getMain().postDelayed(() ->
+ onRebindingCompanionApplicationTimeout(userId, packageName,
+ serviceConnector),
+ REBIND_TIMEOUT);
+ }
+
+ private boolean isRebindingCompanionApplicationScheduled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ return mScheduledForRebindingCompanionApplications.contains(
+ new Pair<>(userId, packageName));
+ }
+ }
+
+ private void onRebindingCompanionApplicationTimeout(
+ @UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionServiceConnector serviceConnector) {
+ // Re-mark the application is bound.
+ if (serviceConnector.isPrimary()) {
+ synchronized (mBoundCompanionApplications) {
+ if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
+ List<CompanionServiceConnector> serviceConnectors =
+ Collections.singletonList(serviceConnector);
+ mBoundCompanionApplications.put(new Pair<>(userId, packageName),
+ serviceConnectors);
+ }
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+ }
+
+ serviceConnector.connect();
+ }
+
+ /**
+ * Dump bound apps.
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Application Controller: \n");
+
+ synchronized (mBoundCompanionApplications) {
+ out.append(" Bound Companion Applications: ");
+ if (mBoundCompanionApplications.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry :
+ mBoundCompanionApplications.entrySet()) {
+ out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ")
+ .append(entry.getKey().second).append(">");
+ for (CompanionServiceConnector serviceConnector : entry.getValue()) {
+ out.append(", isPrimary=").append(
+ String.valueOf(serviceConnector.isPrimary()));
+ }
+ }
+ }
+ }
+
+ out.append(" Companion Applications Scheduled For Rebinding: ");
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ if (mScheduledForRebindingCompanionApplications.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) {
+ out.append("<u").append(String.valueOf(app.first)).append(", ")
+ .append(app.second).append(">");
+ }
+ }
+ }
+ }
+
+ @Nullable
+ CompanionServiceConnector getPrimaryServiceConnector(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<CompanionServiceConnector> connectors;
+ synchronized (mBoundCompanionApplications) {
+ connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName));
+ }
+ return connectors != null ? connectors.get(0) : null;
+ }
+
+ private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
+ @Override
+ public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @UserIdInt int userId) {
+ return super.forUser(userId);
+ }
+
+ synchronized @NonNull List<ComponentName> forPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return forUser(userId).getOrDefault(packageName, Collections.emptyList());
+ }
+
+ synchronized void invalidate(@UserIdInt int userId) {
+ remove(userId);
+ }
+
+ @Override
+ protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ return PackageUtils.getCompanionServicesForUser(mContext, userId);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
deleted file mode 100644
index 7a1a83f..0000000
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ /dev/null
@@ -1,620 +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.companion.presence;
-
-import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.association.AssociationStore;
-
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Class responsible for monitoring companion devices' "presence" status (i.e.
- * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
- *
- * <p>
- * Should only be used by
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * to which it provides the following API:
- * <ul>
- * <li> {@link #onSelfManagedDeviceConnected(int)}
- * <li> {@link #onSelfManagedDeviceDisconnected(int)}
- * <li> {@link #isDevicePresent(int)}
- * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
- * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
- * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
- * </ul>
- */
-@SuppressLint("LongLogTag")
-public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_CompanionDevicePresenceMonitor";
-
- /** Callback for notifying about changes to status of companion devices. */
- public interface Callback {
- /** Invoked when companion device is found nearby or connects. */
- void onDeviceAppeared(int associationId);
-
- /** Invoked when a companion device no longer seen nearby or disconnects. */
- void onDeviceDisappeared(int associationId);
-
- /** Invoked when device has corresponding event changes. */
- void onDevicePresenceEvent(int associationId, int event);
-
- /** Invoked when device has corresponding event changes base on the UUID */
- void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
- }
-
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
- private final @NonNull Callback mCallback;
- private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
- private final @NonNull BleCompanionDeviceScanner mBleScanner;
-
- // NOTE: Same association may appear in more than one of the following sets at the same time.
- // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
- // companion applications, while at the same be connected via BT, or detected nearby by BLE
- // scanner)
- private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
- private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
- private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
- private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>();
-
- // A map to track device presence within 10 seconds of Bluetooth disconnection.
- // The key is the association ID, and the boolean value indicates if the device
- // was detected again within that time frame.
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
- new SparseBooleanArray();
-
- // Tracking "simulated" presence. Used for debugging and testing only.
- private final @NonNull Set<Integer> mSimulated = new HashSet<>();
- private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
- new SimulatedDevicePresenceSchedulerHelper();
-
- private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
- new BleDeviceDisappearedScheduler();
-
- public CompanionDevicePresenceMonitor(UserManager userManager,
- @NonNull AssociationStore associationStore,
- @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mCallback = callback;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
- }
-
- /** Initialize {@link CompanionDevicePresenceMonitor} */
- public void init(Context context) {
- if (DEBUG) Log.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
- Log.w(TAG, "BluetoothAdapter is NOT available.");
- }
-
- mAssociationStore.registerLocalListener(this);
- }
-
- /**
- * @return current connected UUID devices.
- */
- public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
- return mConnectedUuidDevices;
- }
-
- /**
- * Remove current connected UUID device.
- */
- public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
- mConnectedUuidDevices.remove(uuid);
- }
-
- /**
- * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
- * or devices is connected (for Bluetooth); or reported (by the application) to be
- * nearby (for "self-managed" associations).
- */
- public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
- || mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId)
- || mSimulated.contains(associationId);
- }
-
- /**
- * @return whether the current uuid to be observed is present.
- */
- public boolean isDeviceUuidPresent(ParcelUuid uuid) {
- return mConnectedUuidDevices.contains(uuid);
- }
-
- /**
- * @return whether the current device is BT connected and had already reported to the app.
- */
-
- public boolean isBtConnected(int associationId) {
- return mConnectedBtDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device in BLE range and had already reported to the app.
- */
- public boolean isBlePresent(int associationId) {
- return mNearbyBleDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device had been already reported by the simulator.
- */
- public boolean isSimulatePresent(int associationId) {
- return mSimulated.contains(associationId);
- }
-
- /**
- * Marks a "self-managed" device as connected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
- */
- public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_APPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
- */
- public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected when binderDied.
- */
- public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- @Override
- public void onBluetoothCompanionDeviceConnected(int associationId) {
- synchronized (mBtDisconnectedDevices) {
- // A device is considered reconnected within 10 seconds if a pending BLE lost report is
- // followed by a detected Bluetooth connection.
- boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
- if (isReconnected) {
- Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
-
- Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
- + "associationId( " + associationId + " )");
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
-
- // Stop the BLE scan if all devices report BT connected status and BLE was present.
- if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
- }
-
- }
- }
-
- @Override
- public void onBluetoothCompanionDeviceDisconnected(int associationId) {
- Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
- + "associationId( " + associationId + " )");
- // Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
-
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
- // If current device is BLE present but BT is disconnected , means it will be
- // potentially out of range later. Schedule BLE disappeared callback.
- if (isBlePresent(associationId)) {
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.add(associationId);
- }
- mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
- }
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
- final ParcelUuid parcelUuid = uuid.getUuid();
-
- switch(event) {
- case EVENT_BT_CONNECTED:
- boolean added = mConnectedUuidDevices.add(parcelUuid);
-
- if (!added) {
- Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
- + "present by this event=" + event);
- }
-
- break;
- case EVENT_BT_DISCONNECTED:
- final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
-
- if (!removed) {
- Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
- + "as present by this event= " + event);
-
- return;
- }
-
- break;
- }
-
- mCallback.onDevicePresenceEventByUuid(uuid, event);
- }
-
-
- @Override
- public void onBleCompanionDeviceFound(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
- if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
- }
- }
-
- @Override
- public void onBleCompanionDeviceLost(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEvent(int associationId, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- // Make sure the association exists.
- enforceAssociationExists(associationId);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, event);
- break;
- case EVENT_BT_CONNECTED:
- onBluetoothCompanionDeviceConnected(associationId);
- break;
- case EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, event);
- break;
- case EVENT_BT_DISCONNECTED:
- onBluetoothCompanionDeviceDisconnected(associationId);
- break;
- default:
- throw new IllegalArgumentException("Event: " + event + "is not supported");
- }
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- onDevicePresenceEventByUuid(uuid, event);
- }
-
- private void simulateDeviceAppeared(int associationId, int state) {
- onDevicePresenceEvent(mSimulated, associationId, state);
- mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- }
-
- private void simulateDeviceDisappeared(int associationId, int state) {
- mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDevicePresenceEvent(mSimulated, associationId, state);
- }
-
- private void enforceAssociationExists(int associationId) {
- if (mAssociationStore.getAssociationById(associationId) == null) {
- throw new IllegalArgumentException(
- "Association with id " + associationId + " does not exist.");
- }
- }
-
- private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
- int associationId, int event) {
- Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- synchronized (mBtDisconnectedDevices) {
- // If a BLE device is detected within 10 seconds after BT is disconnected,
- // flag it as BLE is present.
- if (mBtDisconnectedDevices.contains(associationId)) {
- Slog.i(TAG, "Device ( " + associationId + " ) is present,"
- + " do not need to send the callback with event ( "
- + EVENT_BLE_APPEARED + " ).");
- mBtDisconnectedDevicesBlePresence.append(associationId, true);
- }
- }
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- final boolean added = presentDevicesForSource.add(associationId);
-
- if (!added) {
- Slog.w(TAG, "Association with id "
- + associationId + " is ALREADY reported as "
- + "present by this source, event=" + event);
- }
-
- mCallback.onDeviceAppeared(associationId);
-
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- final boolean removed = presentDevicesForSource.remove(associationId);
-
- if (!removed) {
- Slog.w(TAG, "Association with id " + associationId + " was NOT reported "
- + "as present by this source, event= " + event);
-
- return;
- }
-
- mCallback.onDeviceDisappeared(associationId);
-
- break;
- default:
- Slog.e(TAG, "Event: " + event + " is not supported");
- return;
- }
-
- mCallback.onDevicePresenceEvent(associationId, event);
- }
-
- /**
- * Implements
- * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
- */
- @Override
- public void onAssociationRemoved(@NonNull AssociationInfo association) {
- final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
-
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
- mSimulated.remove(id);
- mBtDisconnectedDevices.remove(id);
- mBtDisconnectedDevicesBlePresence.delete(id);
-
- // Do NOT call mCallback.onDeviceDisappeared()!
- // CompanionDeviceManagerService will know that the association is removed, and will do
- // what's needed.
- }
-
- /**
- * Return a set of devices that pending to report connectivity
- */
- public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingConnectedDevices) {
- return mBtConnectionListener.mPendingConnectedDevices;
- }
- }
-
- private static void enforceCallerShellOrRoot() {
- final int callingUid = Binder.getCallingUid();
- if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
-
- throw new SecurityException("Caller is neither Shell nor Root");
- }
-
- /**
- * The BLE scan can be only stopped if all the devices have been reported
- * BT connected and BLE presence and are not pending to report BLE lost.
- */
- private boolean canStopBleScan() {
- for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
- int id = ai.getId();
- synchronized (mBtDisconnectedDevices) {
- if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
- && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
- Slog.i(TAG, "The BLE scan cannot be stopped, "
- + "device( " + id + " ) is not yet connected "
- + "OR the BLE is not current present Or is pending to report BLE lost");
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Dumps system information about devices that are marked as "present".
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Present: ");
- if (mConnectedBtDevices.isEmpty()
- && mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- return;
- } else {
- out.append("\n");
- }
-
- out.append(" Connected Bluetooth Devices: ");
- if (mConnectedBtDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mConnectedBtDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Nearby BLE Devices: ");
- if (mNearbyBleDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mNearbyBleDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
- }
-
- private class SimulatedDevicePresenceSchedulerHelper extends Handler {
- SimulatedDevicePresenceSchedulerHelper() {
- super(Looper.getMainLooper());
- }
-
- void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- // First, unschedule if it was scheduled previously.
- if (hasMessages(/* what */ associationId)) {
- removeMessages(/* what */ associationId);
- }
-
- sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
- }
-
- void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- removeMessages(/* what */ associationId);
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- if (mSimulated.contains(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
- }
- }
- }
-
- private class BleDeviceDisappearedScheduler extends Handler {
- BleDeviceDisappearedScheduler() {
- super(Looper.getMainLooper());
- }
-
- void scheduleBleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- removeMessages(associationId);
- }
- Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
- sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
- }
-
- void unScheduleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
-
- removeMessages(associationId);
- }
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
- associationId);
- // If a device hasn't reported after 10 seconds and is not currently present,
- // assume BLE is lost and trigger the onDeviceEvent callback with the
- // EVENT_BLE_DISAPPEARED event.
- if (mBtDisconnectedDevices.contains(associationId)
- && !isCurrentPresent) {
- Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
- + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
similarity index 83%
rename from services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
rename to services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
index 5abdb42..c01c319 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.presence;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
@@ -33,36 +33,42 @@
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
import com.android.server.ServiceThread;
+import com.android.server.companion.CompanionDeviceManagerService;
/**
* Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
* application process.
*/
@SuppressLint("LongLogTag")
-class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
+public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
+
+ /** Listener for changes to the state of the {@link CompanionServiceConnector} */
+ public interface Listener {
+ /**
+ * Called when service binding is died.
+ */
+ void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionServiceConnector serviceConnector);
+ }
+
private static final String TAG = "CDM_CompanionServiceConnector";
- private static final boolean DEBUG = false;
/* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
private static final long UNBIND_POST_DELAY_MS = 5_000;
-
- /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
- interface Listener {
- void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector);
- }
-
- private final @UserIdInt int mUserId;
- private final @NonNull ComponentName mComponentName;
+ @UserIdInt
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ private final boolean mIsPrimary;
// IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only
// installs a listener to the primary ServiceConnector), hence we should always null-check the
// reference before calling on it.
- private @Nullable Listener mListener;
- private boolean mIsPrimary;
+ @Nullable
+ private Listener mListener;
/**
* Create a CompanionDeviceServiceConnector instance.
@@ -79,16 +85,16 @@
* IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
* service importance level should be higher than 125.
*/
- static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
+ static CompanionServiceConnector newInstance(@NonNull Context context,
@UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
boolean isPrimary) {
final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
: BIND_ALMOST_PERCEPTIBLE;
- return new CompanionDeviceServiceConnector(
+ return new CompanionServiceConnector(
context, userId, componentName, bindingFlags, isPrimary);
}
- private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId,
@NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
super(context, buildIntent(componentName), bindingFlags, userId, null);
mUserId = userId;
@@ -133,6 +139,7 @@
return mIsPrimary;
}
+ @NonNull
ComponentName getComponentName() {
return mComponentName;
}
@@ -140,17 +147,15 @@
@Override
protected void onServiceConnectionStatusChanged(
@NonNull ICompanionDeviceService service, boolean isConnected) {
- if (DEBUG) {
- Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
- + " connected=" + isConnected);
- }
+ Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
}
@Override
public void binderDied() {
super.binderDied();
- if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
+ Slog.d(TAG, "binderDied() " + mComponentName.toShortString());
// Handle primary process being killed
if (mListener != null) {
@@ -172,7 +177,8 @@
* within system_server and thus tends to get heavily congested)
*/
@Override
- protected @NonNull Handler getJobHandler() {
+ @NonNull
+ protected Handler getJobHandler() {
return getServiceThread().getThreadHandler();
}
@@ -182,12 +188,14 @@
return -1;
}
- private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
+ @NonNull
+ private static Intent buildIntent(@NonNull ComponentName componentName) {
return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
.setComponent(componentName);
}
- private static @NonNull ServiceThread getServiceThread() {
+ @NonNull
+ private static ServiceThread getServiceThread() {
if (sServiceThread == null) {
synchronized (CompanionDeviceManagerService.class) {
if (sServiceThread == null) {
@@ -206,5 +214,6 @@
* <p>
* Do NOT reference directly, use {@link #getServiceThread()} method instead.
*/
- private static volatile @Nullable ServiceThread sServiceThread;
+ @Nullable
+ private static volatile ServiceThread sServiceThread;
}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
new file mode 100644
index 0000000..642460e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
@@ -0,0 +1,1042 @@
+/*
+ * 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.companion.presence;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationInfo;
+import android.companion.DeviceNotAssociatedException;
+import android.companion.DevicePresenceEvent;
+import android.companion.ObservingDevicePresenceRequest;
+import android.content.Context;
+import android.hardware.power.Mode;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.association.AssociationStore;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_DevicePresenceProcessor";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final CompanionAppBinder mCompanionAppBinder;
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final ObservableUuidStore mObservableUuidStore;
+ @NonNull
+ private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ @NonNull
+ private final BleCompanionDeviceScanner mBleScanner;
+ @NonNull
+ private final PowerManagerInternal mPowerManagerInternal;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ @NonNull
+ private final Set<Integer> mConnectedBtDevices = new HashSet<>();
+ @NonNull
+ private final Set<Integer> mNearbyBleDevices = new HashSet<>();
+ @NonNull
+ private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ @NonNull
+ private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
+ @NonNull
+ @GuardedBy("mBtDisconnectedDevices")
+ private final Set<Integer> mBtDisconnectedDevices = new HashSet<>();
+
+ // A map to track device presence within 10 seconds of Bluetooth disconnection.
+ // The key is the association ID, and the boolean value indicates if the device
+ // was detected again within that time frame.
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
+ new SparseBooleanArray();
+
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
+ private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
+ new BleDeviceDisappearedScheduler();
+
+ public DevicePresenceProcessor(@NonNull Context context,
+ @NonNull CompanionAppBinder companionAppBinder,
+ UserManager userManager,
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore,
+ @NonNull PowerManagerInternal powerManagerInternal) {
+ mContext = context;
+ mCompanionAppBinder = companionAppBinder;
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ mPowerManagerInternal = powerManagerInternal;
+ }
+
+ /** Initialize {@link DevicePresenceProcessor} */
+ public void init(Context context) {
+ if (DEBUG) Slog.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Slog.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerLocalListener(this);
+ }
+
+ /**
+ * Process device presence start request.
+ */
+ public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ Slog.i(TAG,
+ "Start observing request=[" + request + "] for userId=[" + userId + "], package=["
+ + packageName + "]...");
+ final ParcelUuid requestUuid = request.getUuid();
+
+ if (requestUuid != null) {
+ enforceCallerCanObserveDevicePresenceByUuid(mContext);
+
+ // If it's already being observed, then no-op.
+ if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
+ Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
+ + userId + "] is already being observed.");
+ return;
+ }
+
+ final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid,
+ packageName, System.currentTimeMillis());
+ mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+ } else {
+ final int associationId = request.getAssociationId();
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+
+ // If it's already being observed, then no-op.
+ if (association.isNotifyOnDeviceNearby()) {
+ Slog.i(TAG, "Associated device id=[" + association.getId()
+ + "] is already being observed. No-op.");
+ return;
+ }
+
+ association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true)
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ // Send callback immediately if the device is present.
+ if (isDevicePresent(associationId)) {
+ Slog.i(TAG, "Device is already present. Triggering callback.");
+ if (isBlePresent(associationId)) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ } else if (isBtConnected(associationId)) {
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+ } else if (isSimulatePresent(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED);
+ }
+ }
+ }
+
+ Slog.i(TAG, "Registered device presence listener.");
+ }
+
+ /**
+ * Process device presence stop request.
+ */
+ public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ Slog.i(TAG,
+ "Stop observing request=[" + request + "] for userId=[" + userId + "], package=["
+ + packageName + "]...");
+
+ final ParcelUuid requestUuid = request.getUuid();
+
+ if (requestUuid != null) {
+ enforceCallerCanObserveDevicePresenceByUuid(mContext);
+
+ if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
+ Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
+ + userId + "] is already not being observed.");
+ return;
+ }
+
+ mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName);
+ removeCurrentConnectedUuidDevice(requestUuid);
+ } else {
+ final int associationId = request.getAssociationId();
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+
+ // If it's already being observed, then no-op.
+ if (!association.isNotifyOnDeviceNearby()) {
+ Slog.i(TAG, "Associated device id=[" + association.getId()
+ + "] is already not being observed. No-op.");
+ return;
+ }
+
+ association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false)
+ .build();
+ mAssociationStore.updateAssociation(association);
+ }
+
+ Slog.i(TAG, "Unregistered device presence listener.");
+
+ // If last listener is unregistered, then unbind application.
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ }
+
+ /**
+ * For legacy device presence below Android V.
+ *
+ * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
+ * int)}
+ */
+ @Deprecated
+ public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
+ throws RemoteException {
+ Slog.i(TAG,
+ "Start observing device=[" + deviceAddress + "] for userId=[" + userId
+ + "], package=["
+ + packageName + "]...");
+
+ enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " for user " + userId));
+ }
+
+ startObservingDevicePresence(
+ new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
+ .build(), packageName, userId);
+ }
+
+ /**
+ * For legacy device presence below Android V.
+ *
+ * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
+ * int)}
+ */
+ @Deprecated
+ public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
+ throws RemoteException {
+ Slog.i(TAG,
+ "Stop observing device=[" + deviceAddress + "] for userId=[" + userId
+ + "], package=["
+ + packageName + "]...");
+
+ enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " for user " + userId));
+ }
+
+ stopObservingDevicePresence(
+ new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
+ .build(), packageName, userId);
+ }
+
+ /**
+ * @return whether the package should be bound (i.e. at least one of the devices associated with
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
+ */
+ private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final List<AssociationInfo> packageAssociations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo association : packageAssociations) {
+ if (!association.shouldBindWhenPresent()) continue;
+ if (isDevicePresent(association.getId())) return true;
+ }
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (isDeviceUuidPresent(uuid.getUuid())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind the system to the app if it's not bound.
+ *
+ * Set bindImportant to true when the association is self-managed to avoid the target service
+ * being killed.
+ */
+ private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) {
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppBinder.bindCompanionApp(
+ userId, packageName, bindImportant, this::onBinderDied);
+ } else {
+ Slog.i(TAG,
+ "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound.");
+ }
+ }
+
+ /**
+ * @return current connected UUID devices.
+ */
+ public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+ return mConnectedUuidDevices;
+ }
+
+ /**
+ * Remove current connected UUID device.
+ */
+ public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+ mConnectedUuidDevices.remove(uuid);
+ }
+
+ /**
+ * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+ * or devices is connected (for Bluetooth); or reported (by the application) to be
+ * nearby (for "self-managed" associations).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
+ }
+
+ /**
+ * @return whether the current uuid to be observed is present.
+ */
+ public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+ return mConnectedUuidDevices.contains(uuid);
+ }
+
+ /**
+ * @return whether the current device is BT connected and had already reported to the app.
+ */
+
+ public boolean isBtConnected(int associationId) {
+ return mConnectedBtDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device in BLE range and had already reported to the app.
+ */
+ public boolean isBlePresent(int associationId) {
+ return mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device had been already reported by the simulator.
+ */
+ public boolean isSimulatePresent(int associationId) {
+ return mSimulated.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService
+ * CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int)
+ * notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService
+ * CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int)
+ * notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected when binderDied.
+ */
+ public void onSelfManagedDeviceReporterBinderDied(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ synchronized (mBtDisconnectedDevices) {
+ // A device is considered reconnected within 10 seconds if a pending BLE lost report is
+ // followed by a detected Bluetooth connection.
+ boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
+ if (isReconnected) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+
+ Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ + "associationId( " + associationId + " )");
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+
+ // Stop the BLE scan if all devices report BT connected status and BLE was present.
+ if (canStopBleScan()) {
+ mBleScanner.stopScanIfNeeded();
+ }
+
+ }
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
+ + "associationId( " + associationId + " )");
+ // Start BLE scanning when the device is disconnected.
+ mBleScanner.startScan();
+
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
+ // If current device is BLE present but BT is disconnected , means it will be
+ // potentially out of range later. Schedule BLE disappeared callback.
+ if (isBlePresent(associationId)) {
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.add(associationId);
+ }
+ mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
+ }
+ }
+
+
+ @Override
+ public void onBleCompanionDeviceFound(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
+ if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+ }
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEvent(int associationId, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
+ break;
+ case EVENT_BT_CONNECTED:
+ onBluetoothCompanionDeviceConnected(associationId);
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ onBluetoothCompanionDeviceDisconnected(associationId);
+ break;
+ default:
+ throw new IllegalArgumentException("Event: " + event + "is not supported");
+ }
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDevicePresenceEventByUuid(uuid, event);
+ }
+
+ private void simulateDeviceAppeared(int associationId, int state) {
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ private void simulateDeviceDisappeared(int associationId, int state) {
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ int associationId, int eventType) {
+ Slog.i(TAG,
+ "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]...");
+
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (association == null) {
+ Slog.e(TAG, "Association doesn't exist.");
+ return;
+ }
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null);
+
+ if (eventType == EVENT_BLE_APPEARED) {
+ synchronized (mBtDisconnectedDevices) {
+ // If a BLE device is detected within 10 seconds after BT is disconnected,
+ // flag it as BLE is present.
+ if (mBtDisconnectedDevices.contains(associationId)) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is present,"
+ + " do not need to send the callback with event ( "
+ + EVENT_BLE_APPEARED + " ).");
+ mBtDisconnectedDevicesBlePresence.append(associationId, true);
+ }
+ }
+ }
+
+ switch (eventType) {
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ final boolean added = presentDevicesForSource.add(associationId);
+ if (!added) {
+ Slog.w(TAG, "The association is already present.");
+ }
+
+ if (association.shouldBindWhenPresent()) {
+ bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
+ } else {
+ return;
+ }
+
+ if (association.isSelfManaged() || added) {
+ notifyDevicePresenceEvent(userId, packageName, event);
+ // Also send the legacy callback.
+ legacyNotifyDevicePresenceEvent(association, true);
+ }
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
+ final boolean removed = presentDevicesForSource.remove(associationId);
+ if (!removed) {
+ Slog.w(TAG, "The association is already NOT present.");
+ }
+
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ Slog.e(TAG, "Package is not bound");
+ return;
+ }
+
+ if (association.isSelfManaged() || removed) {
+ notifyDevicePresenceEvent(userId, packageName, event);
+ // Also send the legacy callback.
+ legacyNotifyDevicePresenceEvent(association, false);
+ }
+
+ // Check if there are other devices associated to the app that are present.
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ break;
+ default:
+ Slog.e(TAG, "Event: " + eventType + " is not supported.");
+ break;
+ }
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) {
+ Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType
+ + "]...");
+
+ final ParcelUuid parcelUuid = uuid.getUuid();
+ final String packageName = uuid.getPackageName();
+ final int userId = uuid.getUserId();
+ final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType,
+ parcelUuid);
+
+ switch (eventType) {
+ case EVENT_BT_CONNECTED:
+ boolean added = mConnectedUuidDevices.add(parcelUuid);
+ if (!added) {
+ Slog.w(TAG, "This device is already connected.");
+ }
+
+ bindApplicationIfNeeded(userId, packageName, false);
+
+ notifyDevicePresenceEvent(userId, packageName, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+ if (!removed) {
+ Slog.w(TAG, "This device is already disconnected.");
+ return;
+ }
+
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ Slog.e(TAG, "Package is not bound.");
+ return;
+ }
+
+ notifyDevicePresenceEvent(userId, packageName, event);
+
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ break;
+ default:
+ Slog.e(TAG, "Event: " + eventType + " is not supported");
+ break;
+ }
+ }
+
+ /**
+ * Notify device presence event to the app.
+ *
+ * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead.
+ */
+ @Deprecated
+ private void legacyNotifyDevicePresenceEvent(AssociationInfo association,
+ boolean isAppeared) {
+ Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString()
+ + "], isAppeared=[" + isAppeared + "]");
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ final CompanionServiceConnector primaryServiceConnector =
+ mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "Package is not bound.");
+ return;
+ }
+
+ if (isAppeared) {
+ primaryServiceConnector.postOnDeviceAppeared(association);
+ } else {
+ primaryServiceConnector.postOnDeviceDisappeared(association);
+ }
+ }
+
+ /**
+ * Notify the device presence event to the app.
+ */
+ private void notifyDevicePresenceEvent(int userId, String packageName,
+ DevicePresenceEvent event) {
+ Slog.i(TAG,
+ "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=["
+ + packageName + "], event=[" + event + "]...");
+
+ final CompanionServiceConnector primaryServiceConnector =
+ mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "Package is NOT bound.");
+ return;
+ }
+
+ primaryServiceConnector.postOnDevicePresenceEvent(event);
+ }
+
+ /**
+ * Notify the self-managed device presence event to the app.
+ */
+ public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) {
+ Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId);
+
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association id=[" + associationId
+ + "] is not self-managed.");
+ }
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // timestamp.
+ association = (new AssociationInfo.Builder(association))
+ .setLastTimeConnected(System.currentTimeMillis())
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ if (isAppeared) {
+ onSelfManagedDeviceConnected(associationId);
+ } else {
+ onSelfManagedDeviceDisconnected(associationId);
+ }
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, isAppeared);
+ }
+ }
+
+ private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionServiceConnector serviceConnector) {
+
+ boolean isPrimary = serviceConnector.isPrimary();
+ Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
+
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+
+ for (AssociationInfo association : associations) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ break;
+ }
+ }
+
+ mCompanionAppBinder.removePackage(userId, packageName);
+ }
+
+ // Second: schedule rebinding if needed.
+ final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
+
+ if (shouldScheduleRebind) {
+ mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector);
+ }
+ }
+
+ /**
+ * Check if the system should rebind the self-managed secondary services
+ * OR non-self-managed services.
+ */
+ private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
+ // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
+ // app is uninstalled.
+ boolean stillAssociated = false;
+ // Make sure to clean up the state for all the associations
+ // that associate with this package.
+ boolean shouldScheduleRebind = false;
+ boolean shouldScheduleRebindForUuid = false;
+ final List<ObservableUuid> uuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo ai :
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
+ final int associationId = ai.getId();
+ stillAssociated = true;
+ if (ai.isSelfManaged()) {
+ // Do not rebind if primary one is died for selfManaged application.
+ if (isPrimary && isDevicePresent(associationId)) {
+ onSelfManagedDeviceReporterBinderDied(associationId);
+ shouldScheduleRebind = false;
+ }
+ // Do not rebind if both primary and secondary services are died for
+ // selfManaged application.
+ shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId,
+ packageName);
+ } else if (ai.isNotifyOnDeviceNearby()) {
+ // Always rebind for non-selfManaged devices.
+ shouldScheduleRebind = true;
+ }
+ }
+
+ for (ObservableUuid uuid : uuids) {
+ if (isDeviceUuidPresent(uuid.getUuid())) {
+ shouldScheduleRebindForUuid = true;
+ break;
+ }
+ }
+
+ return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+ mSimulated.remove(id);
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(id);
+ mBtDisconnectedDevicesBlePresence.delete(id);
+ }
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+
+ /**
+ * Return a set of devices that pending to report connectivity
+ */
+ public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+ synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+ return mBtConnectionListener.mPendingConnectedDevices;
+ }
+ }
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ /**
+ * The BLE scan can be only stopped if all the devices have been reported
+ * BT connected and BLE presence and are not pending to report BLE lost.
+ */
+ private boolean canStopBleScan() {
+ for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
+ int id = ai.getId();
+ synchronized (mBtDisconnectedDevices) {
+ if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
+ && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
+ Slog.i(TAG, "The BLE scan cannot be stopped, "
+ + "device( " + id + " ) is not yet connected "
+ + "OR the BLE is not current present Or is pending to report BLE lost");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Dumps system information about devices that are marked as "present".
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Present: ");
+ if (mConnectedBtDevices.isEmpty()
+ && mNearbyBleDevices.isEmpty()
+ && mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ return;
+ } else {
+ out.append("\n");
+ }
+
+ out.append(" Connected Bluetooth Devices: ");
+ if (mConnectedBtDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mConnectedBtDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Nearby BLE Devices: ");
+ if (mNearbyBleDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mNearbyBleDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Self-Reported Devices: ");
+ if (mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mReportedSelfManagedDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ if (mSimulated.contains(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
+ }
+ }
+ }
+
+ private class BleDeviceDisappearedScheduler extends Handler {
+ BleDeviceDisappearedScheduler() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleBleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ removeMessages(associationId);
+ }
+ Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
+ sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
+ }
+
+ void unScheduleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+
+ removeMessages(associationId);
+ }
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
+ associationId);
+ // If a device hasn't reported after 10 seconds and is not currently present,
+ // assume BLE is lost and trigger the onDeviceEvent callback with the
+ // EVENT_BLE_DISAPPEARED event.
+ if (mBtDisconnectedDevices.contains(associationId)
+ && !isCurrentPresent) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
+ + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index db15da29..fa0f6bd 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -300,4 +300,18 @@
return readObservableUuidsFromCache(userId);
}
}
+
+ /**
+ * Check if a UUID is being observed by the package.
+ */
+ public boolean isUuidBeingObserved(ParcelUuid uuid, int userId, String packageName) {
+ final List<ObservableUuid> uuidsBeingObserved = getObservableUuidsForPackage(userId,
+ packageName);
+ for (ObservableUuid observableUuid : uuidsBeingObserved) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 793fb7f..697ef87 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -46,7 +46,6 @@
@SuppressLint("LongLogTag")
public class CompanionTransportManager {
private static final String TAG = "CDM_CompanionTransportManager";
- private static final boolean DEBUG = false;
private boolean mSecureTransportEnabled = true;
@@ -137,11 +136,17 @@
}
}
- public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
+ /**
+ * Attach transport.
+ */
+ public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+ Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
- detachSystemDataTransport(packageName, userId, associationId);
+ detachSystemDataTransport(associationId);
}
// TODO: Implement new API to pass a PSK
@@ -149,9 +154,18 @@
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport attached.");
}
- public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ /**
+ * Detach transport.
+ */
+ public void detachSystemDataTransport(int associationId) {
+ Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
final Transport transport = mTransports.removeReturnOld(associationId);
if (transport == null) {
@@ -161,6 +175,8 @@
transport.stop();
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport detached.");
}
private void notifyOnTransportsChanged() {
@@ -307,8 +323,7 @@
int associationId = transport.mAssociationId;
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
if (association != null) {
- detachSystemDataTransport(association.getPackageName(),
- association.getUserId(),
+ detachSystemDataTransport(
association.getId());
}
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index 2cf1f46..d7e766e 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -39,7 +39,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
@@ -208,7 +207,7 @@
/**
* Require the caller to hold necessary permission to observe device presence by UUID.
*/
- public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -235,23 +234,6 @@
return checkCallerCanManageCompanionDevice(context);
}
- /**
- * Check if CDM can trust the context to process the association.
- */
- @Nullable
- public static AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
- @Nullable AssociationInfo association) {
- if (association == null) return null;
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
- return null;
- }
-
- return association;
- }
-
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index b28bc1f..16a9933 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityOptions;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.contextualsearch.CallbackToken;
import android.app.contextualsearch.ContextualSearchManager;
import android.app.contextualsearch.ContextualSearchState;
import android.app.contextualsearch.IContextualSearchCallback;
@@ -164,7 +165,7 @@
}
}
- private Intent getContextualSearchIntent(int entrypoint, IBinder mToken) {
+ private Intent getContextualSearchIntent(int entrypoint, CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent();
if (launchIntent == null) {
return null;
@@ -256,14 +257,14 @@
}
private class ContextualSearchManagerStub extends IContextualSearchManager.Stub {
- private @Nullable IBinder mToken;
+ private @Nullable CallbackToken mToken;
@Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
enforcePermission("startContextualSearch");
- mToken = new Binder();
+ mToken = new CallbackToken();
// We get the launch intent with the system server's identity because the system
// server has READ_FRAME_BUFFER permission to get the screenshot and because only
// the system server can invoke non-exported activities.
@@ -284,7 +285,7 @@
if (DEBUG_USER) {
Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
}
- if (mToken == null || !mToken.equals(token)) {
+ if (mToken == null || !mToken.getToken().equals(token)) {
if (DEBUG_USER) {
Log.e(TAG, "getContextualSearchState: invalid token, returning error");
}
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 595d16d..b55f35d 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -44,6 +44,7 @@
private static final int LOW_MEM_AND_SWAP_UTIL = 6;
private static final int LOW_FILECACHE_AFTER_THRASHING = 7;
private static final int LOW_MEM = 8;
+ private static final int DIRECT_RECL_STUCK = 9;
/**
* Processes the LMK_KILL_OCCURRED packet data
@@ -98,6 +99,8 @@
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_FILECACHE_AFTER_THRASHING;
case LOW_MEM:
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_MEM;
+ case DIRECT_RECL_STUCK:
+ return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__DIRECT_RECL_STUCK;
default:
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a917909..c020a9f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -576,6 +576,8 @@
}
// allowDelayedLocking set here as stopping user is done without any explicit request
// from outside.
+ Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
+ currentlyRunningLru.size(), userId);
if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
@@ -1540,6 +1542,7 @@
}
if (userInfo.isGuest() || userInfo.isEphemeral()) {
// This is a user to be stopped.
+ Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
synchronized (mLock) {
stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
null, null);
@@ -2219,6 +2222,10 @@
mUserSwitchObservers.finishBroadcast();
}
+ /**
+ * Possibly stops the given full user (or its profile) when we switch out of it, if dictated
+ * by policy.
+ */
private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -2228,20 +2235,27 @@
hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
synchronized (mLock) {
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
- boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch();
- if (!disallowRunInBg) {
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users",
- oldUserId);
- }
+ if (hasRestriction || shouldStopUserOnSwitch()) {
+ Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
+ stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
+ null, null);
return;
}
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users",
- oldUserId);
+ }
+
+ // We didn't need to stop the parent, but perhaps one of its profiles needs to be stopped.
+ final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
+ oldUserId, /* enabledOnly= */ false);
+ final int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ final int profileUserId = profiles.get(i).id;
+ if (hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, profileUserId)) {
+ Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
+ synchronized (mLock) {
+ stopUsersLU(profileUserId,
+ /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+ }
}
- stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
- null, null);
}
}
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 0c3dfa7..eb78fe6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -23,6 +23,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -135,6 +136,14 @@
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
+ try {
+ mClientMonitor.getListener().onError(mClientMonitor.getSensorId(),
+ mClientMonitor.getCookie(), BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when trying to send error in cancel "
+ + "watchdog.");
+ }
getWrappedCallback(mOnStartCallback)
.onClientFinished(mClientMonitor, false /* success */);
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index b1defe9..65c9f35 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.os.Trace;
+import android.view.Display;
/**
* An implementation of the offload session that keeps track of whether the session is active.
@@ -42,7 +43,7 @@
@Override
public void setDozeStateOverride(int displayState) {
- mDisplayPowerController.overrideDozeScreenState(displayState);
+ mDisplayPowerController.overrideDozeScreenState(displayState, Display.STATE_REASON_OFFLOAD);
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d99f712..043ff0e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -61,6 +61,7 @@
import android.util.MathUtils;
import android.util.MutableFloat;
import android.util.MutableInt;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -788,14 +789,14 @@
}
@Override
- public void overrideDozeScreenState(int displayState) {
+ public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
mHandler.postAtTime(() -> {
if (mDisplayOffloadSession == null
|| !(DisplayOffloadSession.isSupportedOffloadState(displayState)
|| displayState == Display.STATE_UNKNOWN)) {
return;
}
- mDisplayStateController.overrideDozeScreenState(displayState);
+ mDisplayStateController.overrideDozeScreenState(displayState, reason);
sendUpdatePowerState();
}, mClock.uptimeMillis());
}
@@ -975,7 +976,7 @@
&& mAutomaticBrightnessController.isInIdleMode());
mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
- noteScreenState(mPowerState.getScreenState());
+ noteScreenState(mPowerState.getScreenState(), Display.STATE_REASON_DEFAULT_POLICY);
noteScreenBrightness(mPowerState.getScreenBrightness());
// Initialize all of the brightness tracking state
@@ -1276,8 +1277,10 @@
displayBrightnessFollowers = mDisplayBrightnessFollowers.clone();
}
- int state = mDisplayStateController
- .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
+ final Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController
+ .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
+ int state = stateAndReason.first;
// Initialize things the first time the power state is changed.
if (mustInitialize) {
@@ -1287,7 +1290,9 @@
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
- animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
+ animateScreenStateChange(
+ state, /* reason= */ stateAndReason.second,
+ mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
// Switch to doze auto-brightness mode if needed
@@ -2024,11 +2029,11 @@
Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
}
- private boolean setScreenState(int state) {
- return setScreenState(state, false /*reportOnly*/);
+ private boolean setScreenState(int state, @Display.StateReason int reason) {
+ return setScreenState(state, reason, false /*reportOnly*/);
}
- private boolean setScreenState(int state, boolean reportOnly) {
+ private boolean setScreenState(int state, @Display.StateReason int reason, boolean reportOnly) {
final boolean isOff = (state == Display.STATE_OFF);
final boolean isOn = (state == Display.STATE_ON);
final boolean changed = mPowerState.getScreenState() != state;
@@ -2075,9 +2080,9 @@
+ " value=" + propertyValue + " " + e.getMessage());
}
- mPowerState.setScreenState(state);
+ mPowerState.setScreenState(state, reason);
// Tell battery stats about the transition.
- noteScreenState(state);
+ noteScreenState(state, reason);
}
}
@@ -2174,7 +2179,8 @@
}
}
- private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
+ private void animateScreenStateChange(
+ int target, @Display.StateReason int reason, boolean performScreenOffTransition) {
// If there is already an animation in progress, don't interfere with it.
if (mColorFadeEnabled
&& (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
@@ -2200,14 +2206,14 @@
// display has turned off so it can prepare the appropriate power on animation, but we
// don't want to actually transition to the fully off state since that takes
// significantly longer to transition from.
- setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
+ setScreenState(Display.STATE_OFF, reason, target != Display.STATE_OFF /*reportOnly*/);
}
// If we were in the process of turning off the screen but didn't quite
// finish. Then finish up now to prevent a jarring transition back
// to screen on if we skipped blocking screen on as usual.
if (mPendingScreenOff && target != Display.STATE_OFF) {
- setScreenState(Display.STATE_OFF);
+ setScreenState(Display.STATE_OFF, reason);
mPendingScreenOff = false;
mPowerState.dismissColorFadeResources();
}
@@ -2216,7 +2222,7 @@
// Want screen on. The contents of the screen may not yet
// be visible if the color fade has not been dismissed because
// its last frame of animation is solid black.
- if (!setScreenState(Display.STATE_ON)) {
+ if (!setScreenState(Display.STATE_ON, reason)) {
return; // screen on blocked
}
if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
@@ -2246,7 +2252,7 @@
}
// Set screen state.
- if (!setScreenState(Display.STATE_DOZE)) {
+ if (!setScreenState(Display.STATE_DOZE, reason)) {
return; // screen on blocked
}
@@ -2265,10 +2271,10 @@
// If not already suspending, temporarily set the state to doze until the
// screen on is unblocked, then suspend.
if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
- if (!setScreenState(Display.STATE_DOZE)) {
+ if (!setScreenState(Display.STATE_DOZE, reason)) {
return; // screen on blocked
}
- setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
+ setScreenState(Display.STATE_DOZE_SUSPEND, reason); // already on so can't block
}
// Dismiss the black surface without fanfare.
@@ -2286,10 +2292,10 @@
// If not already suspending, temporarily set the state to on until the
// screen on is unblocked, then suspend.
if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
- if (!setScreenState(Display.STATE_ON)) {
+ if (!setScreenState(Display.STATE_ON, reason)) {
return;
}
- setScreenState(Display.STATE_ON_SUSPEND);
+ setScreenState(Display.STATE_ON_SUSPEND, reason);
}
// Dismiss the black surface without fanfare.
@@ -2305,7 +2311,7 @@
if (mPowerState.getColorFadeLevel() == 0.0f) {
// Turn the screen off.
// A black surface is already hiding the contents of the screen.
- setScreenState(Display.STATE_OFF);
+ setScreenState(Display.STATE_OFF, reason);
mPendingScreenOff = false;
mPowerState.dismissColorFadeResources();
} else if (performScreenOffTransition
@@ -2667,10 +2673,10 @@
}
- private void noteScreenState(int screenState) {
+ private void noteScreenState(int screenState, @Display.StateReason int reason) {
// Log screen state change with display id
FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_STATE_CHANGED_V2,
- screenState, mDisplayStatsId);
+ screenState, mDisplayStatsId, reason);
if (mBatteryStats != null) {
try {
// TODO(multi-display): make this multi-display
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 408d610..d28578a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -23,6 +23,7 @@
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
+import android.view.Display;
import java.io.PrintWriter;
@@ -137,7 +138,13 @@
boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request,
boolean waitForNegativeProximity);
- void overrideDozeScreenState(int displayState);
+ /**
+ * Overrides the current doze screen state.
+ *
+ * @param displayState the new doze display state.
+ * @param reason the reason behind the new doze display state.
+ */
+ void overrideDozeScreenState(int displayState, @Display.StateReason int reason);
void setDisplayOffloadSession(DisplayManagerInternal.DisplayOffloadSession session);
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 90bad12..e5efebc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -159,12 +159,13 @@
/**
* Sets whether the screen is on, off, or dozing.
*/
- public void setScreenState(int state) {
+ public void setScreenState(int state, @Display.StateReason int reason) {
if (mScreenState != state) {
if (DEBUG) {
- Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state));
+ Slog.w(TAG,
+ "setScreenState: state=" + Display.stateToString(state)
+ + "; reason=" + Display.stateReasonToString(reason));
}
-
mScreenState = state;
mScreenReady = false;
scheduleScreenUpdate();
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index 5f28934..21bb208 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -18,6 +18,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.util.IndentingPrintWriter;
+import android.util.Pair;
import android.view.Display;
import com.android.server.display.DisplayPowerProximityStateController;
@@ -33,6 +34,7 @@
private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
private boolean mPerformScreenOffTransition = false;
private int mDozeStateOverride = Display.STATE_UNKNOWN;
+ private int mDozeStateOverrideReason = Display.STATE_REASON_UNKNOWN;
public DisplayStateController(DisplayPowerProximityStateController
displayPowerProximityStateController) {
@@ -47,14 +49,19 @@
* @param isDisplayEnabled A boolean flag representing if the display is enabled
* @param isDisplayInTransition A boolean flag representing if the display is undergoing the
* transition phase
+ * @return a {@link Pair} of integers, the first being the updated display state, and the second
+ * being the reason behind the new display state.
*/
- public int updateDisplayState(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
- boolean isDisplayEnabled, boolean isDisplayInTransition) {
+ public Pair<Integer, Integer> updateDisplayState(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+ boolean isDisplayEnabled,
+ boolean isDisplayInTransition) {
mPerformScreenOffTransition = false;
// Compute the basic display state using the policy.
// We might override this below based on other factors.
// Initialise brightness as invalid.
int state;
+ int reason = Display.STATE_REASON_DEFAULT_POLICY;
switch (displayPowerRequest.policy) {
case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
state = Display.STATE_OFF;
@@ -63,8 +70,10 @@
case DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE:
if (mDozeStateOverride != Display.STATE_UNKNOWN) {
state = mDozeStateOverride;
+ reason = mDozeStateOverrideReason;
} else if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
state = displayPowerRequest.dozeScreenState;
+ reason = displayPowerRequest.dozeScreenStateReason;
} else {
state = Display.STATE_DOZE;
}
@@ -84,11 +93,13 @@
state = Display.STATE_OFF;
}
- return state;
+ return new Pair(state, reason);
}
- public void overrideDozeScreenState(int displayState) {
+ /** Overrides the doze screen state with a given reason. */
+ public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
mDozeStateOverride = displayState;
+ mDozeStateOverrideReason = reason;
}
/**
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d997020..42c9e08 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -512,7 +512,7 @@
}
private void startDozingInternal(IBinder token, int screenState,
- int screenBrightness) {
+ @Display.StateReason int reason, int screenBrightness) {
if (DEBUG) {
Slog.d(TAG, "Dream requested to start dozing: " + token
+ ", screenState=" + screenState
@@ -524,7 +524,7 @@
mCurrentDream.dozeScreenState = screenState;
mCurrentDream.dozeScreenBrightness = screenBrightness;
mPowerManagerInternal.setDozeOverrideFromDreamManager(
- screenState, screenBrightness);
+ screenState, reason, screenBrightness);
if (!mCurrentDream.isDozing) {
mCurrentDream.isDozing = true;
mDozeWakeLock.acquire();
@@ -543,7 +543,9 @@
mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
- Display.STATE_UNKNOWN, PowerManager.BRIGHTNESS_DEFAULT);
+ Display.STATE_UNKNOWN,
+ Display.STATE_REASON_DREAM_MANAGER,
+ PowerManager.BRIGHTNESS_DEFAULT);
}
}
}
@@ -1046,7 +1048,9 @@
}
@Override // Binder call
- public void startDozing(IBinder token, int screenState, int screenBrightness) {
+ public void startDozing(
+ IBinder token, int screenState, @Display.StateReason int reason,
+ int screenBrightness) {
// Requires no permission, called by Dream from an arbitrary process.
if (token == null) {
throw new IllegalArgumentException("token must not be null");
@@ -1054,7 +1058,7 @@
final long ident = Binder.clearCallingIdentity();
try {
- startDozingInternal(token, screenState, screenBrightness);
+ startDozingInternal(token, screenState, reason, screenBrightness);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 2439589..e7f717a 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -387,16 +387,20 @@
private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo(
AudioDeviceInfo audioDeviceInfo) {
String address = audioDeviceInfo.getAddress();
+
// Passing a null route id means we want to get the default id for the route. Generally, we
// only expect to pass null for non-Bluetooth routes.
- String routeId =
- TextUtils.isEmpty(address)
- ? null
- : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
+ String routeId = null;
+
// We use the name from the port instead AudioDeviceInfo#getProductName because the latter
// replaces empty names with the name of the device (example: Pixel 8). In that case we want
// to derive a name ourselves from the type instead.
String deviceName = audioDeviceInfo.getPort().name();
+
+ if (!TextUtils.isEmpty(address)) {
+ routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
+ deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address);
+ }
return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address);
}
diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
index 8119628..b881ef6 100644
--- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
+++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
@@ -119,6 +119,7 @@
BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
deviceStateChangedIntentFilter.addAction(
BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+ deviceStateChangedIntentFilter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
deviceStateChangedIntentFilter, null, null);
@@ -133,13 +134,17 @@
@Nullable
public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
- // TODO: b/305199571 - Optimize the following statement to avoid creating the full
- // MediaRoute2Info instance. We just need the id.
return bluetoothDevice != null
- ? createBluetoothRoute(bluetoothDevice).mRoute.getId()
+ ? getRouteIdForType(bluetoothDevice, getDeviceType(bluetoothDevice))
: null;
}
+ @Nullable
+ public synchronized String getNameForBluetoothAddress(@NonNull String address) {
+ BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
+ return bluetoothDevice != null ? getDeviceName(bluetoothDevice) : null;
+ }
+
public synchronized void activateBluetoothDeviceWithAddress(String address) {
BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(address);
@@ -218,33 +223,12 @@
BluetoothRouteInfo
newBtRoute = new BluetoothRouteInfo();
newBtRoute.mBtDevice = device;
- String deviceName =
- Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
- ? device.getAlias()
- : device.getName();
- if (TextUtils.isEmpty(deviceName)) {
- deviceName = mContext.getResources().getText(R.string.unknownName).toString();
- }
+ String deviceName = getDeviceName(device);
- String routeId = device.getAddress();
- int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
- newBtRoute.mConnectedProfiles = new SparseBooleanArray();
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.A2DP, true);
- }
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
- routeId = HEARING_AID_ROUTE_ID_PREFIX
- + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device);
- type = MediaRoute2Info.TYPE_HEARING_AID;
- }
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
- routeId = LE_AUDIO_ROUTE_ID_PREFIX
- + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.LE_AUDIO, device);
- type = MediaRoute2Info.TYPE_BLE_HEADSET;
- }
+ int type = getDeviceType(device);
+ String routeId = getRouteIdForType(device, type);
+ newBtRoute.mConnectedProfiles = getConnectedProfiles(device);
// Note that volume is only relevant for active bluetooth routes, and those are managed via
// AudioManager.
newBtRoute.mRoute =
@@ -262,6 +246,58 @@
return newBtRoute;
}
+ private String getDeviceName(BluetoothDevice device) {
+ String deviceName =
+ Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName()
+ ? device.getAlias()
+ : device.getName();
+ if (TextUtils.isEmpty(deviceName)) {
+ deviceName = mContext.getResources().getText(R.string.unknownName).toString();
+ }
+ return deviceName;
+ }
+ private SparseBooleanArray getConnectedProfiles(@NonNull BluetoothDevice device) {
+ SparseBooleanArray connectedProfiles = new SparseBooleanArray();
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
+ connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
+ connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ }
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
+ connectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+ }
+
+ return connectedProfiles;
+ }
+
+ private int getDeviceType(@NonNull BluetoothDevice device) {
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
+
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ }
+
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ }
+
+ private String getRouteIdForType(@NonNull BluetoothDevice device, int type) {
+ return switch (type) {
+ case (MediaRoute2Info.TYPE_BLE_HEADSET) ->
+ LE_AUDIO_ROUTE_ID_PREFIX
+ + mBluetoothProfileMonitor.getGroupId(
+ BluetoothProfile.LE_AUDIO, device);
+ case (MediaRoute2Info.TYPE_HEARING_AID) ->
+ HEARING_AID_ROUTE_ID_PREFIX
+ + mBluetoothProfileMonitor.getGroupId(
+ BluetoothProfile.HEARING_AID, device);
+ // TYPE_BLUETOOTH_A2DP
+ default -> device.getAddress();
+ };
+ }
+
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
private MediaRoute2Info mRoute;
@@ -300,6 +336,7 @@
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+ case BluetoothDevice.ACTION_ALIAS_CHANGED:
updateBluetoothRoutes();
notifyBluetoothRoutesUpdated();
}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 8e37b4f..418eacc 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -436,7 +436,7 @@
* Only available when {@code MODES_API} is active; otherwise returns an empty list.
*/
int[] getActiveRuleTypes() {
- if (!Flags.modesApi() || mNewZenMode == ZEN_MODE_OFF) {
+ if (!Flags.modesApi()) {
return new int[0];
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e394482..87b1769 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -747,7 +747,7 @@
onInstallComplete(PackageManager.INSTALL_SUCCEEDED, mContext,
onCompleteSender);
}
- });
+ }, pkgSetting.getAppId(), callingUid, pkgSetting.isSystem());
restoreAndPostInstall(request);
}
} finally {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index c10196f..4dcee04 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -158,6 +158,8 @@
@Nullable
private DomainSet mPreVerifiedDomains;
+ private int mInstallerUidForInstallExisting = INVALID_UID;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -180,7 +182,7 @@
// Install existing package as user
InstallRequest(int userId, int returnCode, AndroidPackage pkg, int[] newUsers,
- Runnable runnable) {
+ Runnable runnable, int appId, int installerUid, boolean isSystem) {
mUserId = userId;
mInstallArgs = null;
mReturnCode = returnCode;
@@ -191,6 +193,9 @@
mIsInstallForUsers = true;
mSessionId = -1;
mRequireUserAction = USER_ACTION_UNSPECIFIED;
+ mAppId = appId;
+ mInstallerUidForInstallExisting = installerUid;
+ mSystem = isSystem;
}
// addForInit
@@ -381,7 +386,8 @@
public int getInstallerPackageUid() {
return (mInstallArgs != null && mInstallArgs.mInstallSource != null)
- ? mInstallArgs.mInstallSource.mInstallerPackageUid : INVALID_UID;
+ ? mInstallArgs.mInstallSource.mInstallerPackageUid
+ : mInstallerUidForInstallExisting;
}
public int getDataLoaderType() {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 24d2a26..a0b6897 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -29,6 +29,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
+import com.android.server.pm.pkg.AndroidPackage;
import java.io.File;
import java.io.IOException;
@@ -111,17 +112,20 @@
long versionCode = 0, apksSize = 0;
if (success) {
- // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because
- // the scan result is null for installExistingPackageAsUser(). Because it's installing
- // a package that's already existing, there's no scanning or parsing involved
- try {
+ if (mInstallRequest.isInstallForUsers()) {
+ // In case of installExistingPackageAsUser, there's no scanned PackageSetting
+ // in the request but the pkg object should be readily available
+ AndroidPackage pkg = mInstallRequest.getPkg();
+ if (pkg != null) {
+ versionCode = pkg.getLongVersionCode();
+ apksSize = getApksSize(new File(pkg.getPath()));
+ }
+ } else {
final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
if (ps != null) {
versionCode = ps.getVersionCode();
apksSize = getApksSize(ps.getPath());
}
- } catch (IllegalStateException | NullPointerException e) {
- // no-op
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 73e7485..324637c 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -717,8 +717,9 @@
break;
case UserManager.DISALLOW_RUN_IN_BACKGROUND:
if (newValue) {
- int currentUser = ActivityManager.getCurrentUser();
- if (currentUser != userId && userId != UserHandle.USER_SYSTEM) {
+ final ActivityManager am = context.getSystemService(ActivityManager.class);
+ if (!am.isProfileForeground(UserHandle.of(userId))
+ && userId != UserHandle.USER_SYSTEM) {
try {
ActivityManager.getService().stopUser(userId, false, null);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 6a0fe90..9a9c4f2 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -431,8 +431,10 @@
}
boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
- boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
- boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+ boolean boostScreenBrightness, int dozeScreenState,
+ @Display.StateReason int dozeScreenStateReason,
+ float dozeScreenBrightness, boolean overrideDrawWakeLock,
+ PowerSaveState powerSaverState, boolean quiescent,
boolean dozeAfterScreenOff, boolean bootCompleted,
boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
boolean brightWhenDozing) {
@@ -444,18 +446,28 @@
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
mDisplayPowerRequest.dozeScreenState = dozeScreenState;
+ mDisplayPowerRequest.dozeScreenStateReason = dozeScreenStateReason;
if ((getWakeLockSummaryLocked() & WAKE_LOCK_DRAW) != 0 && !overrideDrawWakeLock) {
if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ mDisplayPowerRequest.dozeScreenStateReason =
+ Display.STATE_REASON_DRAW_WAKE_LOCK;
}
if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+ mDisplayPowerRequest.dozeScreenStateReason =
+ Display.STATE_REASON_DRAW_WAKE_LOCK;
}
+ } else {
+ mDisplayPowerRequest.dozeScreenStateReason =
+ Display.STATE_REASON_DEFAULT_POLICY;
}
mDisplayPowerRequest.dozeScreenBrightness = dozeScreenBrightness;
} else {
mDisplayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
mDisplayPowerRequest.dozeScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mDisplayPowerRequest.dozeScreenStateReason =
+ Display.STATE_REASON_DEFAULT_POLICY;
}
mDisplayPowerRequest.lowPowerMode = powerSaverState.batterySaverEnabled;
mDisplayPowerRequest.screenLowPowerBrightnessFactor = powerSaverState.brightnessFactor;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ea9ab30..3dec02e 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -640,6 +640,8 @@
// The screen state to use while dozing.
private int mDozeScreenStateOverrideFromDreamManager = Display.STATE_UNKNOWN;
+ private int mDozeScreenStateOverrideReasonFromDreamManager = Display.STATE_REASON_UNKNOWN;
+
// The screen brightness to use while dozing.
private int mDozeScreenBrightnessOverrideFromDreamManager = PowerManager.BRIGHTNESS_DEFAULT;
@@ -3574,6 +3576,7 @@
boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
mDozeScreenStateOverrideFromDreamManager,
+ mDozeScreenStateOverrideReasonFromDreamManager,
mDozeScreenBrightnessOverrideFromDreamManagerFloat,
mDrawWakeLockOverrideFromSidekick,
mBatterySaverSupported
@@ -4383,11 +4386,12 @@
}
private void setDozeOverrideFromDreamManagerInternal(
- int screenState, int screenBrightness) {
+ int screenState, @Display.StateReason int reason, int screenBrightness) {
synchronized (mLock) {
if (mDozeScreenStateOverrideFromDreamManager != screenState
|| mDozeScreenBrightnessOverrideFromDreamManager != screenBrightness) {
mDozeScreenStateOverrideFromDreamManager = screenState;
+ mDozeScreenStateOverrideReasonFromDreamManager = reason;
mDozeScreenBrightnessOverrideFromDreamManager = screenBrightness;
mDozeScreenBrightnessOverrideFromDreamManagerFloat =
BrightnessSynchronizer.brightnessIntToFloat(mDozeScreenBrightnessOverrideFromDreamManager);
@@ -6985,7 +6989,8 @@
}
@Override
- public void setDozeOverrideFromDreamManager(int screenState, int screenBrightness) {
+ public void setDozeOverrideFromDreamManager(
+ int screenState, int reason, int screenBrightness) {
switch (screenState) {
case Display.STATE_UNKNOWN:
case Display.STATE_OFF:
@@ -7002,7 +7007,7 @@
|| screenBrightness > PowerManager.BRIGHTNESS_ON) {
screenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
}
- setDozeOverrideFromDreamManagerInternal(screenState, screenBrightness);
+ setDozeOverrideFromDreamManagerInternal(screenState, reason, screenBrightness);
}
@Override
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1da9f25..6f16d2c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -463,7 +463,7 @@
}
void drawMagnifiedRegionBorderIfNeeded(int displayId) {
- if (Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
return;
}
@@ -653,7 +653,7 @@
mDisplayContent = displayContent;
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
- mMagnifiedViewport = Flags.magnificationAlwaysDrawFullscreenBorder()
+ mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
? null : new MagnifiedViewport();
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(mService);
@@ -697,7 +697,7 @@
mMagnificationSpec.clear();
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setShowMagnifiedBorderIfNeeded();
}
}
@@ -708,7 +708,7 @@
FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
mIsFullscreenMagnificationActivated = activated;
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setMagnifiedRegionBorderShown(activated, true);
mMagnifiedViewport.showMagnificationBoundsIfNeeded();
}
@@ -746,7 +746,7 @@
}
recomputeBounds();
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.onDisplaySizeChanged();
}
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
@@ -908,7 +908,7 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.destroyWindow();
}
}
@@ -919,7 +919,7 @@
FLAGS_MAGNIFICATION_CALLBACK);
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.drawWindowIfNeeded();
}
}
@@ -1039,14 +1039,14 @@
}
visibleWindows.clear();
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
}
final boolean magnifiedChanged =
!mOldMagnificationRegion.equals(mMagnificationRegion);
if (magnifiedChanged) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
}
mOldMagnificationRegion.set(mMagnificationRegion);
@@ -1129,7 +1129,7 @@
}
void dump(PrintWriter pw, String prefix) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.dump(pw, prefix);
}
}
@@ -1235,7 +1235,7 @@
}
// TODO(291891390): Remove this class when we clean up the flag
- // magnificationAlwaysDrawFullscreenBorder
+ // alwaysDrawMagnificationFullscreenBorder
private final class ViewportWindow implements Runnable {
private static final String SURFACE_TITLE = "Magnification Overlay";
@@ -1540,7 +1540,7 @@
public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
// TODO(291891390): Remove this field when we clean up the flag
- // magnificationAlwaysDrawFullscreenBorder
+ // alwaysDrawMagnificationFullscreenBorder
public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
@@ -1569,7 +1569,7 @@
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mService.mGlobalLock) {
if (isFullscreenMagnificationActivated()) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setMagnifiedRegionBorderShown(true, true);
}
mService.scheduleAnimationLocked();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index a28259d..db2a1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -265,7 +265,8 @@
advanceTime(1);
// The display should have been turned off
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+ verify(mHolder.displayPowerState)
+ .setScreenState(Display.STATE_OFF, Display.STATE_REASON_DEFAULT_POLICY);
clearInvocations(mHolder.displayPowerState);
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
@@ -276,13 +277,15 @@
advanceTime(1);
// The prox sensor is debounced so the display should not have been turned back on yet
- verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+ verify(mHolder.displayPowerState, never())
+ .setScreenState(Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY);
// Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
advanceTime(1000);
// The display should have been turned back on
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ verify(mHolder.displayPowerState)
+ .setScreenState(Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY);
}
@Test
@@ -305,7 +308,8 @@
advanceTime(1);
// The display should have been turned off
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+ verify(mHolder.displayPowerState)
+ .setScreenState(Display.STATE_OFF, Display.STATE_REASON_DEFAULT_POLICY);
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
// The display device changes and we no longer have a prox sensor
@@ -318,7 +322,8 @@
// The display should have been turned back on and the listener should have been
// unregistered
- verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ verify(mHolder.displayPowerState)
+ .setScreenState(Display.STATE_ON, Display.STATE_REASON_DEFAULT_POLICY);
verify(mSensorManagerMock).unregisterListener(listener);
}
@@ -814,17 +819,17 @@
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState).setScreenState(anyInt());
+ verify(mHolder.displayPowerState).setScreenState(anyInt(), anyInt());
mHolder = createDisplayPowerController(42, UNIQUE_ID);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt(), anyInt());
mHolder.dpc.onBootCompleted();
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState).setScreenState(anyInt());
+ verify(mHolder.displayPowerState).setScreenState(anyInt(), anyInt());
}
@Test
@@ -1469,7 +1474,7 @@
doAnswer(invocation -> {
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ }).when(mHolder.displayPowerState).setScreenState(anyInt(), anyInt());
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1479,10 +1484,12 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ mHolder.dpc.overrideDozeScreenState(
+ supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState).setScreenState(supportedTargetState);
+ verify(mHolder.displayPowerState)
+ .setScreenState(supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
}
@Test
@@ -1495,7 +1502,7 @@
doAnswer(invocation -> {
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ }).when(mHolder.displayPowerState).setScreenState(anyInt(), anyInt());
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with DOZE.
@@ -1505,10 +1512,12 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- mHolder.dpc.overrideDozeScreenState(unSupportedTargetState);
+ mHolder.dpc.overrideDozeScreenState(
+ unSupportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ verify(mHolder.displayPowerState, never())
+ .setScreenState(anyInt(), eq(Display.STATE_REASON_DEFAULT_POLICY));
}
@Test
@@ -1520,7 +1529,7 @@
doAnswer(invocation -> {
when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
return null;
- }).when(mHolder.displayPowerState).setScreenState(anyInt());
+ }).when(mHolder.displayPowerState).setScreenState(anyInt(), anyInt());
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
// start with OFF.
@@ -1530,10 +1539,11 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- mHolder.dpc.overrideDozeScreenState(supportedTargetState);
+ mHolder.dpc.overrideDozeScreenState(
+ supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt(), anyInt());
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index f5c6bb2..de53266 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -18,11 +18,13 @@
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
+import android.util.Pair;
import android.view.Display;
import androidx.test.filters.SmallTest;
@@ -61,9 +63,11 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
- !DISPLAY_IN_TRANSITION);
- assertEquals(Display.STATE_OFF, state);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_OFF);
assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -101,9 +105,11 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest,
- !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
- assertEquals(Display.STATE_OFF, state);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -117,9 +123,11 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
- DISPLAY_IN_TRANSITION);
- assertEquals(Display.STATE_OFF, state);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, DISPLAY_IN_TRANSITION);
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -133,9 +141,11 @@
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
- !DISPLAY_IN_TRANSITION);
- assertEquals(Display.STATE_OFF, state);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
Display.STATE_ON);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -146,12 +156,15 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
- mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND);
+ mDisplayStateController.overrideDozeScreenState(
+ Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_OFFLOAD);
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
- !DISPLAY_IN_TRANSITION);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
- assertEquals(state, Display.STATE_DOZE_SUSPEND);
+ assertTrue(Display.STATE_DOZE_SUSPEND == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second);
}
@Test
@@ -159,12 +172,15 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
new DisplayManagerInternal.DisplayPowerRequest();
displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
- mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND);
+ mDisplayStateController.overrideDozeScreenState(
+ Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DEFAULT_POLICY);
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
- !DISPLAY_IN_TRANSITION);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
- assertEquals(state, Display.STATE_OFF);
+ assertTrue(Display.STATE_OFF == stateAndReason.first);
+ assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
}
private void validDisplayState(int policy, int displayState, boolean isEnabled,
@@ -172,9 +188,10 @@
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
displayPowerRequest.policy = policy;
- int state = mDisplayStateController.updateDisplayState(displayPowerRequest, isEnabled,
- isInTransition);
- assertEquals(displayState, state);
+ Pair<Integer, Integer> stateAndReason =
+ mDisplayStateController.updateDisplayState(
+ displayPowerRequest, isEnabled, isInTransition);
+ assertTrue(displayState == stateAndReason.first);
verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
displayState);
assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index a776eec..94b8d68 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -260,6 +260,7 @@
/* useProximitySensor= */ false,
/* boostScreenBrightness= */ false,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -299,6 +300,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -337,6 +339,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -374,6 +377,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -411,6 +415,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -449,6 +454,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -485,6 +491,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -522,6 +529,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
@@ -558,6 +566,7 @@
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
+ /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
/* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
/* overrideDrawWakeLock= */ false,
powerSaveState,
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index f86ff14..52f28e9 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1293,7 +1293,10 @@
// Override the display state by DreamManager and verify is reacquires the blocker.
mService.getLocalServiceInstance()
- .setDozeOverrideFromDreamManager(Display.STATE_ON, PowerManager.BRIGHTNESS_DEFAULT);
+ .setDozeOverrideFromDreamManager(
+ Display.STATE_ON,
+ Display.STATE_REASON_DEFAULT_POLICY,
+ PowerManager.BRIGHTNESS_DEFAULT);
assertTrue(isAcquired[0]);
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 65986ea..94a71be 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -270,3 +270,89 @@
"done && " +
"$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
}
+
+FLAKY_AND_IGNORED = [
+ "androidx.test.filters.FlakyTest",
+ "org.junit.Ignore",
+]
+// Used by content protection TEST_MAPPING
+test_module_config {
+ name: "FrameworksServicesTests_contentprotection",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.contentprotection"],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+ name: "FrameworksServicesTests_om",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.om."],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// Used by contexthub TEST_MAPPING
+test_module_config {
+ name: "FrameworksServicesTests_contexthub_presubmit",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.location.contexthub."],
+ // TODO(ron): are these right, does it run anything?
+ include_annotations: ["android.platform.test.annotations.Presubmit"],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+ name: "FrameworksServicesTests_contexthub_postsubmit",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.location.contexthub."],
+ // TODO(ron): are these right, does it run anything?
+ include_annotations: ["android.platform.test.annotations.Postsubmit"],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// Used by contentcapture
+test_module_config {
+ name: "FrameworksServicesTests_contentcapture",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.contentcapture"],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+ name: "FrameworksServicesTests_recoverysystem",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.recoverysystem."],
+ exclude_annotations: ["androidx.test.filters.FlakyTest"],
+}
+
+// server pm TEST_MAPPING
+test_module_config {
+ name: "FrameworksServicesTests_pm_presubmit",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_annotations: ["android.platform.test.annotations.Presubmit"],
+ include_filters: ["com.android.server.pm."],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+ name: "FrameworksServicesTests_pm_postsubmit",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_annotations: ["android.platform.test.annotations.Postsubmit"],
+ include_filters: ["com.android.server.pm."],
+ exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+// server os TEST_MAPPING
+test_module_config {
+ name: "FrameworksServicesTests_os",
+ base: "FrameworksServicesTests",
+ test_suites: ["general-tests"],
+ include_filters: ["com.android.server.os."],
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 1a51c45..58567ca 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -1272,7 +1272,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsEnabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void onFullscreenMagnificationActivationState_systemUiBorderFlagOn_notifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
TEST_DISPLAY, /* activated= */ true);
@@ -1282,7 +1282,7 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void
onFullscreenMagnificationActivationState_systemUiBorderFlagOff_neverNotifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
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 527bc5b..f6da411 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
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.eq;
@@ -29,7 +30,9 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -72,6 +75,8 @@
@Mock
private BaseClientMonitor mNonInterruptableClientMonitor;
@Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
private ClientMonitorCallback mClientCallback;
@Mock
private ClientMonitorCallback mOnStartCallback;
@@ -435,17 +440,18 @@
}
@Test
- public void cancelWatchdogWhenStarted() {
+ public void cancelWatchdogWhenStarted() throws RemoteException {
cancelWatchdog(true);
}
@Test
- public void cancelWatchdogWithoutStarting() {
+ public void cancelWatchdogWithoutStarting() throws RemoteException {
cancelWatchdog(false);
}
- private void cancelWatchdog(boolean start) {
+ private void cancelWatchdog(boolean start) throws RemoteException {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getListener()).thenReturn(mListener);
mInterruptableOperation.start(mOnStartCallback);
if (start) {
@@ -461,6 +467,8 @@
assertThat(mInterruptableOperation.isFinished()).isTrue();
assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ verify(mInterruptableClientMonitor.getListener()).onError(anyInt(), anyInt(), eq(
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0));
verify(mOnStartCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(false));
verify(mInterruptableClientMonitor).destroy();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 981eba5..971323a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -135,6 +135,8 @@
private ISession mSession;
@Mock
private IFingerprint mFingerprint;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
@Before
public void setUp() {
@@ -206,7 +208,7 @@
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
+ createBaseClientMonitor(), mock(ClientMonitorCallback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
assertEquals(1, mScheduler.mPendingOperations.size());
@@ -244,7 +246,7 @@
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
+ createBaseClientMonitor(), mock(ClientMonitorCallback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
assertEquals(1, mScheduler.mPendingOperations.size());
@@ -612,10 +614,10 @@
@Test
public void testInterruptPrecedingClients_whenExpected() {
- final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interruptableMonitor = createBaseClientMonitor();
when(interruptableMonitor.isInterruptable()).thenReturn(true);
- final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interrupter = createBaseClientMonitor();
when(interrupter.interruptsPrecedingClients()).thenReturn(true);
mScheduler.scheduleClientMonitor(interruptableMonitor);
@@ -628,10 +630,10 @@
@Test
public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
- final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interruptableMonitor = createBaseClientMonitor();
when(interruptableMonitor.isInterruptable()).thenReturn(true);
- final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interrupter = createBaseClientMonitor();
when(interrupter.interruptsPrecedingClients()).thenReturn(false);
mScheduler.scheduleClientMonitor(interruptableMonitor);
@@ -741,7 +743,7 @@
//Start watchdog
mScheduler.startWatchdog();
waitForIdle();
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
waitForIdle();
@@ -775,9 +777,9 @@
//Start watchdog
mScheduler.startWatchdog();
waitForIdle();
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
waitForIdle();
@@ -857,7 +859,7 @@
public void testScheduleOperation_whenNoUser() {
mCurrentUserId = UserHandle.USER_NULL;
- final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ final BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(0);
mScheduler.scheduleClientMonitor(nextClient);
@@ -875,9 +877,9 @@
mStartOperationsFinish = false;
final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class)
+ createBaseClientMonitor(),
+ createBaseClientMonitor(),
+ createBaseClientMonitor()
};
for (BaseClientMonitor client : nextClients) {
when(client.getTargetUserId()).thenReturn(5);
@@ -899,7 +901,7 @@
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
- final BaseClientMonitor client = mock(BaseClientMonitor.class);
+ final BaseClientMonitor client = createBaseClientMonitor();
when(client.getTargetUserId()).thenReturn(5);
@@ -913,7 +915,7 @@
assertThat(mScheduler.mCurrentOperation).isNull();
final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
+ createBaseClientMonitor(), new ClientMonitorCallback() {});
mScheduler.mCurrentOperation = fakeOperation;
startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -925,7 +927,7 @@
public void testScheduleOperation_whenSameUser() {
mCurrentUserId = 10;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
mScheduler.scheduleClientMonitor(nextClient);
@@ -943,7 +945,7 @@
mCurrentUserId = 10;
final int nextUserId = 11;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(nextUserId);
mScheduler.scheduleClientMonitor(nextClient);
@@ -963,7 +965,7 @@
public void testStartUser_alwaysStartsNextOperation() {
mCurrentUserId = UserHandle.USER_NULL;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(10);
mScheduler.scheduleClientMonitor(nextClient);
@@ -977,7 +979,7 @@
// schedule second operation but swap out the current operation
// before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
+ nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(11);
mScheduler.scheduleClientMonitor(nextClient);
@@ -994,7 +996,7 @@
// When a stop user client fails, check that mStopUserClient
// is set to null to prevent the scheduler from getting stuck.
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(10);
mScheduler.scheduleClientMonitor(nextClient);
@@ -1008,7 +1010,7 @@
// schedule second operation but swap out the current operation
// before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
+ nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(11);
mShouldFailStopUser = true;
mScheduler.scheduleClientMonitor(nextClient);
@@ -1023,6 +1025,13 @@
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
+ private BaseClientMonitor createBaseClientMonitor() {
+ BaseClientMonitor client = mock(BaseClientMonitor.class);
+ when(client.getListener()).thenReturn(mListener);
+
+ return client;
+ }
+
private void waitForIdle() {
TestableLooper.get(this).processAllMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
index 0ffa891..dae8f93 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -14,5 +14,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_contentcapture"
+ }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
index 419508c..32729a8 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -14,5 +14,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_contentprotection"
+ }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index 6035250..dc8f934 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -20,12 +20,18 @@
],
"postsubmit": [
{
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_contexthub_presubmit"
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
"include-filter": "com.android.server.location.contexthub."
},
{
+ // I believe this include annotation is preventing tests from being run
+ // as there are no matching tests with the Postsubmit annotation.
"include-annotation": "android.platform.test.annotations.Postsubmit"
},
{
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 558e259..41c4383 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -16,5 +16,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_om"
+ }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 5a46f8c4..06e7002 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -8,5 +8,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_os"
+ }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index 85a73bb..f4e724f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -20,21 +20,13 @@
],
"postsubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.pm."
- },
- {
- "include-annotation": "android.platform.test.annotations.Postsubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ // Presubmit is intentional here while testing with SLO checker.
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_pm_presubmit"
+ },
+ {
+ // Leave postsubmit here when migrating
+ "name": "FrameworksServicesTests_pm_postsubmit"
}
]
}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index e9d8b2e..7e7393c 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -11,5 +11,11 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ // b/331020193, Move to presubmit early april 2024
+ "name": "FrameworksServicesTests_recoverysystem"
+ }
]
}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index c4d2460..6f07472 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -3634,12 +3634,11 @@
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
- // Event 1: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
-
// Create bedtime rule
+ // This one has INTERRUPTION_FILTER_ALL to make sure active rules still count when zen mode
+ // (in the notification filtering sense) is not on
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.setType(TYPE_BEDTIME)
.build();
String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime, UPDATE_ORIGIN_APP,
@@ -3652,17 +3651,21 @@
String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive, UPDATE_ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
- // Event 2: Activate bedtime rule
+ // Event 1: Activate bedtime rule. This doesn't turn on notification filtering
mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ // Event 2: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+
// Event 3: Turn immersive on
mZenModeHelper.setAutomaticZenRuleState(immersiveId,
new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
- // Event 4: Turn off bedtime mode, leaving just unknown + immersive
+ // Event 4: Turn off bedtime mode, leaving just manual + immersive
mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
@@ -3670,19 +3673,21 @@
// Total of 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
- // First event: DND_TURNED_ON; active rules: 1; type is ACTIVE_RULE_TYPE_MANUAL
+ // First event: active rules changed; active rules: 1; type is ACTIVE_RULE_TYPE_MANUAL
assertThat(mZenModeEventLogger.getEventId(0)).isEqualTo(
- ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId());
+ ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId());
assertThat(mZenModeEventLogger.getChangedRuleType(0)).isEqualTo(
- DNDProtoEnums.MANUAL_RULE);
+ DNDProtoEnums.AUTOMATIC_RULE);
assertThat(mZenModeEventLogger.getNumRulesActive(0)).isEqualTo(1);
int[] ruleTypes0 = mZenModeEventLogger.getActiveRuleTypes(0);
assertThat(ruleTypes0.length).isEqualTo(1);
- assertThat(ruleTypes0[0]).isEqualTo(ACTIVE_RULE_TYPE_MANUAL);
+ assertThat(ruleTypes0[0]).isEqualTo(TYPE_BEDTIME);
// Second event: active rules: 2; types are TYPE_MANUAL and TYPE_BEDTIME
+ assertThat(mZenModeEventLogger.getEventId(1)).isEqualTo(
+ ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId());
assertThat(mZenModeEventLogger.getChangedRuleType(1)).isEqualTo(
- DNDProtoEnums.AUTOMATIC_RULE);
+ DNDProtoEnums.MANUAL_RULE);
assertThat(mZenModeEventLogger.getNumRulesActive(1)).isEqualTo(2);
int[] ruleTypes1 = mZenModeEventLogger.getActiveRuleTypes(1);
assertThat(ruleTypes1.length).isEqualTo(2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 48b12f7..e37da20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1115,7 +1115,7 @@
argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
}
- @RequiresFlagsDisabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
@Test
public void testDrawMagnifiedViewport() {
final int displayId = mDisplayContent.mDisplayId;
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index d9cbea9..ed89190 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -181,7 +181,7 @@
// We only need to look at the broadcast events that occurred before
// this notification related event.
while (dispatchTimestampsMs.size() > 0
- && dispatchTimestampsMs.peekFirst() < timestampMs) {
+ && dispatchTimestampsMs.peekFirst() <= timestampMs) {
final long dispatchTimestampMs = dispatchTimestampsMs.peekFirst();
final long elapsedDurationMs = timestampMs - dispatchTimestampMs;
// Only increment the counts if the broadcast was sent not too long ago, as
diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp
new file mode 100644
index 0000000..236d704
--- /dev/null
+++ b/test-legacy/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2018 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+java_library {
+ name: "android.test.legacy",
+ sdk_version: "current",
+ libs: [
+ "android.test.mock.stubs",
+ "junit",
+ ],
+ static_libs: [
+ "android.test.base-minus-junit",
+ "android.test.runner-minus-junit",
+ ],
+ dist: {
+ targets: [
+ "sdk",
+ ],
+ },
+}
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
deleted file mode 100644
index da9dc25..0000000
--- a/test-legacy/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Copyright (C) 2018 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
-ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
-
-# Build the android.test.legacy library
-# =====================================
-# Built against the SDK so that it can be statically included in APKs
-# without breaking link type checks.
-#
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := android.test.legacy
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_LIBRARIES := junit android.test.mock.stubs
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android.test.base-minus-junit \
- android.test.runner-minus-junit \
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-$(call declare-license-metadata,$(full_classes_jar),\
- SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS,\
- notice,$(LOCAL_PATH)/../NOTICE,Android,frameworks/base)
-
-# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
-
-endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true