Merge "Add a reason the smart suggestion isn't shown to the layout params."
diff --git a/Android.bp b/Android.bp
index 88c01e7..778aa55 100644
--- a/Android.bp
+++ b/Android.bp
@@ -384,6 +384,9 @@
"//frameworks/base/packages/Tethering/tests/unit",
"//packages/modules/Connectivity/Tethering/tests/unit",
],
+ lint: {
+ extra_check_modules: ["AndroidFrameworkLintChecker"],
+ },
errorprone: {
javacflags: [
"-Xep:AndroidFrameworkBinderIdentity:ERROR",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 788bfe4..9749c80 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -28,6 +28,7 @@
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
+import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -63,6 +64,13 @@
private final PcConstants mPcConstants;
private final PcHandler mHandler;
+ // Note: when determining prefetch bit satisfaction, we mark the bit as satisfied for apps with
+ // active widgets assuming that any prefetch jobs are being used for the widget. However, we
+ // don't have a callback telling us when widget status changes, which is incongruent with the
+ // aforementioned assumption. This inconsistency _should_ be fine since any jobs scheduled
+ // before the widget is activated are definitely not for the widget and don't have to be updated
+ // to "satisfied=true".
+ private AppWidgetManager mAppWidgetManager;
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
@GuardedBy("mLock")
@@ -118,6 +126,11 @@
}
@Override
+ public void onSystemServicesReady() {
+ mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+ }
+
+ @Override
@GuardedBy("mLock")
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (jobStatus.getJob().isPrefetch()) {
@@ -298,11 +311,23 @@
// Mark a prefetch constraint as satisfied in the following scenarios:
// 1. The app is not open but it will be launched soon
// 2. The app is open and the job is already running (so we let it finish)
+ // 3. The app is not open but has an active widget (we can't tell if a widget displays
+ // status/data, so this assumes the prefetch job is to update the data displayed on
+ // the widget).
final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid());
final boolean satisfied;
if (!appIsOpen) {
- satisfied = willBeLaunchedSoonLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now);
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
+ satisfied = willBeLaunchedSoonLocked(userId, pkgName, now)
+ // At the time of implementation, isBoundWidgetPackage() results in a process ID
+ // check and then a lookup into a map. Calling the method here every time
+ // is based on the assumption that widgets won't change often and
+ // AppWidgetManager won't be a bottleneck, so having a local cache won't provide
+ // huge performance gains. If anything changes, we should reconsider having a
+ // local cache.
+ || (mAppWidgetManager != null
+ && mAppWidgetManager.isBoundWidgetPackage(pkgName, userId));
} else {
satisfied = mService.isCurrentlyRunningLocked(jobStatus);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4d94dc1..803b76a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9354,7 +9354,8 @@
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
@@ -9948,6 +9949,16 @@
package android.companion {
+ public final class AssociationInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.net.MacAddress getDeviceMacAddress();
+ method @Nullable public String getDeviceProfile();
+ method @Nullable public CharSequence getDisplayName();
+ method public int getId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
+ }
+
public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9960,6 +9971,7 @@
method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>);
method @NonNull public android.companion.AssociationRequest build();
method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
+ method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
@@ -9995,20 +10007,26 @@
}
public final class CompanionDeviceManager {
- method @RequiresPermission(value=android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
- method public void disassociate(@NonNull String);
- method @NonNull public java.util.List<java.lang.String> getAssociations();
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
+ method @Deprecated public void disassociate(@NonNull String);
+ method public void disassociate(int);
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
+ method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
- field public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+ field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
+ field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
}
public abstract static class CompanionDeviceManager.Callback {
ctor public CompanionDeviceManager.Callback();
- method public abstract void onDeviceFound(android.content.IntentSender);
- method public abstract void onFailure(CharSequence);
+ method public void onAssociationCreated(@NonNull android.companion.AssociationInfo);
+ method public void onAssociationPending(@NonNull android.content.IntentSender);
+ method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
+ method public abstract void onFailure(@Nullable CharSequence);
}
public abstract class CompanionDeviceService extends android.app.Service {
@@ -16511,11 +16529,13 @@
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
ctor public AdaptiveIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ ctor public AdaptiveIconDrawable(@Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable);
method public void draw(android.graphics.Canvas);
method public android.graphics.drawable.Drawable getBackground();
method public static float getExtraInsetFraction();
method public android.graphics.drawable.Drawable getForeground();
method public android.graphics.Path getIconMask();
+ method @Nullable public android.graphics.drawable.Drawable getMonochrome();
method public int getOpacity();
method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
method public void scheduleDrawable(@NonNull android.graphics.drawable.Drawable, @NonNull Runnable, long);
@@ -20684,6 +20704,7 @@
method public boolean isMicrophoneMute();
method public boolean isMusicActive();
method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+ method public boolean isRampingRingerEnabled();
method public boolean isSpeakerphoneOn();
method public boolean isStreamMute(int);
method public boolean isSurroundFormatEnabled(int);
@@ -32408,13 +32429,16 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationAttributes> CREATOR;
field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1; // 0x1
+ field public static final int USAGE_ACCESSIBILITY = 66; // 0x42
field public static final int USAGE_ALARM = 17; // 0x11
field public static final int USAGE_CLASS_ALARM = 1; // 0x1
field public static final int USAGE_CLASS_FEEDBACK = 2; // 0x2
field public static final int USAGE_CLASS_MASK = 15; // 0xf
+ field public static final int USAGE_CLASS_MEDIA = 3; // 0x3
field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0
field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41
field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32
+ field public static final int USAGE_MEDIA = 19; // 0x13
field public static final int USAGE_NOTIFICATION = 49; // 0x31
field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22
field public static final int USAGE_RINGTONE = 33; // 0x21
@@ -35596,7 +35620,7 @@
field public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
field public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
field public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
- field public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
+ field @Deprecated public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
field public static final String AUTO_TIME = "auto_time";
field public static final String AUTO_TIME_ZONE = "auto_time_zone";
field public static final String BLUETOOTH_ON = "bluetooth_on";
@@ -51154,6 +51178,7 @@
method public int getRecommendedTimeoutMillis(int, int);
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
+ method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 81d1c9e..ef59b1a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -256,6 +256,7 @@
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING";
field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
+ field public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission.REQUEST_COMPANION_SELF_MANAGED";
field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -2344,15 +2345,34 @@
package android.companion {
+ public final class AssociationInfo implements android.os.Parcelable {
+ method @NonNull public String getPackageName();
+ method public boolean isSelfManaged();
+ }
+
public final class AssociationRequest implements android.os.Parcelable {
+ method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isForceConfirmation();
+ method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isSelfManaged();
field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING";
field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
}
+ public static final class AssociationRequest.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setSelfManaged(boolean);
+ }
+
public final class CompanionDeviceManager {
+ method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void addOnAssociationsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
method @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public void associate(@NonNull String, @NonNull android.net.MacAddress, @NonNull byte[]);
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+ method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
+ }
+
+ public static interface CompanionDeviceManager.OnAssociationsChangedListener {
+ method public void onAssociationsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
}
}
@@ -12318,7 +12338,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
method public static long getMaxNumberVerificationTimeoutMillis();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
@@ -12326,10 +12346,13 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
method public int getSimApplicationState();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int, int);
method public int getSimCardState();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Collection<android.telephony.UiccSlotMapping> getSimSlotMapping();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ba0b6aa..4b7a9df 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1458,6 +1458,7 @@
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
+ method public void setRampingRingerEnabled(boolean);
}
public static final class AudioRecord.MetricsConstants {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c89e8b0..e420b7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,6 +29,7 @@
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -322,7 +323,7 @@
@UnsupportedAppUsage
private ContextImpl mSystemContext;
- private ContextImpl mSystemUiContext;
+ private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>();
@UnsupportedAppUsage
static volatile IPackageManager sPackageManager;
@@ -2641,22 +2642,26 @@
}
@Override
+ @NonNull
public ContextImpl getSystemUiContext() {
- synchronized (this) {
- if (mSystemUiContext == null) {
- mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext());
- }
- return mSystemUiContext;
- }
+ return getSystemUiContext(DEFAULT_DISPLAY);
}
/**
- * Create the context instance base on system resources & display information which used for UI.
+ * Gets the context instance base on system resources & display information which used for UI.
* @param displayId The ID of the display where the UI is shown.
* @see ContextImpl#createSystemUiContext(ContextImpl, int)
*/
- public ContextImpl createSystemUiContext(int displayId) {
- return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId);
+ @NonNull
+ public ContextImpl getSystemUiContext(int displayId) {
+ synchronized (this) {
+ ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId);
+ if (systemUiContext == null) {
+ systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
+ mDisplaySystemUiContexts.put(displayId, systemUiContext);
+ }
+ return systemUiContext;
+ }
}
public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
@@ -3775,7 +3780,7 @@
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
- if (id != Display.DEFAULT_DISPLAY) {
+ if (id != DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 756833a..9d5e971 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2638,7 +2638,10 @@
overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
mResources.getLoaders()));
context.mDisplay = display;
- context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
+ // Inherit context type if the container is from System or System UI context to bypass
+ // UI context check.
+ context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
+ ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_DISPLAY_CONTEXT;
// Display contexts and any context derived from a display context should always override
// the display that would otherwise be inherited from mToken (or the global configuration if
// mToken is null).
@@ -2691,7 +2694,8 @@
// Step 2. Create the base context of the window context, it will also create a Resources
// associated with the WindowTokenClient and set the token to the base context.
- final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display);
+ final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient,
+ display.getDisplayId());
// Step 3. Create a WindowContext instance and set it as the outer context of the base
// context to make the service obtained by #getSystemService(String) able to query
@@ -2716,9 +2720,7 @@
if (display == null) {
throw new IllegalArgumentException("Display must not be null");
}
- final ContextImpl tokenContext = createWindowContextBase(token, display);
- tokenContext.setResources(createWindowContextResources(tokenContext));
- return tokenContext;
+ return createWindowContextBase(token, display.getDisplayId());
}
/**
@@ -2726,13 +2728,13 @@
* window.
*
* @param token The token to associate with {@link Resources}
- * @param display The {@link Display} to associate with.
+ * @param displayId The ID of {@link Display} to associate with.
*
* @see #createWindowContext(Display, int, Bundle)
* @see #createTokenContext(IBinder, Display)
*/
@UiContext
- ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) {
+ ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) {
ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
mAttributionSource.getAttributionTag(),
mAttributionSource.getNext(),
@@ -2746,8 +2748,8 @@
baseContext.setResources(windowContextResources);
// Associate the display with window context resources so that configuration update from
// the server side will also apply to the display's metrics.
- baseContext.mDisplay = ResourcesManager.getInstance()
- .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
+ baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
+ windowContextResources);
return baseContext;
}
@@ -2983,6 +2985,18 @@
mContentCaptureOptions = options;
}
+ @Override
+ protected void finalize() throws Throwable {
+ // If mToken is a WindowTokenClient, the Context is usually associated with a
+ // WindowContainer. We should detach from WindowContainer when the Context is finalized
+ // if this Context is not a WindowContext. WindowContext finalization is handled in
+ // WindowContext class.
+ if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) {
+ ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded();
+ }
+ super.finalize();
+ }
+
@UnsupportedAppUsage
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
@@ -3003,22 +3017,13 @@
* @param displayId The ID of the display where the UI is shown.
*/
static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) {
- final LoadedApk packageInfo = systemContext.mPackageInfo;
- ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo,
- ContextParams.EMPTY, null, null, null, null, null, 0, null, null);
- context.setResources(createResources(null, packageInfo, null, displayId, null,
- packageInfo.getCompatibilityInfo(), null));
- context.updateDisplay(displayId);
+ final WindowTokenClient token = new WindowTokenClient();
+ final ContextImpl context = systemContext.createWindowContextBase(token, displayId);
+ token.attachContext(context);
+ token.attachToDisplayContent(displayId);
context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI;
- return context;
- }
- /**
- * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}.
- * Uses {@Code Display.DEFAULT_DISPLAY} as the target display.
- */
- static ContextImpl createSystemUiContext(ContextImpl systemContext) {
- return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY);
+ return context;
}
@UnsupportedAppUsage
@@ -3227,7 +3232,13 @@
@Override
public IBinder getWindowContextToken() {
- return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null;
+ switch (mContextType) {
+ case CONTEXT_TYPE_WINDOW_CONTEXT:
+ case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI:
+ return mToken;
+ default:
+ return null;
+ }
}
private void checkMode(int mode) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 57b3196..5cfe09e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3471,7 +3471,10 @@
* Setting custom, overly-complicated password requirements leads to passwords that are hard
* for users to remember and may not provide any security benefits given as Android uses
* hardware-backed throttling to thwart online and offline brute-forcing of the device's
- * screen lock.
+ * screen lock. Company-owned devices (fully-managed and organization-owned managed profile
+ * devices) are able to continue using this method, though it is recommended that
+ * {@link #setRequiredPasswordComplexity(int)} should be used instead.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param quality The new desired quality. One of {@link #PASSWORD_QUALITY_UNSPECIFIED},
* {@link #PASSWORD_QUALITY_BIOMETRIC_WEAK},
@@ -9279,7 +9282,7 @@
throwIfParentInstance("getPermittedInputMethodsForCurrentUser");
if (mService != null) {
try {
- return mService.getPermittedInputMethodsForCurrentUser();
+ return mService.getPermittedInputMethodsAsUser(UserHandle.myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9288,6 +9291,34 @@
}
/**
+ * Returns the list of input methods permitted.
+ *
+ * <p>When this method returns empty list means all input methods are allowed, if a non-empty
+ * list is returned it will contain the intersection of the permitted lists for any device or
+ * profile owners that apply to this user. It will also include any system input methods.
+ *
+ * @return List of input method package names.
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ android.Manifest.permission.MANAGE_USERS
+ }, conditional = true)
+ public @NonNull List<String> getPermittedInputMethods() {
+ throwIfParentInstance("getPermittedInputMethods");
+ List<String> result = null;
+ if (mService != null) {
+ try {
+ result = mService.getPermittedInputMethodsAsUser(myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return result != null ? result : Collections.emptyList();
+ }
+
+ /**
* Called by a profile owner of a managed profile to set the packages that are allowed to use
* a {@link android.service.notification.NotificationListenerService} in the primary user to
* see notifications from the managed profile. By default all packages are permitted by this
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index cf48594..b9fcdf5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -243,7 +243,7 @@
boolean setPermittedInputMethods(in ComponentName admin,in List<String> packageList, boolean parent);
List<String> getPermittedInputMethods(in ComponentName admin, boolean parent);
- List<String> getPermittedInputMethodsForCurrentUser();
+ List<String> getPermittedInputMethodsAsUser(int userId);
boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId, boolean parent);
boolean setPermittedCrossProfileNotificationListeners(in ComponentName admin, in List<String> packageList);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index a71cffe..ceab02f 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -96,6 +96,13 @@
String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
/**
+ * A shell command that enables telephony time zone fallback. See {@link
+ * com.android.server.timezonedetector.TimeZoneDetectorStrategy} for details.
+ * @hide
+ */
+ String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
+
+ /**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
*
* @hide
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 3e799de..08e0178 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
@@ -26,6 +28,8 @@
import android.os.RemoteException;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -709,33 +713,85 @@
* notification
* @return true, if the notification has been triggered successfully
* @throws IllegalArgumentException
+ *
+ * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice,
+ * BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe.
*/
+ @Deprecated
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean notifyCharacteristicChanged(BluetoothDevice device,
BluetoothGattCharacteristic characteristic, boolean confirm) {
+ return notifyCharacteristicChanged(device, characteristic, confirm,
+ characteristic.getValue()) == BluetoothStatusCodes.SUCCESS;
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ BluetoothStatusCodes.SUCCESS,
+ BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
+ BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION,
+ BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED,
+ BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
+ BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED,
+ BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,
+ BluetoothStatusCodes.ERROR_UNKNOWN
+ })
+ public @interface NotifyCharacteristicReturnValues{}
+
+ /**
+ * Send a notification or indication that a local characteristic has been
+ * updated.
+ *
+ * <p>A notification or indication is sent to the remote device to signal
+ * that the characteristic has been updated. This function should be invoked
+ * for every client that requests notifications/indications by writing
+ * to the "Client Configuration" descriptor for the given characteristic.
+ *
+ * @param device the remote device to receive the notification/indication
+ * @param characteristic the local characteristic that has been updated
+ * @param confirm {@code true} to request confirmation from the client (indication) or
+ * {@code false} to send a notification
+ * @param value the characteristic value
+ * @return whether the notification has been triggered successfully
+ * @throws IllegalArgumentException if the characteristic value or service is null
+ */
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @NotifyCharacteristicReturnValues
+ public int notifyCharacteristicChanged(@NonNull BluetoothDevice device,
+ @NonNull BluetoothGattCharacteristic characteristic, boolean confirm,
+ @NonNull byte[] value) {
if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
- if (mService == null || mServerIf == 0) return false;
+ if (mService == null || mServerIf == 0) {
+ return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
+ }
+ if (characteristic == null) {
+ throw new IllegalArgumentException("characteristic must not be null");
+ }
+ if (device == null) {
+ throw new IllegalArgumentException("device must not be null");
+ }
BluetoothGattService service = characteristic.getService();
- if (service == null) return false;
-
- if (characteristic.getValue() == null) {
- throw new IllegalArgumentException("Chracteristic value is empty. Use "
- + "BluetoothGattCharacteristic#setvalue to update");
+ if (service == null) {
+ throw new IllegalArgumentException("Characteristic must have a non-null service");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("Characteristic value must not be null");
}
try {
- mService.sendNotification(mServerIf, device.getAddress(),
+ return mService.sendNotification(mServerIf, device.getAddress(),
characteristic.getInstanceId(), confirm,
- characteristic.getValue(), mAttributionSource);
+ value, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
- return false;
+ throw e.rethrowFromSystemServer();
}
-
- return true;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 1655b62..db5b751 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -18,7 +18,6 @@
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.LocalSocket;
@@ -266,7 +265,7 @@
throw new IOException("bt socket acept failed");
}
- as.mPfd = new ParcelFileDescriptor(fds[0]);
+ as.mPfd = ParcelFileDescriptor.dup(fds[0]);
as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]);
as.mSocketIS = as.mSocket.getInputStream();
as.mSocketOS = as.mSocket.getOutputStream();
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index ab1eb1f..f70e662 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -15,29 +15,24 @@
*/
package android.companion;
-import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
- * A record indicating that a device with a given address was confirmed by the user to be
- * associated to a given companion app
- *
- * @hide
- * TODO(b/1979395): un-hide and rename to AssociationInfo when implementing public APIs that use
- * this class.
+ * Details for a specific "association" that has been established between an app and companion
+ * device.
+ * <p>
+ * An association gives an app the ability to interact with a companion device without needing to
+ * acquire broader runtime permissions. An association only exists after the user has confirmed that
+ * an app should have access to a companion device.
*/
public final class AssociationInfo implements Parcelable {
/**
@@ -45,15 +40,16 @@
* Disclosed to the clients (ie. companion applications) for referring to this record (eg. in
* {@code disassociate()} API call).
*/
- private final int mAssociationId;
+ private final int mId;
private final @UserIdInt int mUserId;
private final @NonNull String mPackageName;
- private final @NonNull List<DeviceId> mDeviceIds;
+ private final @Nullable MacAddress mDeviceMacAddress;
+ private final @Nullable CharSequence mDisplayName;
private final @Nullable String mDeviceProfile;
- private final boolean mManagedByCompanionApp;
+ private final boolean mSelfManaged;
private boolean mNotifyOnDeviceNearby;
private final long mTimeApprovedMs;
@@ -63,23 +59,28 @@
*
* @hide
*/
- public AssociationInfo(int associationId, @UserIdInt int userId, @NonNull String packageName,
- @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile,
- boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) {
- if (associationId <= 0) {
+ public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
+ long timeApprovedMs) {
+ if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
- validateDeviceIds(deviceIds);
+ if (macAddress == null && displayName == null) {
+ throw new IllegalArgumentException("MAC address and the Display Name must NOT be null "
+ + "at the same time");
+ }
- mAssociationId = associationId;
+ mId = id;
mUserId = userId;
mPackageName = packageName;
+ mDeviceMacAddress = macAddress;
+ mDisplayName = displayName;
mDeviceProfile = deviceProfile;
- mDeviceIds = new ArrayList<>(deviceIds);
- mManagedByCompanionApp = managedByCompanionApp;
+ mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mTimeApprovedMs = timeApprovedMs;
}
@@ -87,55 +88,66 @@
/**
* @return the unique ID of this association record.
*/
- public int getAssociationId() {
- return mAssociationId;
+ public int getId() {
+ return mId;
}
- /** @hide */
- public int getUserId() {
+ /**
+ * @return the ID of the user who "owns" this association.
+ * @hide
+ */
+ public @UserIdInt int getUserId() {
return mUserId;
}
- /** @hide */
+ /**
+ * @return the package name of the app which this association refers to.
+ * @hide
+ */
+ @SystemApi
public @NonNull String getPackageName() {
return mPackageName;
}
/**
- * @return list of the device's IDs. At any time a device has at least 1 ID.
+ * @return the MAC address of the device.
*/
- public @NonNull List<DeviceId> getDeviceIds() {
- return Collections.unmodifiableList(mDeviceIds);
- }
-
- /**
- * @param type type of the ID.
- * @return ID of the type if the device has such ID, {@code null} otherwise.
- */
- public @Nullable String getIdOfType(@NonNull String type) {
- for (int i = mDeviceIds.size() - 1; i >= 0; i--) {
- final DeviceId id = mDeviceIds.get(i);
- if (Objects.equals(mDeviceIds.get(i).getType(), type)) return id.getValue();
- }
- return null;
+ public @Nullable MacAddress getDeviceMacAddress() {
+ return mDeviceMacAddress;
}
/** @hide */
- public @NonNull String getDeviceMacAddress() {
- return Objects.requireNonNull(getIdOfType(TYPE_MAC_ADDRESS),
- "MAC address of this device is not specified.");
+ public @Nullable String getDeviceMacAddressAsString() {
+ return mDeviceMacAddress != null ? mDeviceMacAddress.toString() : null;
}
/**
- * @return the profile of the device.
+ * @return the display name of the companion device (optionally) provided by the companion
+ * application.
+ *
+ * @see AssociationRequest.Builder#setDisplayName(CharSequence)
+ */
+ public @Nullable CharSequence getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * @return the companion device profile used when establishing this
+ * association, or {@code null} if no specific profile was used.
+ * @see AssociationRequest.Builder#setDeviceProfile(String)
*/
public @Nullable String getDeviceProfile() {
return mDeviceProfile;
}
- /** @hide */
- public boolean isManagedByCompanionApp() {
- return mManagedByCompanionApp;
+ /**
+ * @return whether the association is managed by the companion application it belongs to.
+ * @see AssociationRequest.Builder#setSelfManaged(boolean)
+ * @hide
+ */
+ @SystemApi
+ public boolean isSelfManaged() {
+ return mSelfManaged;
}
/**
@@ -161,15 +173,40 @@
return mUserId == userId && Objects.equals(mPackageName, packageName);
}
+ /**
+ * Utility method for checking if the association represents a device with the given MAC
+ * address.
+ *
+ * @return {@code false} if the association is "self-managed".
+ * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address.
+ * Otherwise - the result of {@link MacAddress#equals(Object)}
+ *
+ * @hide
+ */
+ public boolean isLinkedTo(@Nullable String addr) {
+ if (mSelfManaged) return false;
+
+ if (addr == null) return false;
+
+ final MacAddress macAddress;
+ try {
+ macAddress = MacAddress.fromString(addr);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ return macAddress.equals(mDeviceMacAddress);
+ }
+
@Override
public String toString() {
return "Association{"
- + "mAssociationId=" + mAssociationId
+ + "mId=" + mId
+ ", mUserId=" + mUserId
+ ", mPackageName='" + mPackageName + '\''
- + ", mDeviceIds=" + mDeviceIds
+ + ", mDeviceMacAddress=" + mDeviceMacAddress
+ + ", mDisplayName='" + mDisplayName + '\''
+ ", mDeviceProfile='" + mDeviceProfile + '\''
- + ", mManagedByCompanionApp=" + mManagedByCompanionApp
+ + ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ '}';
@@ -180,20 +217,21 @@
if (this == o) return true;
if (!(o instanceof AssociationInfo)) return false;
final AssociationInfo that = (AssociationInfo) o;
- return mAssociationId == that.mAssociationId
+ return mId == that.mId
&& mUserId == that.mUserId
- && mManagedByCompanionApp == that.mManagedByCompanionApp
+ && mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mTimeApprovedMs == that.mTimeApprovedMs
&& Objects.equals(mPackageName, that.mPackageName)
- && Objects.equals(mDeviceProfile, that.mDeviceProfile)
- && Objects.equals(mDeviceIds, that.mDeviceIds);
+ && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
+ && Objects.equals(mDisplayName, that.mDisplayName)
+ && Objects.equals(mDeviceProfile, that.mDeviceProfile);
}
@Override
public int hashCode() {
- return Objects.hash(mAssociationId, mUserId, mPackageName, mDeviceIds, mDeviceProfile,
- mManagedByCompanionApp, mNotifyOnDeviceNearby, mTimeApprovedMs);
+ return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
+ mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs);
}
@Override
@@ -203,33 +241,36 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mAssociationId);
+ dest.writeInt(mId);
dest.writeInt(mUserId);
dest.writeString(mPackageName);
- dest.writeParcelableList(mDeviceIds, 0);
+ dest.writeTypedObject(mDeviceMacAddress, 0);
+ dest.writeCharSequence(mDisplayName);
dest.writeString(mDeviceProfile);
- dest.writeBoolean(mManagedByCompanionApp);
+ dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeLong(mTimeApprovedMs);
}
private AssociationInfo(@NonNull Parcel in) {
- mAssociationId = in.readInt();
+ mId = in.readInt();
mUserId = in.readInt();
mPackageName = in.readString();
- mDeviceIds = in.readParcelableList(new ArrayList<>(), DeviceId.class.getClassLoader());
+ mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
+ mDisplayName = in.readCharSequence();
mDeviceProfile = in.readString();
- mManagedByCompanionApp = in.readBoolean();
+ mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
mTimeApprovedMs = in.readLong();
}
+ @NonNull
public static final Parcelable.Creator<AssociationInfo> CREATOR =
new Parcelable.Creator<AssociationInfo>() {
@Override
@@ -242,19 +283,4 @@
return new AssociationInfo(in);
}
};
-
- private static void validateDeviceIds(@NonNull List<DeviceId> ids) {
- if (ids.isEmpty()) throw new IllegalArgumentException("Device must have at least 1 id.");
-
- // Make sure none of the IDs are null, and they all have different types.
- final Set<String> types = new HashSet<>(ids.size());
- for (int i = ids.size() - 1; i >= 0; i--) {
- final DeviceId deviceId = ids.get(i);
- if (deviceId == null) throw new IllegalArgumentException("DeviceId must not be null");
- if (!types.add(deviceId.getType())) {
- throw new IllegalArgumentException(
- "DeviceId cannot have multiple IDs of the same type");
- }
- }
- }
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 7d1aabc..1dc161c 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,6 +16,8 @@
package android.companion;
+import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+
import static com.android.internal.util.CollectionUtils.emptyIfNull;
import android.Manifest;
@@ -58,9 +60,6 @@
genBuilder = false,
genConstDefs = false)
public final class AssociationRequest implements Parcelable {
-
- private static final String LOG_TAG = AssociationRequest.class.getSimpleName();
-
/**
* Device profile: watch.
*
@@ -116,7 +115,7 @@
/**
* Whether only a single device should match the provided filter.
*
- * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac
+ * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
* address, bonded devices are also searched among. This allows to obtain the necessary app
* privileges even if the device is already paired.
*/
@@ -134,6 +133,24 @@
private @Nullable @DeviceProfile String mDeviceProfile = null;
/**
+ * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+ * "self-managed" association.
+ */
+ private final @Nullable CharSequence mDisplayName;
+
+ /**
+ * Whether the association is to be managed by the companion application.
+ */
+ private final boolean mSelfManaged;
+
+ /**
+ * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
+ * confirmation from the user before creating an association, even if such confirmation is not
+ * required.
+ */
+ private final boolean mForceConfirmation;
+
+ /**
* The app package making the request.
*
* Populated by the system.
@@ -167,8 +184,30 @@
*/
private boolean mSkipPrompt = false;
- private void onConstructed() {
- mCreationTime = System.currentTimeMillis();
+ /**
+ * Whether the association is to be managed by the companion application.
+ *
+ * @see Builder#setSelfManaged(boolean)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ public boolean isSelfManaged() {
+ return mSelfManaged;
+ }
+
+ /**
+ * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
+ * confirmation from the user before creating an association, even if such confirmation is not
+ * required.
+ *
+ * @see Builder#setForceConfirmation(boolean)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ public boolean isForceConfirmation() {
+ return mForceConfirmation;
}
/** @hide */
@@ -199,20 +238,27 @@
return mDeviceFilters;
}
+ private void onConstructed() {
+ mCreationTime = System.currentTimeMillis();
+ }
+
/**
* A builder for {@link AssociationRequest}
*/
public static final class Builder extends OneTimeUseBuilder<AssociationRequest> {
private boolean mSingleDevice = false;
- @Nullable private ArrayList<DeviceFilter<?>> mDeviceFilters = null;
- private @Nullable String mDeviceProfile = null;
+ private @Nullable ArrayList<DeviceFilter<?>> mDeviceFilters = null;
+ private @Nullable String mDeviceProfile;
+ private @Nullable CharSequence mDisplayName;
+ private boolean mSelfManaged = false;
+ private boolean mForceConfirmation = false;
public Builder() {}
/**
* Whether only a single device should match the provided filter.
*
- * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac
+ * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
* address, bonded devices are also searched among. This allows to obtain the necessary app
* privileges even if the device is already paired.
*
@@ -249,14 +295,65 @@
return this;
}
+ /**
+ * Adds a display name.
+ * Generally {@link AssociationRequest}s are not required to provide a display name, except
+ * for request for creating "self-managed" associations, which MUST provide a display name.
+ *
+ * @param displayName the display name of the device.
+ */
+ @NonNull
+ public Builder setDisplayName(@NonNull CharSequence displayName) {
+ checkNotUsed();
+ mDisplayName = Objects.requireNonNull(displayName);
+ return this;
+ }
+
+ /**
+ * Indicate whether the association would be managed by the companion application.
+ *
+ * Requests for creating "self-managed" association MUST provide a Display name.
+ *
+ * @see #setDisplayName(CharSequence)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ @NonNull
+ public Builder setSelfManaged(boolean selfManaged) {
+ checkNotUsed();
+ mSelfManaged = selfManaged;
+ return this;
+ }
+
+ /**
+ * Indicates whether the application would prefer the CompanionDeviceManager to collect an
+ * explicit confirmation from the user before creating an association, even if such
+ * confirmation is not required.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ @NonNull
+ public Builder setForceConfirmation(boolean forceConfirmation) {
+ checkNotUsed();
+ mForceConfirmation = forceConfirmation;
+ return this;
+ }
+
/** @inheritDoc */
@NonNull
@Override
public AssociationRequest build() {
markUsed();
- return new AssociationRequest(
- mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, null, null, -1L, false);
+ if (mSelfManaged && mDisplayName == null) {
+ throw new IllegalStateException("Request for a self-managed association MUST "
+ + "provide the display name of the device");
+ }
+ return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation,
+ null, null, -1L, false);
}
}
@@ -283,13 +380,22 @@
* @param singleDevice
* Whether only a single device should match the provided filter.
*
- * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac
+ * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
* address, bonded devices are also searched among. This allows to obtain the necessary app
* privileges even if the device is already paired.
* @param deviceFilters
* If set, only devices matching either of the given filters will be shown to the user
* @param deviceProfile
* If set, association will be requested as a corresponding kind of device
+ * @param displayName
+ * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+ * "self-managed" association.
+ * @param selfManaged
+ * Whether the association is to be managed by the companion application.
+ * @param forceConfirmation
+ * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
+ * confirmation from the user before creating an association, even if such confirmation is not
+ * required.
* @param callingPackage
* The app package making the request.
*
@@ -311,6 +417,9 @@
boolean singleDevice,
@NonNull List<DeviceFilter<?>> deviceFilters,
@Nullable @DeviceProfile String deviceProfile,
+ @Nullable CharSequence displayName,
+ boolean selfManaged,
+ boolean forceConfirmation,
@Nullable String callingPackage,
@Nullable String deviceProfilePrivilegesDescription,
long creationTime,
@@ -322,6 +431,9 @@
this.mDeviceProfile = deviceProfile;
com.android.internal.util.AnnotationValidations.validate(
DeviceProfile.class, null, mDeviceProfile);
+ this.mDisplayName = displayName;
+ this.mSelfManaged = selfManaged;
+ this.mForceConfirmation = forceConfirmation;
this.mCallingPackage = callingPackage;
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
@@ -341,6 +453,17 @@
}
/**
+ * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+ * "self-managed" association.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
* The app package making the request.
*
* Populated by the system.
@@ -396,6 +519,9 @@
"singleDevice = " + mSingleDevice + ", " +
"deviceFilters = " + mDeviceFilters + ", " +
"deviceProfile = " + mDeviceProfile + ", " +
+ "displayName = " + mDisplayName + ", " +
+ "selfManaged = " + mSelfManaged + ", " +
+ "forceConfirmation = " + mForceConfirmation + ", " +
"callingPackage = " + mCallingPackage + ", " +
"deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " +
"creationTime = " + mCreationTime + ", " +
@@ -419,6 +545,9 @@
&& mSingleDevice == that.mSingleDevice
&& Objects.equals(mDeviceFilters, that.mDeviceFilters)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
+ && Objects.equals(mDisplayName, that.mDisplayName)
+ && mSelfManaged == that.mSelfManaged
+ && mForceConfirmation == that.mForceConfirmation
&& Objects.equals(mCallingPackage, that.mCallingPackage)
&& Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription)
&& mCreationTime == that.mCreationTime
@@ -435,6 +564,9 @@
_hash = 31 * _hash + Boolean.hashCode(mSingleDevice);
_hash = 31 * _hash + Objects.hashCode(mDeviceFilters);
_hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
+ _hash = 31 * _hash + Objects.hashCode(mDisplayName);
+ _hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
+ _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
_hash = 31 * _hash + Objects.hashCode(mCallingPackage);
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
_hash = 31 * _hash + Long.hashCode(mCreationTime);
@@ -448,15 +580,19 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
if (mSingleDevice) flg |= 0x1;
- if (mSkipPrompt) flg |= 0x40;
+ if (mSelfManaged) flg |= 0x10;
+ if (mForceConfirmation) flg |= 0x20;
+ if (mSkipPrompt) flg |= 0x200;
if (mDeviceProfile != null) flg |= 0x4;
- if (mCallingPackage != null) flg |= 0x8;
- if (mDeviceProfilePrivilegesDescription != null) flg |= 0x10;
- dest.writeByte(flg);
+ if (mDisplayName != null) flg |= 0x8;
+ if (mCallingPackage != null) flg |= 0x40;
+ if (mDeviceProfilePrivilegesDescription != null) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
+ if (mDisplayName != null) dest.writeCharSequence(mDisplayName);
if (mCallingPackage != null) dest.writeString(mCallingPackage);
if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
dest.writeLong(mCreationTime);
@@ -473,14 +609,17 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
boolean singleDevice = (flg & 0x1) != 0;
- boolean skipPrompt = (flg & 0x40) != 0;
+ boolean selfManaged = (flg & 0x10) != 0;
+ boolean forceConfirmation = (flg & 0x20) != 0;
+ boolean skipPrompt = (flg & 0x200) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader());
String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
- String callingPackage = (flg & 0x8) == 0 ? null : in.readString();
- String deviceProfilePrivilegesDescription = (flg & 0x10) == 0 ? null : in.readString();
+ CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
+ String callingPackage = (flg & 0x40) == 0 ? null : in.readString();
+ String deviceProfilePrivilegesDescription = (flg & 0x80) == 0 ? null : in.readString();
long creationTime = in.readLong();
this.mSingleDevice = singleDevice;
@@ -490,6 +629,9 @@
this.mDeviceProfile = deviceProfile;
com.android.internal.util.AnnotationValidations.validate(
DeviceProfile.class, null, mDeviceProfile);
+ this.mDisplayName = displayName;
+ this.mSelfManaged = selfManaged;
+ this.mForceConfirmation = forceConfirmation;
this.mCallingPackage = callingPackage;
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
@@ -513,10 +655,10 @@
};
@DataClass.Generated(
- time = 1635190605212L,
+ time = 1637228802427L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
+ inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 6719a69..2b12f12 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -16,23 +16,26 @@
package android.companion;
-import android.Manifest;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.app.Activity;
-import android.app.Application;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.net.MacAddress;
-import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -40,10 +43,16 @@
import android.util.ExceptionUtils;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.function.BiConsumer;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* System level service for managing companion devices
@@ -75,10 +84,21 @@
* <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
* <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
* </ul>
+ *
+ * @deprecated use {@link #EXTRA_ASSOCIATION} instead.
*/
+ @Deprecated
public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
/**
+ * Extra field name for the {@link AssociationInfo} object, included into
+ * {@link android.content.Intent} which application receive in
+ * {@link Activity#onActivityResult(int, int, Intent)} after the application's
+ * {@link AssociationRequest} was successfully processed and an association was created.
+ */
+ public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
+
+ /**
* The package name of the companion device discovery component.
*
* @hide
@@ -87,30 +107,121 @@
"com.android.companiondevicemanager";
/**
- * A callback to receive once at least one suitable device is found, or the search failed
- * (e.g. timed out)
+ * Callback for applications to receive updates about and the outcome of
+ * {@link AssociationRequest} issued via {@code associate()} call.
+ *
+ * <p>
+ * The {@link Callback#onAssociationPending(IntentSender)} is invoked after the
+ * {@link AssociationRequest} has been checked by the Companion Device Manager Service and is
+ * pending user's approval.
+ *
+ * The {@link IntentSender} received as an argument to
+ * {@link Callback#onAssociationPending(IntentSender)} "encapsulates" an {@link Activity}
+ * that has UI for the user to:
+ * <ul>
+ * <li>
+ * choose the device to associate the application with (if multiple eligible devices are
+ * available)
+ * </li>
+ * <li>confirm the association</li>
+ * <li>
+ * approve the privileges the application will be granted if the association is to be created
+ * </li>
+ * </ul>
+ *
+ * If the Companion Device Manager Service needs to scan for the devices, the {@link Activity}
+ * will also display the status and the progress of the scan.
+ *
+ * Note that Companion Device Manager Service will only start the scanning after the
+ * {@link Activity} was launched and became visible.
+ *
+ * Applications are expected to launch the UI using the received {@link IntentSender} via
+ * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}.
+ * </p>
+ *
+ * <p>
+ * Upon receiving user's confirmation Companion Device Manager Service will create an
+ * association and will send an {@link AssociationInfo} object that represents the created
+ * association back to the application both via
+ * {@link Callback#onAssociationCreated(AssociationInfo)} and
+ * via {@link Activity#setResult(int, Intent)}.
+ * In the latter the {@code resultCode} will be set to {@link Activity#RESULT_OK} and the
+ * {@code data} {@link Intent} will contain {@link AssociationInfo} extra named
+ * {@link #EXTRA_ASSOCIATION}.
+ * <pre>
+ * <code>
+ * if (resultCode == Activity.RESULT_OK) {
+ * AssociationInfo associationInfo = data.getParcelableExtra(EXTRA_ASSOCIATION);
+ * }
+ * </code>
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * If the Companion Device Manager Service is not able to create an association, it will
+ * invoke {@link Callback#onFailure(CharSequence)}.
+ *
+ * If this happened after the application has launched the UI (eg. the user chose to reject
+ * the association), the outcome will also be delivered to the applications via
+ * {@link Activity#setResult(int)} with the {@link Activity#RESULT_CANCELED}
+ * {@code resultCode}.
+ * </p>
+ *
+ * <p>
+ * Note that in some cases the Companion Device Manager Service may not need to collect
+ * user's approval for creating an association. In such cases, this method will not be
+ * invoked, and {@link #onAssociationCreated(AssociationInfo)} may be invoked right away.
+ * </p>
+ *
+ * @see #associate(AssociationRequest, Executor, Callback)
+ * @see #associate(AssociationRequest, Callback, Handler)
+ * @see #EXTRA_ASSOCIATION
*/
public abstract static class Callback {
+ /**
+ * @deprecated method was renamed to onAssociationPending() to provide better clarity; both
+ * methods are functionally equivalent and only one needs to be overridden.
+ *
+ * @see #onAssociationPending(IntentSender)
+ */
+ @Deprecated
+ public void onDeviceFound(@NonNull IntentSender intentSender) {}
/**
- * Called once at least one suitable device is found
+ * Invoked when the association needs to approved by the user.
*
- * @param chooserLauncher a {@link IntentSender} to launch the UI for user to select a
- * device
+ * Applications should launch the {@link Activity} "encapsulated" in {@code intentSender}
+ * {@link IntentSender} object by calling
+ * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}.
+ *
+ * @param intentSender an {@link IntentSender} which applications should use to launch
+ * the UI for the user to confirm the association.
*/
- public abstract void onDeviceFound(IntentSender chooserLauncher);
+ public void onAssociationPending(@NonNull IntentSender intentSender) {
+ onDeviceFound(intentSender);
+ }
/**
- * Called if there was an error looking for device(s)
+ * Invoked when the association is created.
*
- * @param error the cause of the error
+ * @param associationInfo contains details of the newly-established association.
*/
- public abstract void onFailure(CharSequence error);
+ public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
+
+ /**
+ * Invoked if the association could not be created.
+ *
+ * @param error error message.
+ */
+ public abstract void onFailure(@Nullable CharSequence error);
}
private final ICompanionDeviceManager mService;
private Context mContext;
+ @GuardedBy("mListeners")
+ private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>();
+
/** @hide */
public CompanionDeviceManager(
@Nullable ICompanionDeviceManager service, @NonNull Context context) {
@@ -119,59 +230,109 @@
}
/**
- * Associate this app with a companion device, selected by user
+ * Request to associate this app with a companion device.
*
- * <p>Once at least one appropriate device is found, {@code callback} will be called with a
- * {@link PendingIntent} that can be used to show the list of available devices for the user
- * to select.
- * It should be started for result (i.e. using
- * {@link android.app.Activity#startIntentSenderForResult}), as the resulting
- * {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected
- * device. (e.g. {@link android.bluetooth.BluetoothDevice})</p>
+ * <p>Note that before creating establishing association the system may need to show UI to
+ * collect user confirmation.</p>
*
- * <p>If your app needs to be excluded from battery optimizations (run in the background)
- * or to have unrestricted data access (use data in the background) you can declare that
- * you use the {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and {@link
- * android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} respectively. Note that these
- * special capabilities have a negative effect on the device's battery and user's data
- * usage, therefore you should request them when absolutely necessary.</p>
+ * <p>If the app needs to be excluded from battery optimizations (run in the background)
+ * or to have unrestricted data access (use data in the background) it should declare use of
+ * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and
+ * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its
+ * AndroidManifest.xml respectively.
+ * Note that these special capabilities have a negative effect on the device's battery and
+ * user's data usage, therefore you should request them when absolutely necessary.</p>
*
- * <p>You can call {@link #getAssociations} to get the list of currently associated
- * devices, and {@link #disassociate} to remove an association. Consider doing so when the
- * association is no longer relevant to avoid unnecessary battery and/or data drain resulting
- * from special privileges that the association provides</p>
+ * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently
+ * {@link AssociationInfo} objects, that represent their existing associations.
+ * Applications can also use {@link #disassociate(int)} to remove an association, and are
+ * recommended to do when an association is no longer relevant to avoid unnecessary battery
+ * and/or data drain resulting from special privileges that the association provides</p>
*
* <p>Calling this API requires a uses-feature
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
+ **
+ * @param request A request object that describes details of the request.
+ * @param callback The callback used to notify application when the association is created.
+ * @param handler The handler which will be used to invoke the callback.
*
- * <p>When using {@link AssociationRequest#DEVICE_PROFILE_WATCH watch}
- * {@link AssociationRequest.Builder#setDeviceProfile profile}, caller must also hold
- * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH}</p>
- *
- * @param request specific details about this request
- * @param callback will be called once there's at least one device found for user to choose from
- * @param handler A handler to control which thread the callback will be delivered on, or null,
- * to deliver it on main thread
- *
- * @see AssociationRequest
+ * @see AssociationRequest.Builder
+ * @see #getMyAssociations()
+ * @see #disassociate(int)
+ * @see #associate(AssociationRequest, Executor, Callback)
*/
- @RequiresPermission(
- value = Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH,
- conditional = true)
+ @UserHandleAware
+ @RequiresPermission(anyOf = {
+ REQUEST_COMPANION_PROFILE_WATCH,
+ REQUEST_COMPANION_PROFILE_APP_STREAMING,
+ REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION,
+ }, conditional = true)
public void associate(
@NonNull AssociationRequest request,
@NonNull Callback callback,
@Nullable Handler handler) {
- if (!checkFeaturePresent()) {
- return;
- }
+ if (!checkFeaturePresent()) return;
Objects.requireNonNull(request, "Request cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
+ handler = Handler.mainIfNull(handler);
+
try {
- mService.associate(
- request,
- new CallbackProxy(request, callback, Handler.mainIfNull(handler)),
- getCallingPackage());
+ mService.associate(request, new AssociationRequestCallbackProxy(handler, callback),
+ mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request to associate this app with a companion device.
+ *
+ * <p>Note that before creating establishing association the system may need to show UI to
+ * collect user confirmation.</p>
+ *
+ * <p>If the app needs to be excluded from battery optimizations (run in the background)
+ * or to have unrestricted data access (use data in the background) it should declare use of
+ * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and
+ * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its
+ * AndroidManifest.xml respectively.
+ * Note that these special capabilities have a negative effect on the device's battery and
+ * user's data usage, therefore you should request them when absolutely necessary.</p>
+ *
+ * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently
+ * {@link AssociationInfo} objects, that represent their existing associations.
+ * Applications can also use {@link #disassociate(int)} to remove an association, and are
+ * recommended to do when an association is no longer relevant to avoid unnecessary battery
+ * and/or data drain resulting from special privileges that the association provides</p>
+ *
+ * <p>Calling this API requires a uses-feature
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
+ **
+ * @param request A request object that describes details of the request.
+ * @param executor The executor which will be used to invoke the callback.
+ * @param callback The callback used to notify application when the association is created.
+ *
+ * @see AssociationRequest.Builder
+ * @see #getMyAssociations()
+ * @see #disassociate(int)
+ */
+ @UserHandleAware
+ @RequiresPermission(anyOf = {
+ REQUEST_COMPANION_PROFILE_WATCH,
+ REQUEST_COMPANION_PROFILE_APP_STREAMING,
+ REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION
+ }, conditional = true)
+ public void associate(
+ @NonNull AssociationRequest request,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ if (!checkFeaturePresent()) return;
+ Objects.requireNonNull(request, "Request cannot be null");
+ Objects.requireNonNull(executor, "Executor cannot be null");
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ try {
+ mService.associate(request, new AssociationRequestCallbackProxy(executor, callback),
+ mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -182,15 +343,32 @@
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
*
* @return a list of MAC addresses of devices that have been previously associated with the
- * current app. You can use these with {@link #disassociate}
+ * current app are managed by CompanionDeviceManager (ie. does not include devices managed by
+ * application itself even if they have a MAC address).
+ *
+ * @deprecated use {@link #getMyAssociations()}
*/
+ @Deprecated
+ @UserHandleAware
@NonNull
public List<String> getAssociations() {
- if (!checkFeaturePresent()) {
- return Collections.emptyList();
- }
+ return CollectionUtils.mapNotNull(getMyAssociations(),
+ a -> a.isSelfManaged() ? null : a.getDeviceMacAddressAsString());
+ }
+
+ /**
+ * <p>Calling this API requires a uses-feature
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
+ *
+ * @return a list of associations that have been previously associated with the current app.
+ */
+ @UserHandleAware
+ @NonNull
+ public List<AssociationInfo> getMyAssociations() {
+ if (!checkFeaturePresent()) return Collections.emptyList();
+
try {
- return mService.getAssociations(getCallingPackage(), mContext.getUserId());
+ return mService.getAssociations(mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -209,13 +387,41 @@
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
*
* @param deviceMacAddress the MAC address of device to disassociate from this app
+ *
+ * @deprecated use {@link #disassociate(int)}
*/
+ @UserHandleAware
+ @Deprecated
public void disassociate(@NonNull String deviceMacAddress) {
- if (!checkFeaturePresent()) {
- return;
- }
+ if (!checkFeaturePresent()) return;
+
try {
- mService.disassociate(deviceMacAddress, getCallingPackage());
+ mService.legacyDisassociate(deviceMacAddress, mContext.getOpPackageName(),
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an association.
+ *
+ * <p>Any privileges provided via being associated with a given device will be revoked</p>
+ *
+ * <p>Calling this API requires a uses-feature
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
+ *
+ * @param associationId id of the association to be removed.
+ *
+ * @see #associate(AssociationRequest, Executor, Callback)
+ * @see AssociationInfo#getId()
+ */
+ @UserHandleAware
+ public void disassociate(int associationId) {
+ if (!checkFeaturePresent()) return;
+
+ try {
+ mService.disassociate(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -234,12 +440,14 @@
* <p>Calling this API requires a uses-feature
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
*/
+ @UserHandleAware
public void requestNotificationAccess(ComponentName component) {
if (!checkFeaturePresent()) {
return;
}
try {
- IntentSender intentSender = mService.requestNotificationAccess(component)
+ IntentSender intentSender = mService
+ .requestNotificationAccess(component, mContext.getUserId())
.getIntentSender();
mContext.startIntentSender(intentSender, null, 0, 0, 0);
} catch (RemoteException e) {
@@ -304,9 +512,7 @@
@NonNull String packageName,
@NonNull MacAddress macAddress,
@NonNull UserHandle user) {
- if (!checkFeaturePresent()) {
- return false;
- }
+ if (!checkFeaturePresent()) return false;
Objects.requireNonNull(packageName, "package name cannot be null");
Objects.requireNonNull(macAddress, "mac address cannot be null");
Objects.requireNonNull(user, "user cannot be null");
@@ -322,21 +528,91 @@
* Gets all package-device {@link AssociationInfo}s for the current user.
*
* @return the associations list
+ * @see #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)
+ * @see #removeOnAssociationsChangedListener(OnAssociationsChangedListener)
* @hide
*/
+ @SystemApi
+ @UserHandleAware
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public @NonNull List<AssociationInfo> getAllAssociations() {
- if (!checkFeaturePresent()) {
- return Collections.emptyList();
- }
+ if (!checkFeaturePresent()) return Collections.emptyList();
try {
- return mService.getAssociationsForUser(mContext.getUser().getIdentifier());
+ return mService.getAllAssociationsForUser(mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
+ * Listener for any changes to {@link AssociationInfo}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface OnAssociationsChangedListener {
+ /**
+ * Invoked when a change occurs to any of the associations for the user (including adding
+ * new associations and removing existing associations).
+ *
+ * @param associations all existing associations for the user (after the change).
+ */
+ void onAssociationsChanged(@NonNull List<AssociationInfo> associations);
+ }
+
+ /**
+ * Register listener for any changes to {@link AssociationInfo}.
+ *
+ * @see #getAllAssociations()
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+ public void addOnAssociationsChangedListener(
+ @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) {
+ if (!checkFeaturePresent()) return;
+ synchronized (mListeners) {
+ final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy(
+ executor, listener);
+ try {
+ mService.addOnAssociationsChangedListener(proxy, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mListeners.add(proxy);
+ }
+ }
+
+ /**
+ * Unregister listener for any changes to {@link AssociationInfo}.
+ *
+ * @see #getAllAssociations()
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+ public void removeOnAssociationsChangedListener(
+ @NonNull OnAssociationsChangedListener listener) {
+ if (!checkFeaturePresent()) return;
+ synchronized (mListeners) {
+ final Iterator<OnAssociationsChangedListenerProxy> iterator = mListeners.iterator();
+ while (iterator.hasNext()) {
+ final OnAssociationsChangedListenerProxy proxy = iterator.next();
+ if (proxy.mListener == listener) {
+ try {
+ mService.removeOnAssociationsChangedListener(proxy, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ /**
* Checks whether the bluetooth device represented by the mac address was recently associated
* with the companion app. This allows these devices to skip the Bluetooth pairing dialog if
* their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}.
@@ -404,8 +680,8 @@
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.registerDevicePresenceListenerService(
- mContext.getPackageName(), deviceAddress);
+ mService.registerDevicePresenceListenerService(deviceAddress,
+ mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
throw e.rethrowFromSystemServer();
@@ -437,8 +713,8 @@
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.unregisterDevicePresenceListenerService(
- mContext.getPackageName(), deviceAddress);
+ mService.unregisterDevicePresenceListenerService(deviceAddress,
+ mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
}
@@ -509,78 +785,63 @@
return featurePresent;
}
- private Activity getActivity() {
- return (Activity) mContext;
- }
+ private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub {
+ private final Handler mHandler;
+ private final Callback mCallback;
+ private final Executor mExecutor;
- private String getCallingPackage() {
- return mContext.getPackageName();
- }
-
- private class CallbackProxy extends IFindDeviceCallback.Stub
- implements Application.ActivityLifecycleCallbacks {
-
- private Callback mCallback;
- private Handler mHandler;
- private AssociationRequest mRequest;
-
- final Object mLock = new Object();
-
- private CallbackProxy(AssociationRequest request, Callback callback, Handler handler) {
+ private AssociationRequestCallbackProxy(
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mExecutor = executor;
+ mHandler = null;
mCallback = callback;
+ }
+
+ private AssociationRequestCallbackProxy(
+ @NonNull Handler handler, @NonNull Callback callback) {
mHandler = handler;
- mRequest = request;
- getActivity().getApplication().registerActivityLifecycleCallbacks(this);
+ mExecutor = null;
+ mCallback = callback;
}
@Override
- public void onSuccess(PendingIntent launcher) {
- lockAndPost(Callback::onDeviceFound, launcher.getIntentSender());
+ public void onAssociationPending(@NonNull PendingIntent pi) {
+ execute(mCallback::onAssociationPending, pi.getIntentSender());
}
@Override
- public void onFailure(CharSequence reason) {
- lockAndPost(Callback::onFailure, reason);
+ public void onAssociationCreated(@NonNull AssociationInfo association) {
+ execute(mCallback::onAssociationCreated, association);
}
- <T> void lockAndPost(BiConsumer<Callback, T> action, T payload) {
- synchronized (mLock) {
- if (mHandler != null) {
- mHandler.post(() -> {
- Callback callback = null;
- synchronized (mLock) {
- callback = mCallback;
- }
- if (callback != null) {
- action.accept(callback, payload);
- }
- });
- }
+ @Override
+ public void onFailure(CharSequence error) throws RemoteException {
+ execute(mCallback::onFailure, error);
+ }
+
+ private <T> void execute(Consumer<T> callback, T arg) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> callback.accept(arg));
+ } else {
+ mHandler.post(() -> callback.accept(arg));
}
}
+ }
- @Override
- public void onActivityDestroyed(Activity activity) {
- synchronized (mLock) {
- if (activity != getActivity()) return;
- try {
- mService.stopScan(mRequest, this, getCallingPackage());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- getActivity().getApplication().unregisterActivityLifecycleCallbacks(this);
- mCallback = null;
- mHandler = null;
- mRequest = null;
- mContext = null;
- }
+ private static class OnAssociationsChangedListenerProxy
+ extends IOnAssociationsChangedListener.Stub {
+ private final Executor mExecutor;
+ private final OnAssociationsChangedListener mListener;
+
+ private OnAssociationsChangedListenerProxy(Executor executor,
+ OnAssociationsChangedListener listener) {
+ mExecutor = executor;
+ mListener = listener;
}
- @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
- @Override public void onActivityStarted(Activity activity) {}
- @Override public void onActivityResumed(Activity activity) {}
- @Override public void onActivityPaused(Activity activity) {}
- @Override public void onActivityStopped(Activity activity) {}
- @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
+ @Override
+ public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
+ mExecutor.execute(() -> mListener.onAssociationsChanged(associations));
+ }
}
}
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
deleted file mode 100644
index 5deed1a..0000000
--- a/core/java/android/companion/DeviceId.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * The class represents free-form ID of a companion device.
- *
- * Since companion devices may have multiple IDs of different type at the same time
- * (eg. a MAC address and a Serial Number), this class not only stores the ID itself, it also stores
- * the type of the ID.
- * Both the type of the ID and its actual value are represented as {@link String}-s.
- *
- * Examples of device IDs:
- * - "mac_address: f0:18:98:b3:fd:2e"
- * - "ip_address: 128.121.35.200"
- * - "imei: 352932100034923 / 44"
- * - "serial_number: 96141FFAZ000B7"
- * - "meid_hex: 35293210003492"
- * - "meid_dic: 08918 92240 0001 3548"
- *
- * @hide
- * TODO(b/1979395): un-hide when implementing public APIs that use this class.
- */
-public final class DeviceId implements Parcelable {
- public static final String TYPE_MAC_ADDRESS = "mac_address";
-
- private final @NonNull String mType;
- private final @NonNull String mValue;
-
- /**
- * @param type type of the ID. Non-empty. Max length - 16 characters.
- * @param value the ID. Non-empty. Max length - 48 characters.
- * @throws IllegalArgumentException if either {@param type} or {@param value} is empty or
- * exceeds its max allowed length.
- */
- public DeviceId(@NonNull String type, @NonNull String value) {
- if (type.isEmpty() || value.isEmpty()) {
- throw new IllegalArgumentException("'type' and 'value' should not be empty");
- }
- this.mType = type;
- this.mValue = value;
- }
-
- /**
- * @return the type of the ID.
- */
- public @NonNull String getType() {
- return mType;
- }
-
- /**
- * @return the ID.
- */
- public @NonNull String getValue() {
- return mValue;
- }
-
- @Override
- public String toString() {
- return "DeviceId{"
- + "type='" + mType + '\''
- + ", value='" + mValue + '\''
- + '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof DeviceId)) return false;
- DeviceId deviceId = (DeviceId) o;
- return Objects.equals(mType, deviceId.mType) && Objects.equals(mValue,
- deviceId.mValue);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mType, mValue);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mType);
- dest.writeString(mValue);
- }
-
- private DeviceId(@NonNull Parcel in) {
- mType = in.readString();
- mValue = in.readString();
- }
-
- public static final @NonNull Creator<DeviceId> CREATOR = new Creator<DeviceId>() {
- @Override
- public DeviceId createFromParcel(@NonNull Parcel in) {
- return new DeviceId(in);
- }
-
- @Override
- public DeviceId[] newArray(int size) {
- return new DeviceId[size];
- }
- };
-}
diff --git a/core/java/android/companion/IFindDeviceCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
similarity index 66%
copy from core/java/android/companion/IFindDeviceCallback.aidl
copy to core/java/android/companion/IAssociationRequestCallback.aidl
index a3a47a9..8cc2a71 100644
--- a/core/java/android/companion/IFindDeviceCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,13 @@
package android.companion;
import android.app.PendingIntent;
+import android.companion.AssociationInfo;
/** @hide */
-interface IFindDeviceCallback {
- @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- oneway void onSuccess(in PendingIntent launcher);
- oneway void onFailure(in CharSequence reason);
-}
+interface IAssociationRequestCallback {
+ oneway void onAssociationPending(in PendingIntent pendingIntent);
+
+ oneway void onAssociationCreated(in AssociationInfo associationInfo);
+
+ oneway void onFailure(in CharSequence error);
+}
\ No newline at end of file
diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
index 71e5b24..702e8db 100644
--- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
@@ -17,7 +17,7 @@
package android.companion;
import android.companion.AssociationRequest;
-import android.companion.IFindDeviceCallback;
+import android.companion.IAssociationRequestCallback;
import com.android.internal.infra.AndroidFuture;
@@ -26,7 +26,7 @@
void startDiscovery(
in AssociationRequest request,
in String callingPackage,
- in IFindDeviceCallback findCallback,
+ in IAssociationRequestCallback applicationCallback,
in AndroidFuture<String> serviceCallback);
void onAssociationCreated();
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 101f948..1558db2 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -17,7 +17,8 @@
package android.companion;
import android.app.PendingIntent;
-import android.companion.IFindDeviceCallback;
+import android.companion.IAssociationRequestCallback;
+import android.companion.IOnAssociationsChangedListener;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.content.ComponentName;
@@ -28,32 +29,41 @@
* @hide
*/
interface ICompanionDeviceManager {
- void associate(in AssociationRequest request,
- in IFindDeviceCallback callback,
- in String callingPackage);
- void stopScan(in AssociationRequest request,
- in IFindDeviceCallback callback,
- in String callingPackage);
+ void associate(in AssociationRequest request, in IAssociationRequestCallback callback,
+ in String callingPackage, int userId);
- List<String> getAssociations(String callingPackage, int userId);
- List<AssociationInfo> getAssociationsForUser(int userId);
+ List<AssociationInfo> getAssociations(String callingPackage, int userId);
+ List<AssociationInfo> getAllAssociationsForUser(int userId);
- void disassociate(String deviceMacAddress, String callingPackage);
+ /** @deprecated */
+ void legacyDisassociate(String deviceMacAddress, String callingPackage, int userId);
+ void disassociate(int associationId);
+
+ /** @deprecated */
boolean hasNotificationAccess(in ComponentName component);
- PendingIntent requestNotificationAccess(in ComponentName component);
+ PendingIntent requestNotificationAccess(in ComponentName component, int userId);
+
+ /** @deprecated */
boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress,
int userId);
- void registerDevicePresenceListenerService(in String packageName, in String deviceAddress);
+ void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
- void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress);
+ void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
+ /** @deprecated */
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
+ /** @deprecated */
void createAssociation(in String packageName, in String macAddress, int userId,
in byte[] certificate);
void dispatchMessage(in int messageId, in int associationId, in byte[] message);
+
+ void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+ void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
}
diff --git a/core/java/android/companion/IFindDeviceCallback.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl
similarity index 67%
rename from core/java/android/companion/IFindDeviceCallback.aidl
rename to core/java/android/companion/IOnAssociationsChangedListener.aidl
index a3a47a9..e6794b7 100644
--- a/core/java/android/companion/IFindDeviceCallback.aidl
+++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,9 @@
package android.companion;
-import android.app.PendingIntent;
+import android.companion.AssociationInfo;
/** @hide */
-interface IFindDeviceCallback {
- @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- oneway void onSuccess(in PendingIntent launcher);
- oneway void onFailure(in CharSequence reason);
-}
+interface IOnAssociationsChangedListener {
+ oneway void onAssociationsChanged(in List<AssociationInfo> associations);
+}
\ No newline at end of file
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index ee24084..c906a13 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -70,9 +70,17 @@
private static final String TAG = "NetworkTemplate";
/**
+ * Initial Version of the backup serializer.
+ */
+ public static final int BACKUP_VERSION_1_INIT = 1;
+ /**
+ * Version of the backup serializer that added carrier template support.
+ */
+ public static final int BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2;
+ /**
* Current Version of the Backup Serializer.
*/
- private static final int BACKUP_VERSION = 1;
+ private static final int BACKUP_VERSION = BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE;
public static final int MATCH_MOBILE = 1;
public static final int MATCH_WIFI = 4;
@@ -285,6 +293,10 @@
private final int mRoaming;
private final int mDefaultNetwork;
private final int mSubType;
+ /**
+ * The subscriber Id match rule defines how the template should match networks with
+ * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
+ */
private final int mSubscriberIdMatchRule;
// Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
@@ -348,7 +360,7 @@
mSubscriberIdMatchRule = subscriberIdMatchRule;
checkValidSubscriberIdMatchRule();
if (!isKnownMatchRule(matchRule)) {
- Log.e(TAG, "Unknown network template rule " + matchRule
+ throw new IllegalArgumentException("Unknown network template rule " + matchRule
+ " will not match any identity.");
}
}
@@ -842,11 +854,17 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
+ if (!isPersistable()) {
+ Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
+ }
+
out.writeInt(BACKUP_VERSION);
out.writeInt(mMatchRule);
BackupUtils.writeString(out, mSubscriberId);
BackupUtils.writeString(out, mNetworkId);
+ out.writeInt(mMetered);
+ out.writeInt(mSubscriberIdMatchRule);
return baos.toByteArray();
}
@@ -854,7 +872,7 @@
public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
throws IOException, BackupUtils.BadVersionException {
int version = in.readInt();
- if (version < 1 || version > BACKUP_VERSION) {
+ if (version < BACKUP_VERSION_1_INIT || version > BACKUP_VERSION) {
throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
}
@@ -862,11 +880,27 @@
String subscriberId = BackupUtils.readString(in);
String networkId = BackupUtils.readString(in);
- if (!isKnownMatchRule(matchRule)) {
- throw new BackupUtils.BadVersionException(
- "Restored network template contains unknown match rule " + matchRule);
+ final int metered;
+ final int subscriberIdMatchRule;
+ if (version >= BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) {
+ metered = in.readInt();
+ subscriberIdMatchRule = in.readInt();
+ } else {
+ // For backward compatibility, fill the missing filters from match rules.
+ metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL;
+ subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT;
}
- return new NetworkTemplate(matchRule, subscriberId, networkId);
+ try {
+ return new NetworkTemplate(matchRule,
+ subscriberId, new String[] { subscriberId },
+ networkId, metered, NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
+ NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
+ } catch (IllegalArgumentException e) {
+ throw new BackupUtils.BadVersionException(
+ "Restored network template contains unknown match rule " + matchRule, e);
+ }
}
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 92861fb..1e424d1 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,18 +1,6 @@
# Haptics
-per-file CombinedVibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file CombinedVibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file ExternalVibration.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file ExternalVibration.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IExternalVibrationController.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IExternalVibratorService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IVibratorManagerService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file NullVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file SystemVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file SystemVibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file Vibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file *Vibration* = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
# PowerManager
per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index e986036..36cdfcc 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -81,6 +81,11 @@
* actions, such as emulation of physical effects, and texting feedback vibration.
*/
public static final int USAGE_CLASS_FEEDBACK = 0x2;
+ /**
+ * Vibration usage class value to use when the vibration is part of media, such as music, movie,
+ * soundtrack, game or animations.
+ */
+ public static final int USAGE_CLASS_MEDIA = 0x3;
/**
* Mask for vibration usage class value.
@@ -121,6 +126,15 @@
* such as a fingerprint sensor.
*/
public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for accessibility vibrations, such as with a screen reader.
+ */
+ public static final int USAGE_ACCESSIBILITY = 0x40 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for media vibrations, such as music, movie, soundtrack, animations, games,
+ * or any interactive media that isn't for touch feedback specifically.
+ */
+ public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
/**
* @hide
@@ -208,13 +222,17 @@
case USAGE_NOTIFICATION:
return AudioAttributes.USAGE_NOTIFICATION;
case USAGE_COMMUNICATION_REQUEST:
- return AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
+ return AudioAttributes.USAGE_VOICE_COMMUNICATION;
case USAGE_RINGTONE:
return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
case USAGE_TOUCH:
return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
case USAGE_ALARM:
return AudioAttributes.USAGE_ALARM;
+ case USAGE_ACCESSIBILITY:
+ return AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+ case USAGE_MEDIA:
+ return AudioAttributes.USAGE_MEDIA;
default:
return AudioAttributes.USAGE_UNKNOWN;
}
@@ -286,12 +304,16 @@
return "UNKNOWN";
case USAGE_ALARM:
return "ALARM";
+ case USAGE_ACCESSIBILITY:
+ return "ACCESSIBILITY";
case USAGE_RINGTONE:
return "RINGTONE";
case USAGE_NOTIFICATION:
return "NOTIFICATION";
case USAGE_COMMUNICATION_REQUEST:
return "COMMUNICATION_REQUEST";
+ case USAGE_MEDIA:
+ return "MEDIA";
case USAGE_TOUCH:
return "TOUCH";
case USAGE_PHYSICAL_EMULATION:
@@ -405,21 +427,31 @@
case AudioAttributes.USAGE_NOTIFICATION_EVENT:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
mUsage = USAGE_NOTIFICATION;
break;
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
- case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ case AudioAttributes.USAGE_ASSISTANT:
mUsage = USAGE_COMMUNICATION_REQUEST;
break;
case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
mUsage = USAGE_RINGTONE;
break;
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ mUsage = USAGE_ACCESSIBILITY;
+ break;
case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
mUsage = USAGE_TOUCH;
break;
case AudioAttributes.USAGE_ALARM:
mUsage = USAGE_ALARM;
break;
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ mUsage = USAGE_MEDIA;
+ break;
default:
mUsage = USAGE_UNKNOWN;
}
@@ -450,6 +482,8 @@
* {@link VibrationAttributes#USAGE_TOUCH},
* {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION},
* {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}.
+ * {@link VibrationAttributes#USAGE_ACCESSIBILITY}.
+ * {@link VibrationAttributes#USAGE_MEDIA}.
* @return the same Builder instance.
*/
public @NonNull Builder setUsage(int usage) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d08475d..d3b1b40 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -54,6 +54,7 @@
import android.database.SQLException;
import android.location.ILocationManager;
import android.location.LocationManager;
+import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.Uri;
@@ -3632,6 +3633,12 @@
private static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, /* tag= */ null,
+ /* makeDefault= */ false, userHandle, overrideableByRestore);
+ }
+
+ private static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ String tag, boolean makeDefault, int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, value is unchanged.");
@@ -3642,8 +3649,8 @@
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
- return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle,
- overrideableByRestore);
+ return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault,
+ userHandle, overrideableByRestore);
}
/**
@@ -4479,6 +4486,15 @@
public static final String VIBRATE_ON = "vibrate_on";
/**
+ * Whether applying ramping ringer on incoming phone call ringtone.
+ * <p>1 = apply ramping ringer
+ * <p>0 = do not apply ramping ringer
+ * @hide
+ */
+ @Readable
+ public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
+
+ /**
* If 1, redirects the system vibrator to all currently attached input devices
* that support vibration. If there are no such input devices, then the system
* vibrator is used instead.
@@ -5348,6 +5364,7 @@
PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
PUBLIC_SETTINGS.add(VIBRATE_WHEN_RINGING);
+ PUBLIC_SETTINGS.add(APPLY_RAMPING_RINGER);
}
/**
@@ -5885,6 +5902,10 @@
}
/** @hide */
+ public static void getMovedToSystemSettings(Set<String> outKeySet) {
+ }
+
+ /** @hide */
public static void clearProviderForTest() {
sProviderHolder.clearProviderForTest();
sNameValueCache.clearGenerationTrackerForTest();
@@ -10482,7 +10503,9 @@
* Whether applying ramping ringer on incoming phone call ringtone.
* <p>1 = apply ramping ringer
* <p>0 = do not apply ramping ringer
+ * @deprecated Use {@link AudioManager#isRampingRingerEnabled()} instead
*/
+ @Deprecated
@Readable
public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
@@ -15261,12 +15284,24 @@
MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
}
+ // Certain settings have been moved from global to the per-user system namespace
+ private static final HashSet<String> MOVED_TO_SYSTEM;
+ static {
+ MOVED_TO_SYSTEM = new HashSet<>(1);
+ MOVED_TO_SYSTEM.add(Global.APPLY_RAMPING_RINGER);
+ }
+
/** @hide */
public static void getMovedToSecureSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_SECURE);
}
/** @hide */
+ public static void getMovedToSystemSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SYSTEM);
+ }
+
+ /** @hide */
public static void clearProviderForTest() {
sProviderHolder.clearProviderForTest();
sNameValueCache.clearGenerationTrackerForTest();
@@ -15298,6 +15333,11 @@
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getStringForUser(resolver, name, userHandle);
}
+ if (MOVED_TO_SYSTEM.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ + " to android.provider.Settings.System, returning read-only value.");
+ return System.getStringForUser(resolver, name, userHandle);
+ }
return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
@@ -15462,6 +15502,13 @@
return Secure.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle, overrideableByRestore);
}
+ // Global and System have the same access policy so we can forward writes
+ if (MOVED_TO_SYSTEM.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ + " to android.provider.Settings.System, value is unchanged.");
+ return System.putStringForUser(resolver, name, value, tag,
+ makeDefault, userHandle, overrideableByRestore);
+ }
return sNameValueCache.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle, overrideableByRestore);
}
diff --git a/core/java/android/util/BackupUtils.java b/core/java/android/util/BackupUtils.java
index 474ceda..4fcb13c 100644
--- a/core/java/android/util/BackupUtils.java
+++ b/core/java/android/util/BackupUtils.java
@@ -37,6 +37,10 @@
public BadVersionException(String message) {
super(message);
}
+
+ public BadVersionException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
}
public static String readString(DataInputStream in) throws IOException {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0b4857d..2c766bd 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -855,6 +855,23 @@
void attachWindowContextToWindowToken(IBinder clientToken, IBinder token);
/**
+ * Attaches a {@code clientToken} to associate with DisplayContent.
+ * <p>
+ * Note that this API should be invoked after calling
+ * {@link android.window.WindowTokenClient#attachContext(Context)}
+ * </p>
+ *
+ * @param clientToken {@link android.window.WindowContext#getWindowContextToken()
+ * the WindowContext's token}
+ * @param displayId The display associated with the window context
+ *
+ * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is
+ * attached to the DisplayContent successfully. {@code null}, otherwise.
+ * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid
+ */
+ Configuration attachToDisplayContent(IBinder clientToken, int displayId);
+
+ /**
* Detaches {@link android.window.WindowContext} from the window manager node it's currently
* attached to. It is no-op if the WindowContext is not attached to a window manager node.
*
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 02b2c5d..d609fb8 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -124,7 +124,12 @@
public void setControl(@Nullable InsetsSourceControl control, int[] showTypes,
int[] hideTypes) {
super.setControl(control, showTypes, hideTypes);
- if (control == null && !isRequestedVisibleAwaitingControl()) {
+ // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME:
+ // 1) Already requested show IME, in the meantime of WM callback the control but got null
+ // control when relayout comes first
+ // 2) Make sure no regression on some implicit request IME visibility calls (e.g.
+ // toggleSoftInput)
+ if (control == null && !mIsRequestedVisibleAwaitingControl) {
hide();
removeSurface();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index bb3f4e5..dc61727 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -121,6 +121,8 @@
public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400;
/** @hide */
public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800;
+ /** @hide */
+ public static final int STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED = 0x00001000;
/** @hide */
public static final int DALTONIZER_DISABLED = -1;
@@ -244,6 +246,8 @@
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
+ boolean mIsAudioDescriptionByDefaultRequested;
+
// accessibility tracing state
int mAccessibilityTracingState = 0;
@@ -1293,15 +1297,19 @@
(stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
final boolean highTextContrastEnabled =
(stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+ final boolean audioDescriptionEnabled =
+ (stateFlags & STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED) != 0;
final boolean wasEnabled = isEnabled();
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
// Ensure listeners get current state from isZzzEnabled() calls.
mIsEnabled = enabled;
mIsTouchExplorationEnabled = touchExplorationEnabled;
mIsHighTextContrastEnabled = highTextContrastEnabled;
+ mIsAudioDescriptionByDefaultRequested = audioDescriptionEnabled;
if (wasEnabled != isEnabled()) {
notifyAccessibilityStateChanged();
@@ -1678,6 +1686,29 @@
}
}
+ /**
+ * Determines if users want to select sound track with audio description by default.
+ *
+ * Audio description, also referred to as a video description, described video, or
+ * more precisely called a visual description, is a form of narration used to provide
+ * information surrounding key visual elements in a media work for the benefit of
+ * blind and visually impaired consumers.
+ *
+ * The method provides the preference value to content provider apps to select the
+ * default sound track during playing a video or movie.
+ *
+ * @return {@code true} if the audio description is enabled, {@code false} otherwise.
+ */
+ public boolean isAudioDescriptionRequested() {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsAudioDescriptionByDefaultRequested;
+ }
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 078ab25..4e8d2da 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -98,4 +98,6 @@
int getFocusStrokeWidth();
int getFocusColor();
+
+ boolean isAudioDescriptionByDefaultEnabled();
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 5aa6233..17b675f 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,13 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import android.view.IWindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,7 +35,6 @@
* @hide
*/
public class WindowContextController {
- private final IWindowManager mWms;
/**
* {@code true} to indicate that the {@code mToken} is associated with a
* {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a
@@ -56,14 +52,7 @@
* {@link Context#getWindowContextToken()}.
*/
public WindowContextController(@NonNull WindowTokenClient token) {
- this(token, WindowManagerGlobal.getWindowManagerService());
- }
-
- /** Used for test only. DO NOT USE it in production code. */
- @VisibleForTesting
- public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
mToken = token;
- mWms = mockWms;
}
/**
@@ -80,19 +69,7 @@
throw new IllegalStateException("A Window Context can be only attached to "
+ "a DisplayArea once.");
}
- try {
- final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
- displayId, options);
- if (configuration != null) {
- mAttachedToDisplayArea = true;
- // Send the DisplayArea's configuration to WindowContext directly instead of
- // waiting for dispatching from WMS.
- mToken.onConfigurationChanged(configuration, displayId,
- false /* shouldReportConfigChange */);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options);
}
/**
@@ -120,22 +97,14 @@
throw new IllegalStateException("The Window Context should have been attached"
+ " to a DisplayArea.");
}
- try {
- mWms.attachWindowContextToWindowToken(mToken, windowToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mToken.attachToWindowToken(windowToken);
}
/** Detaches the window context from the node it's currently associated with. */
public void detachIfNeeded() {
if (mAttachedToDisplayArea) {
- try {
- mWms.detachWindowContextFromWindowContainer(mToken);
- mAttachedToDisplayArea = false;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mToken.detachFromWindowContainerIfNeeded();
+ mAttachedToDisplayArea = false;
}
}
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index f3e3859..b331a9e 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,6 +21,7 @@
import static android.window.ConfigurationHelper.shouldUpdateResources;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
@@ -31,7 +32,11 @@
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Log;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
@@ -59,10 +64,14 @@
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
+ private IWindowManager mWms;
+
private final Configuration mConfiguration = new Configuration();
private boolean mShouldDumpConfigForIme;
+ private boolean mAttachToWindowContainer;
+
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
@@ -84,6 +93,88 @@
}
/**
+ * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
+ *
+ * @param type The window type of the {@link WindowContext}
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @param options The window context launched option
+ * @return {@code true} if attaching successfully.
+ */
+ public boolean attachToDisplayArea(@WindowType int type, int displayId,
+ @Nullable Bundle options) {
+ try {
+ final Configuration configuration = getWindowManagerService()
+ .attachWindowContextToDisplayArea(this, type, displayId, options);
+ if (configuration == null) {
+ return false;
+ }
+ onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+ mAttachToWindowContainer = true;
+ return true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
+ *
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @return {@code true} if attaching successfully.
+ */
+ public boolean attachToDisplayContent(int displayId) {
+ final IWindowManager wms = getWindowManagerService();
+ // #createSystemUiContext may call this method before WindowManagerService is initialized.
+ if (wms == null) {
+ return false;
+ }
+ try {
+ final Configuration configuration = wms.attachToDisplayContent(this, displayId);
+ if (configuration == null) {
+ return false;
+ }
+ onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
+ mAttachToWindowContainer = true;
+ return true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
+ *
+ * @param windowToken the window token to associated with
+ */
+ public void attachToWindowToken(IBinder windowToken) {
+ try {
+ getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
+ mAttachToWindowContainer = true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
+ public void detachFromWindowContainerIfNeeded() {
+ if (!mAttachToWindowContainer) {
+ return;
+ }
+ try {
+ getWindowManagerService().detachWindowContextFromWindowContainer(this);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private IWindowManager getWindowManagerService() {
+ if (mWms == null) {
+ mWms = WindowManagerGlobal.getWindowManagerService();
+ }
+ return mWms;
+ }
+
+ /**
* Called when {@link Configuration} updates from the server side receive.
*
* @param newConfig the updated {@link Configuration}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 92d5a47..6d4b8c5 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -724,9 +724,6 @@
DataOutputStream usapOutputStream = null;
ZygoteArguments args = null;
- // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
- blockSigTerm();
-
LocalSocket sessionSocket = null;
if (argBuffer == null) {
// Read arguments from usapPoolSocket instead.
@@ -742,6 +739,10 @@
ZygoteCommandBuffer tmpArgBuffer = null;
try {
sessionSocket = usapPoolSocket.accept();
+ // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+ // This is safe from a race condition because the pool is only flushed after
+ // the SystemServer changes its internal state to stop using the USAP pool.
+ blockSigTerm();
usapOutputStream =
new DataOutputStream(sessionSocket.getOutputStream());
@@ -759,9 +760,10 @@
unblockSigTerm();
IoUtils.closeQuietly(sessionSocket);
IoUtils.closeQuietly(tmpArgBuffer);
- blockSigTerm();
}
} else {
+ // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+ blockSigTerm();
try {
args = ZygoteArguments.getInstance(argBuffer);
} catch (Exception ex) {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 83e26f6..db5d49d 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -601,7 +601,7 @@
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto apply_ramping_ringer = 147 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 147; // Used to be apply_ramping_ringer
message MultiSim {
option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index f8b5b233..73d6a17 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -242,7 +242,9 @@
optional SettingProto when_to_make_wifi_calls = 34 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto apply_ramping_ringer = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 35;
+ // Next tag = 36;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f10880e..d926f98 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3015,6 +3015,13 @@
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to create a "self-managed" association.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a companion app to associate to Wi-Fi.
<p>Only for use by a single pre-approved app.
@hide
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index 861e329..34b6a54 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -51,10 +51,5 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<bool name="config_showUserSwitcherByDefault">true</bool>
-
- <!-- Enable dynamic keyguard positioning for large-width screens. This will cause the keyguard
- to be aligned to one side of the screen when in landscape mode. -->
- <bool name="config_enableDynamicKeyguardPositioning">true</bool>
-
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a36785f..2b830b4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1483,6 +1483,30 @@
<integer-array name="config_autoBrightnessLevels">
</integer-array>
+ <!-- Array of light sensor lux values to define our levels for auto backlight brightness
+ support whilst in idle mode.
+ The N entries of this array define N + 1 control points as follows:
+ (1-based arrays)
+
+ Point 1: (0, value[1]): lux <= 0
+ Point 2: (level[1], value[2]): 0 < lux <= level[1]
+ Point 3: (level[2], value[3]): level[2] < lux <= level[3]
+ ...
+ Point N+1: (level[N], value[N+1]): level[N] < lux
+
+ The control points must be strictly increasing. Each control point
+ corresponds to an entry in the brightness backlight values arrays.
+ For example, if lux == level[1] (first element of the levels array)
+ then the brightness will be determined by value[2] (second element
+ of the brightness values array).
+
+ Spline interpolation is used to determine the auto-brightness
+ backlight values for lux levels between these control points.
+
+ Must be overridden in platform specific overlays -->
+ <integer-array name="config_autoBrightnessLevelsIdle">
+ </integer-array>
+
<!-- Timeout (in milliseconds) after which we remove the effects any user interactions might've
had on the brightness mapping. This timeout doesn't start until we transition to a
non-interactive display policy so that we don't reset while users are using their devices,
@@ -1506,6 +1530,10 @@
<integer-array name="config_autoBrightnessLcdBacklightValues_doze">
</integer-array>
+ <!-- Enables idle screen brightness mode on this device.
+ If this is true, config_autoBrightnessDisplayValuesNitsIdle must be defined. -->
+ <bool name="config_enableIdleScreenBrightnessMode">false</bool>
+
<!-- Array of desired screen brightness in nits corresponding to the lux values
in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
config_screenBrightnessMaximumNits, the display brightness is defined as the measured
@@ -1522,6 +1550,13 @@
<array name="config_autoBrightnessDisplayValuesNits">
</array>
+ <!-- Array of desired screen brightness in nits for idle screen brightness mode.
+ This array should meet the same requirements as config_autoBrightnessDisplayValuesNits.
+ This array also corresponds to the lux values given in config_autoBrightnessLevelsIdle.
+ In order to activate this mode, config_enableIdleScreenBrightnessMode must be true. -->
+ <array name="config_autoBrightnessDisplayValuesNitsIdle">
+ </array>
+
<!-- Array of output values for button backlight corresponding to the luX values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
@@ -1767,6 +1802,13 @@
provider services. -->
<string name="config_secondaryLocationTimeZoneProviderPackageName" translatable="false"></string>
+ <!-- Whether the time zone detection logic supports fall back from geolocation suggestions to
+ telephony suggestions temporarily in certain circumstances. Reduces time zone detection
+ latency during some scenarios like air travel. Only useful when both geolocation and
+ telephony time zone detection are supported on a device.
+ See com.android.server.timezonedetector.TimeZoneDetectorStrategy for more information. -->
+ <bool name="config_supportTelephonyTimeZoneFallback" translatable="false">false</bool>
+
<!-- Whether to enable network location overlay which allows network location provider to be
replaced by an app at run-time. When disabled, only the
config_networkLocationProviderPackageName package will be searched for network location
@@ -5045,10 +5087,6 @@
<!-- Whether to select voice/data/sms preference without user confirmation -->
<bool name="config_voice_data_sms_auto_fallback">false</bool>
- <!-- Whether to enable dynamic keyguard positioning for wide screen devices (e.g. only using
- half of the screen, to be accessible using only one hand). -->
- <bool name="config_enableDynamicKeyguardPositioning">false</bool>
-
<!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot -->
<bool name="config_allow_pin_storage_for_unattended_reboot">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index faa9902..45d9a36 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1902,6 +1902,7 @@
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
<java-symbol type="array" name="config_autoBrightnessLevels" />
+ <java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
<java-symbol type="array" name="config_ambientThresholdLevels" />
<java-symbol type="array" name="config_ambientBrighteningThresholds" />
<java-symbol type="array" name="config_ambientDarkeningThresholds" />
@@ -2237,6 +2238,7 @@
<java-symbol type="string" name="config_primaryLocationTimeZoneProviderPackageName" />
<java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" />
<java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
+ <java-symbol type="bool" name="config_supportTelephonyTimeZoneFallback" />
<java-symbol type="bool" name="config_autoResetAirplaneMode" />
<java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
<java-symbol type="bool" name="config_killableInputMethods" />
@@ -3788,7 +3790,9 @@
<java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" />
<java-symbol type="drawable" name="ic_logout" />
+ <java-symbol type="bool" name="config_enableIdleScreenBrightnessMode" />
<java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" />
+ <java-symbol type="array" name="config_autoBrightnessDisplayValuesNitsIdle" />
<java-symbol type="array" name="config_screenBrightnessBacklight" />
<java-symbol type="array" name="config_screenBrightnessNits" />
@@ -4322,8 +4326,6 @@
<java-symbol type="bool" name="config_voice_data_sms_auto_fallback" />
- <java-symbol type="bool" name="config_enableDynamicKeyguardPositioning" />
-
<java-symbol type="attr" name="colorAccentPrimary" />
<java-symbol type="attr" name="colorAccentSecondary" />
<java-symbol type="attr" name="colorAccentTertiary" />
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 0ac8f08..6b69b3f 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -223,7 +223,7 @@
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
- AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+ AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
mVibratorSpy.vibrate(effect, audioAttributes);
@@ -235,7 +235,7 @@
assertEquals(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
vibrationAttributes.getUsage());
// Keeps original AudioAttributes usage to be used by the VibratorService.
- assertEquals(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ assertEquals(AudioAttributes.USAGE_VOICE_COMMUNICATION,
vibrationAttributes.getAudioUsage());
}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index a6e351d..52cb9f3 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -24,16 +24,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.content.res.Configuration;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
-import android.view.IWindowManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -59,17 +56,14 @@
public class WindowContextControllerTest {
private WindowContextController mController;
@Mock
- private IWindowManager mMockWms;
- @Mock
private WindowTokenClient mMockToken;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mController = new WindowContextController(mMockToken, mMockWms);
+ mController = new WindowContextController(mMockToken);
doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
- doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
- anyInt(), anyInt(), any());
+ doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any());
}
@Test(expected = IllegalStateException.class)
@@ -81,10 +75,10 @@
}
@Test
- public void testDetachIfNeeded_NotAttachedYet_DoNothing() throws Exception {
+ public void testDetachIfNeeded_NotAttachedYet_DoNothing() {
mController.detachIfNeeded();
- verify(mMockWms, never()).detachWindowContextFromWindowContainer(any());
+ verify(mMockToken, never()).detachFromWindowContainerIfNeeded();
}
@Test
@@ -93,8 +87,6 @@
null /* options */);
assertThat(mController.mAttachedToDisplayArea).isTrue();
- verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY),
- eq(false) /* shouldReportConfigChange */);
mController.detachIfNeeded();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 103b836..f83f401 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -422,6 +422,11 @@
<permission name="android.permission.MANAGE_NOTIFICATIONS"/>
<!-- Permission required for CompanionDeviceManager CTS test. -->
<permission name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" />
+ <permission name="android.permission.MANAGE_COMPANION_DEVICES" />
+ <permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
+ <permission name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
+ <permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+ <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<!-- Permission required for testing registering pull atom callbacks. -->
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
<!-- Permission required for testing system audio effect APIs. -->
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 0f356a6..2f56b18 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -74,6 +74,10 @@
* getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
* getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
* </pre>
+ *
+ * <p>An alternate drawable can be specified using <code><monochrome></code> tag which can be
+ * drawn in place of the two (background and foreground) layers. This drawable is tinted
+ * according to the device or surface theme.
*/
public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
@@ -120,6 +124,7 @@
*/
private static final int BACKGROUND_ID = 0;
private static final int FOREGROUND_ID = 1;
+ private static final int MONOCHROME_ID = 2;
/**
* State variable that maintains the {@link ChildDrawable} array.
@@ -188,6 +193,18 @@
*/
public AdaptiveIconDrawable(Drawable backgroundDrawable,
Drawable foregroundDrawable) {
+ this(backgroundDrawable, foregroundDrawable, null);
+ }
+
+ /**
+ * Constructor used to dynamically create this drawable.
+ *
+ * @param backgroundDrawable drawable that should be rendered in the background
+ * @param foregroundDrawable drawable that should be rendered in the foreground
+ * @param monochromeDrawable an alternate drawable which can be tinted per system theme color
+ */
+ public AdaptiveIconDrawable(@Nullable Drawable backgroundDrawable,
+ @Nullable Drawable foregroundDrawable, @Nullable Drawable monochromeDrawable) {
this((LayerState)null, null);
if (backgroundDrawable != null) {
addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
@@ -195,6 +212,9 @@
if (foregroundDrawable != null) {
addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
}
+ if (monochromeDrawable != null) {
+ addLayer(MONOCHROME_ID, createChildDrawable(monochromeDrawable));
+ }
}
/**
@@ -227,9 +247,8 @@
state.mSourceDrawableId = Resources.getAttributeSetSourceResId(attrs);
final ChildDrawable[] array = state.mChildren;
- for (int i = 0; i < state.mChildren.length; i++) {
- final ChildDrawable layer = array[i];
- layer.setDensity(deviceDensity);
+ for (int i = 0; i < array.length; i++) {
+ array[i].setDensity(deviceDensity);
}
inflateLayers(r, parser, attrs, theme);
@@ -286,6 +305,18 @@
return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
}
+
+ /**
+ * Returns the monochrome version of this drawable. Callers can use a tinted version of
+ * this drawable instead of the original drawable on surfaces stressing user theming.
+ *
+ * @return the monochrome drawable
+ */
+ @Nullable
+ public Drawable getMonochrome() {
+ return mLayerState.mChildren[MONOCHROME_ID].mDrawable;
+ }
+
@Override
protected void onBoundsChange(Rect bounds) {
if (bounds.isEmpty()) {
@@ -316,9 +347,6 @@
for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
final ChildDrawable r = mLayerState.mChildren[i];
- if (r == null) {
- continue;
- }
final Drawable d = r.mDrawable;
if (d == null) {
continue;
@@ -359,14 +387,11 @@
if (mLayersShader == null) {
mCanvas.setBitmap(mLayersBitmap);
mCanvas.drawColor(Color.BLACK);
- for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
- if (mLayerState.mChildren[i] == null) {
- continue;
- }
- final Drawable dr = mLayerState.mChildren[i].mDrawable;
- if (dr != null) {
- dr.draw(mCanvas);
- }
+ if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas);
+ }
+ if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas);
}
mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint.setShader(mLayersShader);
@@ -480,12 +505,18 @@
continue;
}
String tagName = parser.getName();
- if (tagName.equals("background")) {
- childIndex = BACKGROUND_ID;
- } else if (tagName.equals("foreground")) {
- childIndex = FOREGROUND_ID;
- } else {
- continue;
+ switch (tagName) {
+ case "background":
+ childIndex = BACKGROUND_ID;
+ break;
+ case "foreground":
+ childIndex = FOREGROUND_ID;
+ break;
+ case "monochrome":
+ childIndex = MONOCHROME_ID;
+ break;
+ default:
+ continue;
}
final ChildDrawable layer = new ChildDrawable(state.mDensity);
@@ -941,7 +972,7 @@
static class LayerState extends ConstantState {
private int[] mThemeAttrs;
- final static int N_CHILDREN = 2;
+ static final int N_CHILDREN = 3;
ChildDrawable[] mChildren;
// The density at which to render the drawable and its children.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 75bc461..7f37036 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -29,6 +29,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
@@ -122,6 +123,16 @@
}
/**
+ * Callbacks for events in which the focus has changed.
+ */
+ public interface FocusListener {
+ /**
+ * Notifies when the task which is focused has changed.
+ */
+ void onFocusTaskChanged(RunningTaskInfo taskInfo);
+ }
+
+ /**
* Keys map from either a task id or {@link TaskListenerType}.
* @see #addListenerForTaskId
* @see #addListenerForType
@@ -142,6 +153,8 @@
/** @see #addLocusIdListener */
private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
+ private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>();
+
private final Object mLock = new Object();
private StartingWindowController mStartingWindow;
@@ -155,6 +168,9 @@
@Nullable
private final Optional<RecentTasksController> mRecentTasks;
+ @Nullable
+ private RunningTaskInfo mLastFocusedTaskInfo;
+
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
Optional.empty() /* recentTasksController */);
@@ -331,6 +347,27 @@
}
}
+ /**
+ * Adds a listener to be notified for task focus changes.
+ */
+ public void addFocusListener(FocusListener listener) {
+ synchronized (mLock) {
+ mFocusListeners.add(listener);
+ if (mLastFocusedTaskInfo != null) {
+ listener.onFocusTaskChanged(mLastFocusedTaskInfo);
+ }
+ }
+ }
+
+ /**
+ * Removes listener.
+ */
+ public void removeLocusIdListener(FocusListener listener) {
+ synchronized (mLock) {
+ mFocusListeners.remove(listener);
+ }
+ }
+
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
if (mStartingWindow != null) {
@@ -422,6 +459,18 @@
mRecentTasks.ifPresent(recentTasks ->
recentTasks.onTaskWindowingModeChanged(taskInfo));
}
+ // TODO (b/207687679): Remove check for HOME once bug is fixed
+ final boolean isFocusedOrHome = taskInfo.isFocused
+ || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+ && taskInfo.isVisible);
+ final boolean focusTaskChanged = (mLastFocusedTaskInfo == null
+ || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome;
+ if (focusTaskChanged) {
+ for (int i = 0; i < mFocusListeners.size(); i++) {
+ mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo);
+ }
+ mLastFocusedTaskInfo = taskInfo;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index d07fff3..3579bf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -321,6 +321,16 @@
mSplitLayoutHandler.onLayoutSizeChanged(this);
}
+ /** Sets divide position base on the ratio within root bounds. */
+ public void setDivideRatio(float ratio) {
+ final int position = isLandscape()
+ ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
+ : mRootBounds.top + (int) (mRootBounds.height() * ratio);
+ DividerSnapAlgorithm.SnapTarget snapTarget =
+ mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
+ setDividePosition(snapTarget.position);
+ }
+
/** Resets divider position. */
public void resetDividerPosition() {
mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 46c7b50..f562fd9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -241,10 +241,11 @@
static PhonePipMenuController providesPipPhoneMenuController(Context context,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
SystemWindows systemWindows,
+ Optional<SplitScreenController> splitScreenOptional,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, mainExecutor, mainHandler);
+ systemWindows, splitScreenOptional, mainExecutor, mainHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 8d9ad4d..caa1f01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -90,6 +90,11 @@
default void updateMenuBounds(Rect destinationBounds) {}
/**
+ * Update when the current focused task changes.
+ */
+ default void onFocusTaskChanged(RunningTaskInfo taskInfo) {}
+
+ /**
* Returns a default LayoutParams for the PIP Menu.
* @param width the PIP stack width.
* @param height the PIP stack height.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 90135f2a..c2ebc30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -98,7 +98,7 @@
* see also {@link PipMotionHelper}.
*/
public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
- DisplayController.OnDisplaysChangedListener {
+ DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
/**
@@ -286,6 +286,7 @@
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
+ mTaskOrganizer.addFocusListener(this);
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
}
@@ -772,6 +773,11 @@
}
@Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mPipMenuController.onFocusTaskChanged(taskInfo);
+ }
+
+ @Override
public boolean supportSizeCompatUI() {
// PIP doesn't support size compat.
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5687f4d..eb512af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ParceledListSlice;
@@ -43,10 +44,12 @@
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/**
* Manages the PiP menu view which can show menu options or a scrim.
@@ -114,6 +117,7 @@
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
+ private final Optional<SplitScreenController> mSplitScreenController;
private ParceledListSlice<RemoteAction> mAppActions;
private ParceledListSlice<RemoteAction> mMediaActions;
private SyncRtSurfaceTransactionApplier mApplier;
@@ -145,6 +149,7 @@
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
+ Optional<SplitScreenController> splitScreenOptional,
ShellExecutor mainExecutor, Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
@@ -152,6 +157,7 @@
mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
+ mSplitScreenController = splitScreenOptional;
}
public boolean isMenuVisible() {
@@ -180,7 +186,8 @@
if (mPipMenuView != null) {
detachPipMenuView();
}
- mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler);
+ mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
+ mSplitScreenController);
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
@@ -209,6 +216,13 @@
updateMenuLayout(destinationBounds);
}
+ @Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mPipMenuView != null) {
+ mPipMenuView.onFocusTaskChanged(taskInfo);
+ }
+ }
+
/**
* Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some
* reason (ie. the window isn't ready yet, thus {@link android.view.ViewRootImpl} is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index b209699..82e8273 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.phone;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
@@ -32,8 +33,10 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -61,11 +64,13 @@
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/**
* Translucent window that gets started on top of a task in PIP to allow the user to control it.
@@ -105,6 +110,7 @@
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
private int mDismissFadeOutDurationMs;
+ private boolean mFocusedTaskAllowSplitScreen;
private final List<RemoteAction> mActions = new ArrayList<>();
@@ -116,6 +122,7 @@
private AnimatorSet mMenuContainerAnimator;
private PhonePipMenuController mController;
+ private Optional<SplitScreenController> mSplitScreenControllerOptional;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -144,12 +151,14 @@
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
public PipMenuView(Context context, PhonePipMenuController controller,
- ShellExecutor mainExecutor, Handler mainHandler) {
+ ShellExecutor mainExecutor, Handler mainHandler,
+ Optional<SplitScreenController> splitScreenController) {
super(context, null, 0);
mContext = context;
mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
+ mSplitScreenControllerOptional = splitScreenController;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
inflate(context, R.layout.pip_menu, this);
@@ -255,6 +264,15 @@
return super.dispatchGenericMotionEvent(event);
}
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
+ && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+ mFocusedTaskAllowSplitScreen = isSplitScreen
+ || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && taskInfo.supportsSplitScreenMultiWindow
+ && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME);
+ }
+
void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
mAllowMenuTimeout = allowMenuTimeout;
@@ -278,7 +296,8 @@
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 1f);
ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
- mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f);
+ mEnterSplitButton.getAlpha(),
+ ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f);
if (menuState == MENU_STATE_FULL) {
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
enterSplitAnim);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 3d3a630..4a99097 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -83,14 +83,15 @@
* Starts tasks simultaneously in one transition.
*/
oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+ in Bundle sideOptions, int sidePosition, float splitRatio,
+ in RemoteTransition remoteTransition) = 10;
/**
* Version of startTasks using legacy transition system.
*/
oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
int sideTaskId, in Bundle sideOptions, int sidePosition,
- in RemoteAnimationAdapter adapter) = 11;
+ float splitRatio, in RemoteAnimationAdapter adapter) = 11;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6b42ed7..262a9a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -183,6 +183,11 @@
return mStageCoordinator.isSplitScreenVisible();
}
+ public boolean isTaskInSplitScreen(int taskId) {
+ return isSplitScreenVisible()
+ && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ }
+
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
new WindowContainerTransaction());
@@ -605,21 +610,21 @@
@Override
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
+ float splitRatio, RemoteAnimationAdapter adapter) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
- adapter));
+ splitRatio, adapter));
}
@Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition,
+ @SplitPosition int sidePosition, float splitRatio,
@Nullable RemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, remoteTransition));
+ sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a3726d4..6280f76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -274,6 +274,17 @@
return mSideStageListener.mVisible && mMainStageListener.mVisible;
}
+ @SplitScreen.StageType
+ int getStageOfTask(int taskId) {
+ if (mMainStage.containsTask(taskId)) {
+ return STAGE_TYPE_MAIN;
+ } else if (mSideStage.containsTask(taskId)) {
+ return STAGE_TYPE_SIDE;
+ }
+
+ return STAGE_TYPE_UNDEFINED;
+ }
+
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StageType int stageType,
@SplitPosition int stagePosition, WindowContainerTransaction wct) {
StageTaskListener targetStage;
@@ -322,7 +333,7 @@
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@Nullable RemoteTransition remoteTransition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -334,6 +345,7 @@
mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
+ mSplitLayout.setDivideRatio(splitRatio);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
@@ -349,7 +361,7 @@
/** Starts 2 tasks in one legacy transition. */
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
+ float splitRatio, RemoteAnimationAdapter adapter) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Need to add another wrapper here in shell so that we can inject the divider bar
// and also manage the process elevation via setRunningRemote
@@ -404,6 +416,7 @@
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
+ mSplitLayout.setDivideRatio(splitRatio);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index 1d463d5..e6c2f38e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -82,7 +83,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 10cf0b7..1a3e42c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -67,7 +68,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index 722ec34..5c78b29 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -86,7 +87,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 38c008c..251d92d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.apppairs
import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -71,7 +72,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index 13824b8..d47057f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -87,7 +87,7 @@
testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
primaryApp.component)
- @FlakyTest
+ @Postsubmit
@Test
fun appPairsSecondaryBoundsIsVisibleAtEnd() =
testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index c003084..097867a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.apppairs
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -89,13 +89,13 @@
}
}
- @FlakyTest(bugId = 172776659)
+ @Postsubmit
@Test
fun appPairsPrimaryBoundsIsVisibleAtEnd() =
testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
primaryApp.component)
- @FlakyTest(bugId = 172776659)
+ @Postsubmit
@Test
fun appPairsSecondaryBoundsIsVisibleAtEnd() =
testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index 1605d80..a928bbd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -18,7 +18,7 @@
import android.content.Context
import android.graphics.Point
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.test.filters.RequiresDevice
@@ -66,7 +66,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun testAppIsAlwaysVisible() {
testSpec.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index a8f17a75..64636be 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.bubble
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -49,7 +49,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun testAppIsAlwaysVisible() {
testSpec.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index 3885155..ef7d65e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -74,7 +73,7 @@
splitScreenApp.component, secondaryApp.component,
FlickerComponentName.SNAPSHOT)
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -94,11 +93,11 @@
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
- @Postsubmit
+ @FlakyTest
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
index 079a6ef..c1fba7d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -124,7 +124,7 @@
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index a787f2b..77fb101 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -122,11 +122,11 @@
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
- @Postsubmit
+ @FlakyTest
@Test
fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -136,7 +136,7 @@
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 030e040..34c0f849 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -25,8 +27,10 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -56,6 +60,8 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
/**
* Defines the transition used to run the test
@@ -76,6 +82,24 @@
}
}
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
+
/** {@inheritDoc} */
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 2def979..1b7220e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -16,8 +16,10 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -25,9 +27,11 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -57,6 +61,9 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -65,6 +72,24 @@
}
}
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
+
@Before
fun onBefore() {
// This CUJ don't work in shell transitions because of b/204570898 b/204562589
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 9191d0e..40be21a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -72,22 +72,6 @@
}
}
- @Presubmit
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Presubmit
- @Test
- override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
-
@FlakyTest
@Test
override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
@@ -104,14 +88,6 @@
testSpec.statusBarLayerRotatesScales()
}
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
- @Presubmit
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 9c26105..a940a7f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -82,19 +83,19 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
- @FlakyTest
+ @Postsubmit
@Test
override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
@@ -102,7 +103,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
@@ -110,7 +111,7 @@
super.statusBarLayerRotatesScales()
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
@@ -118,7 +119,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAppShowsOnTop() {
testSpec.assertWmEnd {
@@ -126,7 +127,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
@@ -134,13 +135,13 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAlwaysVisible() = testSpec.assertWm {
this.isAppWindowVisible(pipApp.component)
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
@@ -148,7 +149,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 73eebad..453050f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -106,6 +106,12 @@
}
@Test
+ public void testSetDivideRatio() {
+ mSplitLayout.setDivideRatio(0.5f);
+ verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
+ }
+
+ @Test
public void testOnDoubleTappedDivider() {
mSplitLayout.onDoubleTappedDivider();
verify(mSplitLayoutHandler).onDoubleTappedDivider();
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 491f5f5..a2d0103 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -107,6 +107,8 @@
target: {
android: {
shared_libs: [
+ "android.hardware.graphics.common-V3-ndk",
+ "android.hardware.graphics.common@1.2",
"liblog",
"libcutils",
"libutils",
@@ -126,9 +128,11 @@
static_libs: [
"libEGL_blobCache",
"libprotoutil",
+ "libshaders",
"libstatslog_hwui",
"libstatspull_lazy",
"libstatssocket_lazy",
+ "libtonemap",
],
},
host: {
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 0b3b393..a5c0924 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -20,9 +20,11 @@
// TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead.
#include <surfacetexture/surface_texture_platform.h>
+
#include "AutoBackendTextureRelease.h"
#include "Matrix.h"
#include "Properties.h"
+#include "android/hdr_metadata.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
#include "renderthread/RenderThread.h"
@@ -147,6 +149,9 @@
mUpdateTexImage = false;
float transformMatrix[16];
android_dataspace dataspace;
+ AHdrMetadataType hdrMetadataType;
+ android_cta861_3_metadata cta861_3;
+ android_smpte2086_metadata smpte2086;
int slot;
bool newContent = false;
ARect currentCrop;
@@ -155,8 +160,9 @@
// is necessary if the SurfaceTexture queue is in synchronous mode, and we
// cannot tell which mode it is in.
AHardwareBuffer* hardwareBuffer = ASurfaceTexture_dequeueBuffer(
- mSurfaceTexture.get(), &slot, &dataspace, transformMatrix, &outTransform,
- &newContent, createReleaseFence, fenceWait, this, ¤tCrop);
+ mSurfaceTexture.get(), &slot, &dataspace, &hdrMetadataType, &cta861_3,
+ &smpte2086, transformMatrix, &outTransform, &newContent, createReleaseFence,
+ fenceWait, this, ¤tCrop);
if (hardwareBuffer) {
mCurrentSlot = slot;
@@ -173,7 +179,18 @@
SkRect currentCropRect =
SkRect::MakeLTRB(currentCrop.left, currentCrop.top, currentCrop.right,
currentCrop.bottom);
- updateLayer(forceFilter, layerImage, outTransform, currentCropRect);
+
+ float maxLuminanceNits = -1.f;
+ if (hdrMetadataType & HDR10_SMPTE2086) {
+ maxLuminanceNits = std::max(smpte2086.maxLuminance, maxLuminanceNits);
+ }
+
+ if (hdrMetadataType & HDR10_CTA861_3) {
+ maxLuminanceNits =
+ std::max(cta861_3.maxContentLightLevel, maxLuminanceNits);
+ }
+ updateLayer(forceFilter, layerImage, outTransform, currentCropRect,
+ maxLuminanceNits);
}
}
}
@@ -186,13 +203,15 @@
}
void DeferredLayerUpdater::updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage,
- const uint32_t transform, SkRect currentCrop) {
+ const uint32_t transform, SkRect currentCrop,
+ float maxLuminanceNits) {
mLayer->setBlend(mBlend);
mLayer->setForceFilter(forceFilter);
mLayer->setSize(mWidth, mHeight);
mLayer->setCurrentCropRect(currentCrop);
mLayer->setWindowTransform(transform);
mLayer->setImage(layerImage);
+ mLayer->setMaxLuminanceNits(maxLuminanceNits);
}
void DeferredLayerUpdater::detachSurfaceTexture() {
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index da8041f..9a4c550 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -91,7 +91,7 @@
void detachSurfaceTexture();
void updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage, const uint32_t transform,
- SkRect currentCrop);
+ SkRect currentCrop, float maxLuminanceNits = -1.f);
void destroyLayer();
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 0789344..47eb5d3 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -96,6 +96,12 @@
inline sk_sp<SkImage> getImage() const { return this->layerImage; }
+ inline void setMaxLuminanceNits(float maxLuminanceNits) {
+ mMaxLuminanceNits = maxLuminanceNits;
+ }
+
+ inline float getMaxLuminanceNits() { return mMaxLuminanceNits; }
+
void draw(SkCanvas* canvas);
protected:
@@ -158,6 +164,11 @@
*/
bool mBlend = false;
+ /**
+ * Max input luminance if the layer is HDR
+ */
+ float mMaxLuminanceNits = -1;
+
}; // struct Layer
} // namespace uirenderer
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 1439656..553b08f 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -15,12 +15,19 @@
*/
#include "LayerDrawable.h"
+
+#include <shaders/shaders.h>
+#include <utils/Color.h>
#include <utils/MathUtils.h>
+#include "DeviceInfo.h"
#include "GrBackendSurface.h"
#include "SkColorFilter.h"
+#include "SkRuntimeEffect.h"
#include "SkSurface.h"
#include "gl/GrGLTypes.h"
+#include "math/mat4.h"
+#include "system/graphics-base-v1.0.h"
#include "system/window.h"
namespace android {
@@ -69,6 +76,35 @@
isIntegerAligned(dstDevRect.y()));
}
+static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
+ const shaders::LinearEffect& linearEffect,
+ float maxDisplayLuminance, float maxLuminance) {
+ auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+ auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
+ if (!runtimeEffect) {
+ LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+ }
+
+ SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
+
+ effectBuilder.child("child") = std::move(shader);
+
+ const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, mat4(),
+ maxDisplayLuminance, maxLuminance);
+
+ for (const auto& uniform : uniforms) {
+ effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+ }
+
+ return effectBuilder.makeShader(nullptr, false);
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+ const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
// TODO: Context arg probably doesn't belong here – do debug check at callsite instead.
bool LayerDrawable::DrawLayer(GrRecordingContext* context,
SkCanvas* canvas,
@@ -150,8 +186,30 @@
sampling = SkSamplingOptions(SkFilterMode::kLinear);
}
- canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- constraint);
+ const auto sourceDataspace = static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
+ const SkImageInfo& imageInfo = canvas->imageInfo();
+ const auto destinationDataspace = static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
+
+ if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
+ const auto effect = shaders::LinearEffect{
+ .inputDataspace = sourceDataspace,
+ .outputDataspace = destinationDataspace,
+ .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
+ .fakeInputDataspace = destinationDataspace};
+ auto shader = layerImage->makeShader(sampling,
+ SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
+ constexpr float kMaxDisplayBrightess = 1000.f;
+ shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
+ layer->getMaxLuminanceNits());
+ paint.setShader(shader);
+ canvas->drawRect(skiaDestRect, paint);
+ } else {
+ canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+ constraint);
+ }
+
canvas->restore();
// restore the original matrix
if (useLayerTransform) {
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..79b2155
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,565 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final String component = Settings.Secure.getString(getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java"
+ line="60"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java"
+ line="188"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java"
+ line="194"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/app/AssistUtils.java"
+ line="216"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" * volume, false otherwise."
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/media/java/android/media/AudioManager.java"
+ line="1028"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" public final boolean isEnabled() {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="70"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" public final String getRawLocale() {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="81"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getFloat()` called from system process. Please call `android.provider.Settings.Secure#getFloatForUser()` instead. "
+ errorLine1=" public final float getFontScale() {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="111"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" public int getRawUserStyle() {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="120"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final int foregroundColor = Secure.getInt("
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="478"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final int backgroundColor = Secure.getInt("
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="480"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final int edgeType = Secure.getInt("
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="482"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final int edgeColor = Secure.getInt("
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="484"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final int windowColor = Secure.getInt("
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="486"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java"
+ line="489"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" String nearbyComponent = Settings.Secure.getString("
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/app/ChooserActivity.java"
+ line="1156"
+ column="26"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" final ContentResolver cr = context.getContentResolver();"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java"
+ line="587"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java"
+ line="588"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" int inversionEnabled = Settings.Secure.getInt(contentResolver,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/security/ConfirmationPrompt.java"
+ line="220"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getFloat()` called from system process. Please call `android.provider.Settings.System#getFloatForUser()` instead. "
+ errorLine1=" float fontScale = Settings.System.getFloat(contentResolver,"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/security/ConfirmationPrompt.java"
+ line="225"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" int a11yEnabled = Settings.Secure.getInt(contentResolver,"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/security/ConfirmationPrompt.java"
+ line="237"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" Secure.getInt(resolver, Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL, 1) != 0;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/content/F2fsUtils.java"
+ line="96"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" int speed = DEFAULT_POINTER_SPEED;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/hardware/input/InputManager.java"
+ line="865"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final ContentResolver contentResolver = fallbackContext.getContentResolver();"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java"
+ line="2860"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" if (mShowImeWithHardKeyboard == ShowImeWithHardKeyboardType.UNKNOWN) {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java"
+ line="1205"
+ column="79"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" if (showImeWithHardKeyboardUri.equals(uri)) {"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java"
+ line="1225"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" RemoteViews.MARGIN_BOTTOM, 0);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/app/Notification.java"
+ line="5619"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java"
+ line="40"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(mContext.getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java"
+ line="328"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java"
+ line="3129"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java"
+ line="3131"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getString()` called from system process. Please call `android.provider.Settings.System#getStringForUser()` instead. "
+ errorLine1=" final String touchDataJson = Settings.System.getString("
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/app/PlatLogoActivity.java"
+ line="184"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) == 1;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java"
+ line="463"
+ column="8"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java"
+ line="97"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" final String setting = getDefaultRingtoneSetting(type);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/media/java/android/media/RingtoneManager.java"
+ line="1105"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final String targetString = Settings.Secure.getString(context.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java"
+ line="55"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final String targetsValue = Settings.Secure.getString(context.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java"
+ line="82"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final String targetString = Settings.Secure.getString(context.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java"
+ line="112"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/SpeechRecognizer.java"
+ line="665"
+ column="3"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
+ line="296"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
+ line="297"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
+ line="298"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
+ errorLine1=" boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
+ line="299"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(getContentResolver(), name, defaultValue);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/tts/TextToSpeechService.java"
+ line="422"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" return Settings.Secure.getInt(getContext().getContentResolver(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="frameworks/base/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java"
+ line="63"
+ column="24"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" String engine = getString(mContext.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/tts/TtsEngines.java"
+ line="116"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE));"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/tts/TtsEngines.java"
+ line="337"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/tts/TtsEngines.java"
+ line="373"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" final String prefList = Settings.Secure.getString(mContext.getContentResolver(),"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/speech/tts/TtsEngines.java"
+ line="527"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
+ errorLine1=" }"
+ errorLine2=" ^">
+ <location
+ file="frameworks/base/core/java/android/service/autofill/UserData.java"
+ line="535"
+ column="10"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
+ errorLine1=" public static boolean isActiveService(Context context, ComponentName service) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/core/java/android/service/voice/VoiceInteractionService.java"
+ line="156"
+ column="74"/>
+ </issue>
+
+</issues>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 2916b01..337b45c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -62,6 +62,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -1021,6 +1022,29 @@
}
/**
+ * Returns the current user setting for ramping ringer on incoming phone call ringtone.
+ *
+ * @return true if the incoming phone call ringtone is configured to gradually increase its
+ * volume, false otherwise.
+ */
+ public boolean isRampingRingerEnabled() {
+ return Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.APPLY_RAMPING_RINGER, 0) != 0;
+ }
+
+ /**
+ * Sets the flag for enabling ramping ringer on incoming phone call ringtone.
+ *
+ * @see #isRampingRingerEnabled()
+ * @hide
+ */
+ @TestApi
+ public void setRampingRingerEnabled(boolean enabled) {
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.APPLY_RAMPING_RINGER, enabled ? 1 : 0);
+ }
+
+ /**
* Checks valid ringer mode values.
*
* @return true if the ringer mode indicated is valid, false otherwise.
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index bac44ad..e979a1b5 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -163,6 +163,14 @@
* {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}.
* </td>
* </tr>
+ * <tr>
+ * <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
+ * <td>1</td>
+ * <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
+ * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+ * little-endian value, with the lower 6 bits set to zero.
+ * </td>
+ * </tr>
* </table>
*
* @see android.graphics.ImageFormat
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 4daedfc..939b679 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5106,7 +5106,6 @@
public MediaImage(
@NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly,
long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
- mFormat = ImageFormat.YUV_420_888;
mTimestamp = timestamp;
mIsImageValid = true;
mIsReadOnly = buffer.isReadOnly();
@@ -5119,6 +5118,11 @@
mBufferContext = 0;
+ int cbPlaneOffset = -1;
+ int crPlaneOffset = -1;
+ int planeOffsetInc = -1;
+ int pixelStride = -1;
+
// read media-info. See MediaImage2
if (info.remaining() == 104) {
int type = info.getInt();
@@ -5136,14 +5140,27 @@
"unsupported size: " + mWidth + "x" + mHeight);
}
int bitDepth = info.getInt();
- if (bitDepth != 8) {
+ if (bitDepth != 8 && bitDepth != 10) {
throw new UnsupportedOperationException("unsupported bit depth: " + bitDepth);
}
int bitDepthAllocated = info.getInt();
- if (bitDepthAllocated != 8) {
+ if (bitDepthAllocated != 8 && bitDepthAllocated != 16) {
throw new UnsupportedOperationException(
"unsupported allocated bit depth: " + bitDepthAllocated);
}
+ if (bitDepth == 8 && bitDepthAllocated == 8) {
+ mFormat = ImageFormat.YUV_420_888;
+ planeOffsetInc = 1;
+ pixelStride = 2;
+ } else if (bitDepth == 10 && bitDepthAllocated == 16) {
+ mFormat = ImageFormat.YCBCR_P010;
+ planeOffsetInc = 2;
+ pixelStride = 4;
+ } else {
+ throw new UnsupportedOperationException("couldn't infer ImageFormat"
+ + " bitDepth: " + bitDepth + " bitDepthAllocated: " + bitDepthAllocated);
+ }
+
mPlanes = new MediaPlane[numPlanes];
for (int ix = 0; ix < numPlanes; ix++) {
int planeOffset = info.getInt();
@@ -5165,12 +5182,31 @@
buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8)
+ (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc);
mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc);
+ if ((mFormat == ImageFormat.YUV_420_888 || mFormat == ImageFormat.YCBCR_P010)
+ && ix == 1) {
+ cbPlaneOffset = planeOffset;
+ } else if ((mFormat == ImageFormat.YUV_420_888
+ || mFormat == ImageFormat.YCBCR_P010) && ix == 2) {
+ crPlaneOffset = planeOffset;
+ }
}
} else {
throw new UnsupportedOperationException(
"unsupported info length: " + info.remaining());
}
+ // Validate chroma semiplanerness.
+ if (mFormat == ImageFormat.YCBCR_P010) {
+ if (crPlaneOffset != cbPlaneOffset + planeOffsetInc) {
+ throw new UnsupportedOperationException("Invalid plane offsets"
+ + " cbPlaneOffset: " + cbPlaneOffset + " crPlaneOffset: " + crPlaneOffset);
+ }
+ if (mPlanes[1].getPixelStride() != pixelStride
+ || mPlanes[2].getPixelStride() != pixelStride) {
+ throw new UnsupportedOperationException("Invalid pixelStride");
+ }
+ }
+
if (cropRect == null) {
cropRect = new Rect(0, 0, mWidth, mHeight);
}
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index bd283dc..8568383 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -346,4 +346,13 @@
mAvSharedMemSize = 0;
mAvSharedHandle = nullptr;
}
+
+Result FilterClient::setDelayHint(const FilterDelayHint& hint) {
+ if (mTunerFilter) {
+ Status s = mTunerFilter->setDelayHint(hint);
+ return ClientHelper::getServiceSpecificErrorCode(s);
+ }
+ return Result::INVALID_STATE;
+}
+
} // namespace android
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 7ebe7bc7..20e5610 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -33,6 +33,7 @@
using ::aidl::android::hardware::tv::tuner::DemuxFilterSettings;
using ::aidl::android::hardware::tv::tuner::DemuxFilterStatus;
using ::aidl::android::hardware::tv::tuner::DemuxFilterType;
+using ::aidl::android::hardware::tv::tuner::FilterDelayHint;
using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
using ::aidl::android::media::tv::tuner::ITunerFilter;
using ::android::hardware::EventFlag;
@@ -152,6 +153,11 @@
*/
Result freeSharedFilterToken(const string& filterToken);
+ /**
+ * Set a filter delay hint.
+ */
+ Result setDelayHint(const FilterDelayHint& hint);
+
private:
Result getFilterMq();
int64_t copyData(int8_t* buffer, int64_t size);
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
index ae63462..0c8e431 100644
--- a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
@@ -41,5 +41,5 @@
interface ISecureElementService {
String[] getReaders();
android.se.omapi.ISecureElementReader getReader(in String reader);
- boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames);
+ boolean[] isNfcEventAllowed(in String reader, in byte[] aid, in String[] packageNames, in int userId);
}
diff --git a/omapi/aidl/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
index 61ae481..13707ec 100644
--- a/omapi/aidl/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
@@ -52,7 +52,7 @@
* Checks if the application defined by the package name is allowed to
* receive NFC transaction events for the defined AID.
*/
- boolean[] isNFCEventAllowed(in String reader, in byte[] aid,
- in String[] packageNames);
+ boolean[] isNfcEventAllowed(in String reader, in byte[] aid,
+ in String[] packageNames, in int userId);
}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java
deleted file mode 100644
index 84fb0e6..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport;
-import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager;
-
-/**
- * This service provides encryption of backup data. For an intent used to bind to this service, it
- * provides an {@link IntermediateEncryptingTransport} which is an implementation of {@link
- * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from the
- * real {@link IBackupTransport}.
- */
-public class BackupEncryptionService extends Service {
- public static final String TAG = "BackupEncryption";
- private static IntermediateEncryptingTransportManager sTransportManager = null;
-
- @Override
- public void onCreate() {
- Log.i(TAG, "onCreate:" + this);
- if (sTransportManager == null) {
- Log.i(TAG, "Creating IntermediateEncryptingTransportManager");
- sTransportManager = new IntermediateEncryptingTransportManager(this);
- }
- }
-
- @Override
- public void onDestroy() {
- Log.i(TAG, "onDestroy:" + this);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // TODO (b141536117): Check connection with TransportClient.connect and return null on fail.
- return sTransportManager.get(intent);
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- sTransportManager.cleanup(intent);
- return false;
- }
-}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
index 1d841b4..db2dd2f 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
@@ -16,8 +16,6 @@
package com.android.server.backup.encryption;
-import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
-
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
@@ -36,6 +34,8 @@
import java.util.Map;
public class KeyValueEncrypter {
+ private static final String TAG = "KeyValueEncrypter";
+
private final Context mContext;
private final EncryptionKeyHelper mKeyHelper;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
deleted file mode 100644
index c3cb335..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.transport;
-
-import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
-
-import android.app.backup.BackupTransport;
-import android.app.backup.RestoreDescription;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.encryption.KeyValueEncrypter;
-import com.android.server.backup.transport.DelegatingTransport;
-import com.android.server.backup.transport.TransportClient;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
- * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
- * TransportClient.connect(String)}.
- */
-public class IntermediateEncryptingTransport extends DelegatingTransport {
- private static final String BACKUP_TEMP_DIR = "backup";
- private static final String RESTORE_TEMP_DIR = "restore";
-
- private final TransportClient mTransportClient;
- private final Object mConnectLock = new Object();
- private final Context mContext;
- private volatile IBackupTransport mRealTransport;
- private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
- private final KeyValueEncrypter mKeyValueEncrypter;
- private final boolean mShouldEncrypt;
-
- IntermediateEncryptingTransport(
- TransportClient transportClient, Context context, boolean shouldEncrypt) {
- this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
- }
-
- @VisibleForTesting
- IntermediateEncryptingTransport(
- TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
- boolean shouldEncrypt) {
- mTransportClient = transportClient;
- mContext = context;
- mKeyValueEncrypter = keyValueEncrypter;
- mShouldEncrypt = shouldEncrypt;
- }
-
- @Override
- protected IBackupTransport getDelegate() throws RemoteException {
- if (mRealTransport == null) {
- connect();
- }
- Log.d(TAG, "real transport = " + mRealTransport.name());
- return mRealTransport;
- }
-
- @Override
- public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
- throws RemoteException {
- if (!mShouldEncrypt) {
- return super.performBackup(packageInfo, inFd, flags);
- }
-
- File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
- if (encryptedStorageFile == null) {
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- // Encrypt the backup data and write it into a temp file.
- try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
- mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
- encryptedOutput);
- } catch (Throwable e) {
- Log.e(TAG, "Failed to encrypt backup data: ", e);
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- // Pass the temp file to the real transport for backup.
- try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
- return super.performBackup(
- packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
- } catch (IOException e) {
- Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
- return BackupTransport.TRANSPORT_ERROR;
- }
- }
-
- @Override
- public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
- if (!mShouldEncrypt) {
- return super.getRestoreData(outFd);
- }
-
- String nextRestorePackage = mNextRestorePackage.get();
- if (nextRestorePackage == null) {
- Log.e(TAG, "No next restore package set");
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
- if (encryptedStorageFile == null) {
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- // Get encrypted restore data from the real transport and write it into a temp file.
- try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
- int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
- if (status != BackupTransport.TRANSPORT_OK) {
- Log.e(TAG, "Failed to read restore data from transport, status = " + status);
- return status;
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- // Decrypt the data and write it into the fd given by the real transport.
- try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
- mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
- encryptedStorageFile.delete();
- } catch (Exception e) {
- Log.e(TAG, "Failed to decrypt restored data: ", e);
- return BackupTransport.TRANSPORT_ERROR;
- }
-
- return BackupTransport.TRANSPORT_OK;
- }
-
- @Override
- public RestoreDescription nextRestorePackage() throws RemoteException {
- if (!mShouldEncrypt) {
- return super.nextRestorePackage();
- }
-
- RestoreDescription restoreDescription = super.nextRestorePackage();
- mNextRestorePackage.set(restoreDescription.getPackageName());
-
- return restoreDescription;
- }
-
- @VisibleForTesting
- protected File getBackupTempStorage(String packageName) {
- return getTempStorage(packageName, BACKUP_TEMP_DIR);
- }
-
- @VisibleForTesting
- protected File getRestoreTempStorage(String packageName) {
- return getTempStorage(packageName, RESTORE_TEMP_DIR);
- }
-
- private File getTempStorage(String packageName, String operationType) {
- File encryptedDir = new File(mContext.getFilesDir(), operationType);
- encryptedDir.mkdir();
- File encryptedFile = new File(encryptedDir, packageName);
- try {
- encryptedFile.createNewFile();
- } catch (IOException e) {
- Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
- }
- return encryptedFile;
- }
-
- private void connect() throws RemoteException {
- Log.i(TAG, "connecting " + mTransportClient);
- synchronized (mConnectLock) {
- if (mRealTransport == null) {
- mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport");
- if (mRealTransport == null) {
- throw new RemoteException("Could not connect: " + mTransportClient);
- }
- }
- }
- }
-
- @VisibleForTesting
- TransportClient getClient() {
- return mTransportClient;
- }
-
- @VisibleForTesting
- void setNextRestorePackage(String nextRestorePackage) {
- mNextRestorePackage.set(nextRestorePackage);
- }
-}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
deleted file mode 100644
index 7c4082c..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.transport;
-
-import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.server.backup.transport.TransportClientManager;
-import com.android.server.backup.transport.TransportStats;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
-public class IntermediateEncryptingTransportManager {
- private static final String CALLER = "IntermediateEncryptingTransportManager";
- private final TransportClientManager mTransportClientManager;
- private final Object mTransportsLock = new Object();
- private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
- private Context mContext;
-
- @VisibleForTesting
- IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
- mTransportClientManager = transportClientManager;
- }
-
- public IntermediateEncryptingTransportManager(Context context) {
- this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
- mContext = context;
- }
-
- /**
- * Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and
- * provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
- * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
- * the real {@link IBackupTransport}.
- *
- * @param intent {@link Intent} created with a call to {@link
- * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
- * @return
- */
- public IntermediateEncryptingTransport get(Intent intent) {
- Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
- Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
- synchronized (mTransportsLock) {
- return mTransports.computeIfAbsent(
- transportIntent.getComponent(), c -> create(transportIntent));
- }
- }
-
- /** Create an instance of {@link IntermediateEncryptingTransport}. */
- private IntermediateEncryptingTransport create(Intent realTransportIntent) {
- Log.d(TAG, "create: intent:" + realTransportIntent);
-
- LockPatternUtils patternUtils = new LockPatternUtils(mContext);
- boolean shouldEncrypt =
- realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
- && (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
- || patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
-
- return new IntermediateEncryptingTransport(
- mTransportClientManager.getTransportClient(
- realTransportIntent.getComponent(),
- realTransportIntent.getExtras(),
- CALLER),
- mContext,
- shouldEncrypt);
- }
-
- /**
- * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
- * #get(Intent)} with this {@link Intent}.
- */
- public void cleanup(Intent intent) {
- Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
- Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent);
-
- IntermediateEncryptingTransport transport;
- synchronized (mTransportsLock) {
- transport = mTransports.remove(transportIntent.getComponent());
- }
- if (transport != null) {
- mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER);
- } else {
- Log.i(TAG, "Could not find IntermediateEncryptingTransport");
- }
- }
-}
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java
deleted file mode 100644
index 0d43a19..0000000
--- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.transport;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotSame;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.backup.transport.TransportClient;
-import com.android.server.backup.transport.TransportClientManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class IntermediateEncryptingTransportManagerTest {
- @Mock private TransportClient mTransportClient;
- @Mock private TransportClientManager mTransportClientManager;
-
- private final ComponentName mTransportComponent = new ComponentName("pkg", "class");
- private final Bundle mExtras = new Bundle();
- private Intent mEncryptingTransportIntent;
- private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mExtras.putInt("test", 1);
- mEncryptingTransportIntent =
- TransportClientManager.getEncryptingTransportIntent(mTransportComponent)
- .putExtras(mExtras);
- mIntermediateEncryptingTransportManager =
- new IntermediateEncryptingTransportManager(mTransportClientManager);
- }
-
- @Test
- public void testGet_createsClientWithRealTransportComponentAndExtras() {
- when(mTransportClientManager.getTransportClient(any(), any(), any()))
- .thenReturn(mTransportClient);
-
- IntermediateEncryptingTransport intermediateEncryptingTransport =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
-
- assertEquals(mTransportClient, intermediateEncryptingTransport.getClient());
- verify(mTransportClientManager, times(1))
- .getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any());
- verifyNoMoreInteractions(mTransportClientManager);
- }
-
- @Test
- public void testGet_callTwice_returnsSameTransport() {
- IntermediateEncryptingTransport transport1 =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
- IntermediateEncryptingTransport transport2 =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
-
- assertEquals(transport1, transport2);
- }
-
- @Test
- public void testCleanup_disposesTransportClient() {
- when(mTransportClientManager.getTransportClient(any(), any(), any()))
- .thenReturn(mTransportClient);
-
- IntermediateEncryptingTransport transport =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
- mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
-
- verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any());
- verify(mTransportClientManager, times(1))
- .disposeOfTransportClient(eq(mTransportClient), any());
- verifyNoMoreInteractions(mTransportClientManager);
- }
-
- @Test
- public void testCleanup_removesCachedTransport() {
- when(mTransportClientManager.getTransportClient(any(), any(), any()))
- .thenReturn(mTransportClient);
-
- IntermediateEncryptingTransport transport1 =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
- mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
- IntermediateEncryptingTransport transport2 =
- mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
-
- assertNotSame(transport1, transport2);
- }
-}
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
deleted file mode 100644
index a85b2e4..0000000
--- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.transport;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.backup.BackupTransport;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.encryption.KeyValueEncrypter;
-import com.android.server.backup.transport.TransportClient;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class IntermediateEncryptingTransportTest {
- private static final String TEST_PACKAGE_NAME = "test_package";
-
- private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
- private final PackageInfo mTestPackage = new PackageInfo();
-
- @Mock private IBackupTransport mRealTransport;
- @Mock private TransportClient mTransportClient;
- @Mock private ParcelFileDescriptor mParcelFileDescriptor;
- @Mock private KeyValueEncrypter mKeyValueEncrypter;
- @Mock private Context mContext;
-
- @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
- private File mTempFile;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mIntermediateEncryptingTransport =
- new IntermediateEncryptingTransport(
- mTransportClient, mContext, mKeyValueEncrypter, true);
- mTestPackage.packageName = TEST_PACKAGE_NAME;
- mTempFile = mTemporaryFolder.newFile();
-
- when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
- when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
- }
-
- @Test
- public void testGetDelegate_callsConnect() throws Exception {
- IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
-
- assertEquals(mRealTransport, ret);
- verify(mTransportClient, times(1)).connect(anyString());
- verifyNoMoreInteractions(mTransportClient);
- }
-
- @Test
- public void testGetDelegate_callTwice_callsConnectOnce() throws Exception {
- when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
-
- IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate();
- IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate();
-
- assertEquals(mRealTransport, ret1);
- assertEquals(mRealTransport, ret2);
- verify(mTransportClient, times(1)).connect(anyString());
- verifyNoMoreInteractions(mTransportClient);
- }
-
- @Test
- public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
- throws Exception {
- mIntermediateEncryptingTransport =
- new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
-
- mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
-
- verify(mKeyValueEncrypter, times(1))
- .encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
- verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
- }
-
- @Test
- public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
- throws Exception {
- mIntermediateEncryptingTransport =
- new TestIntermediateTransport(
- mTransportClient, mContext, mKeyValueEncrypter, false);
-
- mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
-
- verifyZeroInteractions(mKeyValueEncrypter);
- verify(mRealTransport, times(1))
- .performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
- }
-
- @Test
- public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
- throws Exception {
- mIntermediateEncryptingTransport =
- new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
- mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
-
- mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
-
- verify(mKeyValueEncrypter, times(1))
- .decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
- verify(mRealTransport, times(1)).getRestoreData(any());
- }
-
- @Test
- public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
- throws Exception {
- mIntermediateEncryptingTransport =
- new TestIntermediateTransport(
- mTransportClient, mContext, mKeyValueEncrypter, false);
- mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
-
- mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
-
- verifyZeroInteractions(mKeyValueEncrypter);
- verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
- }
-
- private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
- TestIntermediateTransport(
- TransportClient transportClient,
- Context context,
- KeyValueEncrypter keyValueEncrypter,
- boolean shouldEncrypt) {
- super(transportClient, context, keyValueEncrypter, shouldEncrypt);
- }
-
- @Override
- protected File getBackupTempStorage(String packageName) {
- return mTempFile;
- }
-
- @Override
- protected File getRestoreTempStorage(String packageName) {
- return mTempFile;
- }
- }
-}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 9f07317..126b823 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -42,8 +42,8 @@
import android.companion.BluetoothDeviceFilter;
import android.companion.BluetoothLeDeviceFilter;
import android.companion.DeviceFilter;
+import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceDiscoveryService;
-import android.companion.IFindDeviceCallback;
import android.companion.WifiDeviceFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -92,7 +92,7 @@
AssociationRequest mRequest;
List<DeviceFilterPair> mDevicesFound;
DeviceFilterPair mSelectedDevice;
- IFindDeviceCallback mFindCallback;
+ IAssociationRequestCallback mApplicationCallback;
AndroidFuture<String> mServiceCallback;
boolean mIsScanning = false;
@@ -104,13 +104,13 @@
@Override
public void startDiscovery(AssociationRequest request,
String callingPackage,
- IFindDeviceCallback findCallback,
+ IAssociationRequestCallback appCallback,
AndroidFuture<String> serviceCallback) {
Log.i(LOG_TAG,
"startDiscovery() called with: filter = [" + request
- + "], findCallback = [" + findCallback + "]"
+ + "], appCallback = [" + appCallback + "]"
+ "], serviceCallback = [" + serviceCallback + "]");
- mFindCallback = findCallback;
+ mApplicationCallback = appCallback;
mServiceCallback = serviceCallback;
Handler.getMain().sendMessage(obtainMessage(
CompanionDeviceDiscoveryService::startDiscovery,
@@ -299,7 +299,7 @@
//TODO also, on timeout -> call onFailure
private void onReadyToShowUI() {
try {
- mFindCallback.onSuccess(PendingIntent.getActivity(
+ mApplicationCallback.onAssociationPending(PendingIntent.getActivity(
this, 0,
new Intent(this, CompanionDeviceActivity.class),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 912b468..a901160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -194,7 +194,7 @@
void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
if (BluetoothUtils.D) {
Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device "
- + mDevice.getAlias() + ", newProfileState " + newProfileState);
+ + mDevice.getAnonymizedAddress() + ", newProfileState " + newProfileState);
}
if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF)
{
@@ -745,7 +745,7 @@
}
if (BluetoothUtils.D) {
- Log.d(TAG, "updating profiles for " + mDevice.getAlias());
+ Log.d(TAG, "updating profiles for " + mDevice.getAnonymizedAddress());
BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index a944bf5..71accc4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -47,6 +47,7 @@
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
Settings.System.ADAPTIVE_SLEEP, // moved to secure
+ Settings.System.APPLY_RAMPING_RINGER,
Settings.System.VIBRATE_INPUT_DEVICES,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
Settings.System.TEXT_AUTO_REPLACE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 63acffb..84e9d28 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -117,6 +117,7 @@
VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 8c669d2..01ef34b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -284,10 +284,9 @@
// versionCode of com.android.providers.settings corresponds to SDK_INT
mRestoredFromSdkInt = (int) appVersionCode;
- HashSet<String> movedToGlobal = new HashSet<String>();
- Settings.System.getMovedToGlobalSettings(movedToGlobal);
- Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
+ Set<String> movedToGlobal = getMovedToGlobalSettings();
Set<String> movedToSecure = getMovedToSecureSettings();
+ Set<String> movedToSystem = getMovedToSystemSettings();
Set<String> preservedGlobalSettings = getSettingsToPreserveInRestore(
Settings.Global.CONTENT_URI);
@@ -318,32 +317,23 @@
switch (key) {
case KEY_SYSTEM :
restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
- movedToSecure, R.array.restore_blocked_system_settings,
- dynamicBlockList,
+ movedToSecure, /* movedToSystem= */ null,
+ R.array.restore_blocked_system_settings, dynamicBlockList,
preservedSystemSettings);
mSettingsHelper.applyAudioSettings();
break;
case KEY_SECURE :
- restoreSettings(
- data,
- Settings.Secure.CONTENT_URI,
- movedToGlobal,
- null,
- R.array.restore_blocked_secure_settings,
- dynamicBlockList,
+ restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal,
+ /* movedToSecure= */ null, movedToSystem,
+ R.array.restore_blocked_secure_settings, dynamicBlockList,
preservedSecureSettings);
break;
case KEY_GLOBAL :
- restoreSettings(
- data,
- Settings.Global.CONTENT_URI,
- null,
- movedToSecure,
- R.array.restore_blocked_global_settings,
- dynamicBlockList,
- preservedGlobalSettings);
+ restoreSettings(data, Settings.Global.CONTENT_URI, /* movedToGlobal= */ null,
+ movedToSecure, movedToSystem, R.array.restore_blocked_global_settings,
+ dynamicBlockList, preservedGlobalSettings);
break;
case KEY_WIFI_SUPPLICANT :
@@ -435,10 +425,9 @@
if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version);
if (version <= FULL_BACKUP_VERSION) {
// Generate the moved-to-global lookup table
- HashSet<String> movedToGlobal = new HashSet<String>();
- Settings.System.getMovedToGlobalSettings(movedToGlobal);
- Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
+ Set<String> movedToGlobal = getMovedToGlobalSettings();
Set<String> movedToSecure = getMovedToSecureSettings();
+ Set<String> movedToSystem = getMovedToSystemSettings();
// system settings data first
int nBytes = in.readInt();
@@ -446,22 +435,19 @@
byte[] buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
- movedToSecure, R.array.restore_blocked_system_settings,
- Collections.emptySet(), Collections.emptySet());
+ movedToSecure, /* movedToSystem= */ null,
+ R.array.restore_blocked_system_settings, Collections.emptySet(),
+ Collections.emptySet());
// secure settings
nBytes = in.readInt();
if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
if (nBytes > buffer.length) buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
- restoreSettings(
- buffer,
- nBytes,
- Settings.Secure.CONTENT_URI,
- movedToGlobal,
- null,
- R.array.restore_blocked_secure_settings,
- Collections.emptySet(), Collections.emptySet());
+ restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal,
+ /* movedToSecure= */ null, movedToSystem,
+ R.array.restore_blocked_secure_settings, Collections.emptySet(),
+ Collections.emptySet());
// Global only if sufficiently new
if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -469,10 +455,10 @@
if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of global settings data");
if (nBytes > buffer.length) buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
- movedToGlobal.clear(); // no redirection; this *is* the global namespace
- restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
- movedToSecure, R.array.restore_blocked_global_settings,
- Collections.emptySet(), Collections.emptySet());
+ restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI,
+ /* movedToGlobal= */ null, movedToSecure, movedToSystem,
+ R.array.restore_blocked_global_settings, Collections.emptySet(),
+ Collections.emptySet());
}
// locale
@@ -543,6 +529,13 @@
}
}
+ private Set<String> getMovedToGlobalSettings() {
+ HashSet<String> movedToGlobalSettings = new HashSet<String>();
+ Settings.System.getMovedToGlobalSettings(movedToGlobalSettings);
+ Settings.Secure.getMovedToGlobalSettings(movedToGlobalSettings);
+ return movedToGlobalSettings;
+ }
+
private Set<String> getMovedToSecureSettings() {
Set<String> movedToSecureSettings = new HashSet<>();
Settings.Global.getMovedToSecureSettings(movedToSecureSettings);
@@ -550,6 +543,13 @@
return movedToSecureSettings;
}
+ private Set<String> getMovedToSystemSettings() {
+ Set<String> movedToSystemSettings = new HashSet<>();
+ Settings.Global.getMovedToSystemSettings(movedToSystemSettings);
+ Settings.Secure.getMovedToSystemSettings(movedToSystemSettings);
+ return movedToSystemSettings;
+ }
+
private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
long[] stateChecksums = new long[STATE_SIZE];
@@ -710,8 +710,9 @@
private void restoreSettings(
BackupDataInput data,
Uri contentUri,
- HashSet<String> movedToGlobal,
+ Set<String> movedToGlobal,
Set<String> movedToSecure,
+ Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
Set<String> settingsToPreserve) {
@@ -728,6 +729,7 @@
contentUri,
movedToGlobal,
movedToSecure,
+ movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
settingsToPreserve);
@@ -737,8 +739,9 @@
byte[] settings,
int bytes,
Uri contentUri,
- HashSet<String> movedToGlobal,
+ Set<String> movedToGlobal,
Set<String> movedToSecure,
+ Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
Set<String> settingsToPreserve) {
@@ -749,6 +752,7 @@
contentUri,
movedToGlobal,
movedToSecure,
+ movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
settingsToPreserve);
@@ -760,8 +764,9 @@
int pos,
int bytes,
Uri contentUri,
- HashSet<String> movedToGlobal,
+ Set<String> movedToGlobal,
Set<String> movedToSecure,
+ Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
Set<String> settingsToPreserve) {
@@ -842,6 +847,8 @@
destination = Settings.Global.CONTENT_URI;
} else if (movedToSecure != null && movedToSecure.contains(key)) {
destination = Settings.Secure.CONTENT_URI;
+ } else if (movedToSystem != null && movedToSystem.contains(key)) {
+ destination = Settings.System.CONTENT_URI;
} else {
destination = contentUri;
}
@@ -1192,6 +1199,7 @@
Settings.Secure.CONTENT_URI,
null,
null,
+ null,
blockedSettingsArrayId,
dynamicBlocklist,
preservedSettings);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5d75d4f..a67b565 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1620,7 +1620,11 @@
p.end(token);
// Please insert new settings using the same order as in GlobalSettingsProto.
+ // The rest of the settings were moved to Settings.Secure or Settings.System, and are thus
+ // excluded here since they're deprecated from Settings.Global.
+
// Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated.
+ // Settings.Global.APPLY_RAMPING_RINGER intentionally excluded since it's deprecated.
}
private static void dumpProtoConfigSettingsLocked(
@@ -2953,6 +2957,10 @@
Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
+ dumpSetting(s, p,
+ Settings.System.APPLY_RAMPING_RINGER,
+ SystemSettingsProto.APPLY_RAMPING_RINGER);
+
// Please insert new settings using the same order as in SecureSettingsProto.
// The rest of the settings were moved to Settings.Secure, and are thus excluded here since
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ce7517f..11e4916 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -289,6 +289,12 @@
Settings.Global.getMovedToSecureSettings(sGlobalMovedToSecureSettings);
}
+ // Per all users global settings that moved to the per user system settings.
+ static final Set<String> sGlobalMovedToSystemSettings = new ArraySet<>();
+ static {
+ Settings.Global.getMovedToSystemSettings(sGlobalMovedToSystemSettings);
+ }
+
// Per user secure settings that are cloned for the managed profiles of the user.
private static final Set<String> sSecureCloneToManagedSettings = new ArraySet<>();
static {
@@ -2604,6 +2610,10 @@
if (sGlobalMovedToSecureSettings.contains(name)) {
table = TABLE_SECURE;
}
+
+ if (sGlobalMovedToSystemSettings.contains(name)) {
+ table = TABLE_SYSTEM;
+ }
}
return table;
@@ -3594,7 +3604,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 206;
+ private static final int SETTINGS_VERSION = 207;
private final int mUserId;
@@ -4611,18 +4621,7 @@
if (currentVersion == 174) {
// Version 174: Set the default value for Global Settings: APPLY_RAMPING_RINGER
-
- final SettingsState globalSettings = getGlobalSettingsLocked();
-
- Setting currentRampingRingerSetting = globalSettings.getSettingLocked(
- Settings.Global.APPLY_RAMPING_RINGER);
- if (currentRampingRingerSetting.isNull()) {
- globalSettings.insertSettingOverrideableByRestoreLocked(
- Settings.Global.APPLY_RAMPING_RINGER,
- getContext().getResources().getBoolean(
- R.bool.def_apply_ramping_ringer) ? "1" : "0", null,
- true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
+ // Removed. Moved APPLY_RAMPING_RINGER to System Settings, set in version 206.
currentVersion = 175;
}
@@ -5435,6 +5434,34 @@
currentVersion = 206;
}
+ if (currentVersion == 206) {
+ // Version 206: APPLY_RAMPING_RINGER moved to System settings. Use the old value
+ // for the newly inserted system setting and keep it to be restored to other
+ // users. Set default value if global value is not set.
+ final SettingsState systemSettings = getSystemSettingsLocked(userId);
+ Setting globalValue = getGlobalSettingsLocked()
+ .getSettingLocked(Global.APPLY_RAMPING_RINGER);
+ Setting currentValue = systemSettings
+ .getSettingLocked(Settings.System.APPLY_RAMPING_RINGER);
+ if (currentValue.isNull()) {
+ if (!globalValue.isNull()) {
+ // Recover settings from Global.
+ systemSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.System.APPLY_RAMPING_RINGER, globalValue.getValue(),
+ globalValue.getTag(), globalValue.isDefaultFromSystem(),
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else {
+ // Set default value.
+ systemSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.System.APPLY_RAMPING_RINGER,
+ getContext().getResources().getBoolean(
+ R.bool.def_apply_ramping_ringer) ? "1" : "0",
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+ currentVersion = 207;
+ }
// vXXX: Add new settings above this point.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4aee164..aa6661b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -345,7 +345,6 @@
}
// The settings provider must hold its lock when calling here.
- @GuardedBy("mLock")
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
return mNullSetting;
@@ -385,7 +384,6 @@
}
// The settings provider must hold its lock when calling here.
- @GuardedBy("mLock")
public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag,
boolean makeDefault, String packageName) {
return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index f5334fb..433aac7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -205,8 +205,8 @@
mAgentUnderTest.mSettingsHelper = settingsHelper;
byte[] backupData = generateBackupData(TEST_VALUES);
- mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, new HashSet<>(),
- Collections.emptySet(), /* blockedSettingsArrayId */ 0, Collections.emptySet(),
+ mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI,
+ null, null, null, /* blockedSettingsArrayId */ 0, Collections.emptySet(),
new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI))));
assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING));
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index abd010d..aefe3b7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -90,7 +90,6 @@
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
- <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
@@ -297,9 +296,13 @@
<!-- Permission needed to wipe the device for Test Harness Mode -->
<uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" />
- <!-- Permissions required to test CompanionDeviceManager teses in CTS -->
- <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
+ <!-- Permission needed for CTS test - CompanionDeviceManagerTest -->
<uses-permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9aad278..e5726b0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -27,7 +27,6 @@
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
-import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnPreDrawListener
@@ -87,10 +86,11 @@
// If the parent of the view we are launching from is the background of some other animated
// dialog, then this means the caller intent is to launch a dialog from another dialog. In
// this case, we also animate the parent (which is the dialog background).
- val animatedParent = openedDialogs
- .firstOrNull { it.dialogContentParent == view.parent }
- val parentHostDialog = animatedParent?.hostDialog
- val animateFrom = animatedParent?.dialogContentParent ?: view
+ val animatedParent = openedDialogs.firstOrNull {
+ it.dialogContentWithBackground == view || it.dialogContentWithBackground == view.parent
+ }
+ val dialogContentWithBackground = animatedParent?.dialogContentWithBackground
+ val animateFrom = dialogContentWithBackground ?: view
// Make sure we don't run the launch animation from the same view twice at the same time.
if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
@@ -109,7 +109,7 @@
onDialogDismissed = { openedDialogs.remove(it) },
originalDialog = dialog,
animateBackgroundBoundsChange,
- openedDialogs.firstOrNull { it.hostDialog == parentHostDialog }
+ animatedParent
)
val hostDialog = animatedDialog.hostDialog
openedDialogs.add(animatedDialog)
@@ -288,13 +288,12 @@
private val hostDialogRoot = FrameLayout(context)
/**
- * The parent of the original dialog content view, that serves as a fake window that will have
- * the same size as the original dialog window and to which we will set the original dialog
- * window background.
+ * The dialog content with its background. When animating a fullscreen dialog, this is just the
+ * first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen)
+ * dialog, this is an additional view that serves as a fake window that will have the same size
+ * as the original dialog window and to which we will set the original dialog window background.
*/
- val dialogContentParent = FrameLayout(context).apply {
- id = DIALOG_CONTENT_PARENT_ID
- }
+ var dialogContentWithBackground: ViewGroup? = null
/**
* The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -451,59 +450,87 @@
hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
dialogView.isClickable = true
- // Set the background of the window dialog to the dialog itself.
- // TODO(b/193634619): Support dialog windows without background.
- // TODO(b/193634619): Support dialog whose background comes from the content view instead of
- // the window.
- val typedArray =
- originalDialog.context.obtainStyledAttributes(com.android.internal.R.styleable.Window)
- val backgroundRes =
- typedArray.getResourceId(com.android.internal.R.styleable.Window_windowBackground, 0)
- typedArray.recycle()
- if (backgroundRes == 0) {
- throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
+ // Remove the original dialog view from its parent.
+ (dialogView.parent as? ViewGroup)?.removeView(dialogView)
+
+ val originalDialogWindow = originalDialog.window!!
+ val isOriginalWindowFullScreen =
+ originalDialogWindow.attributes.width == ViewGroup.LayoutParams.MATCH_PARENT &&
+ originalDialogWindow.attributes.height == ViewGroup.LayoutParams.MATCH_PARENT
+ if (isOriginalWindowFullScreen) {
+ // If the original dialog window is fullscreen, then we look for the first ViewGroup
+ // that has a background and animate towards that ViewGroup given that this is probably
+ // what represents the actual dialog view.
+ dialogContentWithBackground = findFirstViewGroupWithBackground(dialogView)
+ ?: throw IllegalStateException("Unable to find ViewGroup with background")
+
+ hostDialogRoot.addView(
+ dialogView,
+
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ )
+ } else {
+ // Add a parent view to the original dialog view to which we will set the original
+ // dialog window background. This View serves as a fake window with background, so that
+ // we are sure that we don't override the original dialog content view paddings with the
+ // window background that usually has insets.
+ dialogContentWithBackground = FrameLayout(context).apply {
+ id = DIALOG_CONTENT_PARENT_ID
+
+ // TODO(b/193634619): Support dialog windows without background.
+ background = originalDialogWindow.decorView?.background
+ ?: throw IllegalStateException(
+ "Dialogs with no backgrounds on window are not supported")
+
+ addView(
+ dialogView,
+
+ // It should match its parent size, which is sized the same as the original
+ // dialog window.
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ )
+ }
+
+ // Add the parent (that has the background) to the host window.
+ hostDialogRoot.addView(
+ dialogContentWithBackground,
+
+ // We give it the size and gravity of its original dialog window.
+ FrameLayout.LayoutParams(
+ originalDialogWindow.attributes.width,
+ originalDialogWindow.attributes.height,
+ originalDialogWindow.attributes.gravity
+ )
+ )
}
- // Add a parent view to the original dialog view to which we will set the original dialog
- // window background. This View serves as a fake window with background, so that we are sure
- // that we don't override the dialog view paddings with the window background that usually
- // has insets.
- dialogContentParent.setBackgroundResource(backgroundRes)
- hostDialogRoot.addView(
- dialogContentParent,
+ val dialogContentWithBackground = this.dialogContentWithBackground!!
- // We give it the size of its original dialog window.
- FrameLayout.LayoutParams(
- originalDialog.window.attributes.width,
- originalDialog.window.attributes.height,
- Gravity.CENTER
- )
- )
+ // Make the dialog and its background invisible for now, to make sure it's not drawn yet.
+ dialogContentWithBackground.visibility = View.INVISIBLE
- // Make the dialog view parent invisible for now, to make sure it's not drawn yet.
- dialogContentParent.visibility = View.INVISIBLE
-
- val background = dialogContentParent.background!!
+ val background = dialogContentWithBackground.background!!
originalDialogBackgroundColor =
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
?.color
?.defaultColor ?: Color.BLACK
- // Add the dialog view to its parent (that has the original window background).
- (dialogView.parent as? ViewGroup)?.removeView(dialogView)
- dialogContentParent.addView(
- dialogView,
-
- // It should match its parent size, which is sized the same as the original dialog
- // window.
- FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
- )
+ if (isOriginalWindowFullScreen) {
+ // If the original window is full screen, the ViewGroup with background might already be
+ // correctly laid out. Make sure we relayout and that the layout listener below is still
+ // called.
+ dialogContentWithBackground.layout(0, 0, 0, 0)
+ dialogContentWithBackground.requestLayout()
+ }
// Start the animation when the dialog is laid out in the center of the host dialog.
- dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+ dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View,
left: Int,
@@ -515,7 +542,7 @@
oldRight: Int,
oldBottom: Int
) {
- dialogContentParent.removeOnLayoutChangeListener(this)
+ dialogContentWithBackground.removeOnLayoutChangeListener(this)
isOriginalDialogViewLaidOut = true
maybeStartLaunchAnimation()
@@ -523,6 +550,25 @@
})
}
+ private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
+ if (view !is ViewGroup) {
+ return null
+ }
+
+ if (view.background != null) {
+ return view
+ }
+
+ for (i in 0 until view.childCount) {
+ val match = findFirstViewGroupWithBackground(view.getChildAt(i))
+ if (match != null) {
+ return match
+ }
+ }
+
+ return null
+ }
+
fun onOriginalDialogSizeChanged() {
// The dialog is the single child of the root.
if (hostDialogRoot.childCount != 1) {
@@ -571,7 +617,8 @@
// at the end of the launch animation, because the lauch animation already correctly
// handles bounds changes.
if (backgroundLayoutListener != null) {
- dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener)
+ dialogContentWithBackground!!
+ .addOnLayoutChangeListener(backgroundLayoutListener)
}
}
)
@@ -638,10 +685,12 @@
(touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
touchSurface.visibility = View.VISIBLE
- dialogContentParent.visibility = View.INVISIBLE
+ val dialogContentWithBackground = this.dialogContentWithBackground!!
+ dialogContentWithBackground.visibility = View.INVISIBLE
if (backgroundLayoutListener != null) {
- dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener)
+ dialogContentWithBackground
+ .removeOnLayoutChangeListener(backgroundLayoutListener)
}
// The animated ghost was just removed. We create a temporary ghost that will be
@@ -674,8 +723,8 @@
) {
// Create 2 ghost controllers to animate both the dialog and the touch surface in the host
// dialog.
- val startView = if (isLaunching) touchSurface else dialogContentParent
- val endView = if (isLaunching) dialogContentParent else touchSurface
+ val startView = if (isLaunching) touchSurface else dialogContentWithBackground!!
+ val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface
val startViewController = GhostedViewLaunchAnimatorController(startView)
val endViewController = GhostedViewLaunchAnimatorController(endView)
startViewController.launchContainer = hostDialogRoot
@@ -736,7 +785,9 @@
}
private fun shouldAnimateDialogIntoView(): Boolean {
- if (exitAnimationDisabled) {
+ // Don't animate if the dialog was previously hidden using hide() (either on the host dialog
+ // or on the original dialog) or if we disabled the exit animation.
+ if (exitAnimationDisabled || !hostDialog.isShowing) {
return false
}
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md
index 51f8516..b0c718d 100644
--- a/packages/SystemUI/docs/keyguard/bouncer.md
+++ b/packages/SystemUI/docs/keyguard/bouncer.md
@@ -2,6 +2,13 @@
[KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM.
+## Supported States
+
+1. Phone, portrait mode - The default and typically only way to view the bouncer. Screen cannot rotate.
+1. Phone, landscape - Can only get into this state via lockscreen activities. Launch camera, rotate to landscape, tap lock icon is one example.
+1. Foldables - Both landscape and portrait are supported. In landscape, the bouncer can appear on either of the hinge and can be dragged to the other side. Also refered to as "OneHandedMode in [KeyguardSecurityContainerController][3]
+1. Tablets - The bouncer is supplemented with user icons and a multi-user switcher, when available.
+
## Components
The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts.
@@ -11,6 +18,8 @@
1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
+Fun fact: Naming comes from the concept of a bouncer at a bar or nightclub, who prevent troublemakers from entering or eject them from the premises.
+
[1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer
[2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController
[3]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityContainerController
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
index e09bf7e..625ce1f 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
@@ -16,5 +16,7 @@
-->
<resources>
+ <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to
+ switch sides -->
<bool name="can_use_one_handed_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
index e09bf7e..4daa648 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
@@ -16,5 +16,11 @@
-->
<resources>
+ <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to
+ switch sides -->
<bool name="can_use_one_handed_bouncer">true</bool>
+
+ <!-- Will display the bouncer on one side of the display, and the current user icon and
+ user switcher on the other side -->
+ <bool name="bouncer_display_user_switcher">true</bool>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index 6176f7c..6194aa0 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -22,5 +22,11 @@
<!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
+ <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to
+ switch sides -->
<bool name="can_use_one_handed_bouncer">false</bool>
+ <!-- Will display the bouncer on one side of the display, and the current user icon and
+ user switcher on the other side -->
+ <bool name="bouncer_display_user_switcher">false</bool>
+
</resources>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 98518c2..4a5b637 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -185,7 +185,7 @@
<LinearLayout
android:id="@+id/turn_on_wifi_layout"
style="@style/InternetDialog.Network"
- android:layout_height="72dp"
+ android:layout_height="@dimen/internet_dialog_wifi_network_height"
android:gravity="center"
android:clickable="false"
android:focusable="false">
@@ -227,7 +227,7 @@
<LinearLayout
android:id="@+id/wifi_connected_layout"
style="@style/InternetDialog.Network"
- android:layout_height="72dp"
+ android:layout_height="@dimen/internet_dialog_wifi_network_height"
android:paddingStart="20dp"
android:paddingEnd="24dp"
android:background="@drawable/settingslib_switch_bar_bg_on"
@@ -249,7 +249,7 @@
android:orientation="vertical"
android:clickable="false"
android:layout_width="wrap_content"
- android:layout_height="72dp"
+ android:layout_height="@dimen/internet_dialog_wifi_network_height"
android:layout_marginEnd="30dp"
android:layout_weight="1"
android:gravity="start|center_vertical">
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index 868331e..f6a2136 100644
--- a/packages/SystemUI/res/layout/internet_list_item.xml
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -25,7 +25,7 @@
<LinearLayout
android:id="@+id/wifi_list"
style="@style/InternetDialog.Network"
- android:layout_height="72dp"
+ android:layout_height="@dimen/internet_dialog_wifi_network_height"
android:paddingStart="20dp"
android:paddingEnd="24dp">
<FrameLayout
@@ -45,7 +45,7 @@
android:orientation="vertical"
android:clickable="false"
android:layout_width="wrap_content"
- android:layout_height="72dp"
+ android:layout_height="@dimen/internet_dialog_wifi_network_height"
android:layout_marginEnd="30dp"
android:layout_weight="1"
android:gravity="start|center_vertical">
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aee1f43..2ab4c0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1240,6 +1240,8 @@
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
+ <!-- The height of the WiFi network in Internet panel. -->
+ <dimen name="internet_dialog_wifi_network_height">72dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">@dimen/match_parent</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b6cdd1b..94f1548 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -373,11 +373,11 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
- <item name="android:windowIsFloating">true</item>
+ <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="Theme.SystemUI.Dialog">
+ <!-- Settings windowFullscreen: true is necessary to be able to intercept touch events -->
+ <!-- that would otherwise be intercepted by the Shade. -->
+ <item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:windowCloseOnTouchOutside">true</item>
</style>
<style name="QSBorderlessButton">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 2dbd5de..78867f7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -481,7 +481,9 @@
* orientation overview.
*/
public void setSkipOverrideUserLockPrefsOnce() {
- mSkipOverrideUserLockPrefsOnce = true;
+ // If live-tile is enabled (recents animation keeps running in overview), there is no
+ // activity switch so the display rotation is not changed, then it is no need to skip.
+ mSkipOverrideUserLockPrefsOnce = !mIsRecentsAnimationRunning;
}
private boolean shouldOverrideUserLockPrefs(final int rotation) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 3ebd652..986f296 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -479,9 +479,7 @@
Resources resources = mView.getResources();
- if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)
- && resources.getBoolean(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
+ if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)) {
gravity = resources.getInteger(
R.integer.keyguard_host_view_one_handed_gravity);
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d4d3d5b..dfbee98 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -496,19 +496,22 @@
}
private boolean canUseOneHandedBouncer() {
- // Is it enabled?
- if (!getResources().getBoolean(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
- return false;
- }
-
- if (!KeyguardSecurityModel.isSecurityViewOneHanded(mCurrentSecurityMode)) {
+ if (!isSecurityViewOneHanded(mCurrentSecurityMode)) {
return false;
}
return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
}
+ /**
+ * Returns whether the given security view should be used in a "one handed" way. This can be
+ * used to change how the security view is drawn (e.g. take up less of the screen, and align to
+ * one side).
+ */
+ private boolean isSecurityViewOneHanded(SecurityMode securityMode) {
+ return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
+ }
+
private void configureOneHandedMode() {
boolean oneHandedMode = canUseOneHandedBouncer();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 69328cd..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -94,13 +94,4 @@
throw new IllegalStateException("Unknown security quality:" + security);
}
}
-
- /**
- * Returns whether the given security view should be used in a "one handed" way. This can be
- * used to change how the security view is drawn (e.g. take up less of the screen, and align to
- * one side).
- */
- public static boolean isSecurityViewOneHanded(SecurityMode securityMode) {
- return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 68132f4..b2ecc614 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -30,6 +31,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -82,14 +84,18 @@
void updateColorAndBackgroundVisibility() {
if (mUseBackground && mLockIcon.getDrawable() != null) {
- mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.textColorPrimary);
+ mLockIconColor = ColorUtils.blendARGB(
+ Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary),
+ Color.WHITE,
+ mDozeAmount);
mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
mBgView.setAlpha(1f - mDozeAmount);
mBgView.setVisibility(View.VISIBLE);
} else {
- mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
- R.attr.wallpaperTextColorAccent);
+ mLockIconColor = ColorUtils.blendARGB(
+ Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent),
+ Color.WHITE,
+ mDozeAmount);
mBgView.setVisibility(View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 5fdf026..bc6d2f6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -23,6 +23,7 @@
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.media.taptotransfer.MediaTttChipController;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -126,6 +127,8 @@
c.getUnfoldTransitionWallpaperController().init();
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
+ // No init method needed, just needs to be gotten so that it's created.
+ getMediaTttChipController();
}
/**
@@ -172,6 +175,9 @@
*/
Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
+ /** */
+ Optional<MediaTttChipController> getMediaTttChipController();
+
/**
* Member injection into the supplied argument.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index a1413f9..458cdc1f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -46,6 +46,10 @@
public static final BooleanFlag NOTIFICATION_UPDATES =
new BooleanFlag(102, true);
+ public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
+ new BooleanFlag(103, false);
+
+
/***************************************/
// 200 - keyguard/lockscreen
public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -114,6 +118,10 @@
public static final BooleanFlag MONET =
new BooleanFlag(800, true, R.bool.flag_monet);
+ /***************************************/
+ // 900 - media
+ public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index fa3ad4e..ff14064 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -31,8 +31,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -56,6 +54,7 @@
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -80,8 +79,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -112,7 +109,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
@@ -123,6 +120,7 @@
import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -239,6 +237,7 @@
private int mSmallestScreenWidthDp;
private final Optional<StatusBar> mStatusBarOptional;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -346,7 +345,8 @@
@Main Handler handler,
PackageManager packageManager,
Optional<StatusBar> statusBarOptional,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -377,6 +377,7 @@
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -435,11 +436,14 @@
}
/**
- * Show the global actions dialog (creating if necessary)
+ * Show the global actions dialog (creating if necessary) or hide it if it's already showing.
*
- * @param keyguardShowing True if keyguard is showing
+ * @param keyguardShowing True if keyguard is showing
+ * @param isDeviceProvisioned True if device is provisioned
+ * @param view The view from which we should animate the dialog when showing it
*/
- public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
+ @Nullable View view) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null && mDialog.isShowing()) {
@@ -451,7 +455,7 @@
mDialog.dismiss();
mDialog = null;
} else {
- handleShow();
+ handleShow(view);
}
}
@@ -483,7 +487,7 @@
}
}
- protected void handleShow() {
+ protected void handleShow(@Nullable View view) {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
@@ -493,8 +497,13 @@
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mDialog.getWindow().setAttributes(attrs);
// Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
- mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
- mDialog.show();
+ mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+
+ if (view != null) {
+ mDialogLaunchAnimator.showFromView(mDialog, view);
+ } else {
+ mDialog.show();
+ }
mWindowManagerFuncs.onGlobalActionsShown();
}
@@ -643,7 +652,7 @@
}
}
- protected void onRotate() {
+ protected void onRefresh() {
// re-allocate actions between main and overflow lists
this.createActionItems();
}
@@ -667,7 +676,7 @@
com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
- mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
+ mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils);
dialog.setOnDismissListener(this);
@@ -702,14 +711,6 @@
}
@Override
- public void onUiModeChanged() {
- mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
- if (mDialog != null && mDialog.isShowing()) {
- mDialog.refreshDialog();
- }
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
if (mDialog != null && mDialog.isShowing()
&& (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) {
@@ -717,6 +718,7 @@
mDialog.refreshDialog();
}
}
+
/**
* Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
* called when the quick access wallet requests dismissal.
@@ -1363,6 +1365,10 @@
final Action action = mAdapter.getItem(position);
if (action instanceof LongPressAction) {
if (mDialog != null) {
+ // Usually clicking an item shuts down the phone, locks, or starts an activity.
+ // We don't want to animate back into the power button when that happens, so we
+ // disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
} else {
Log.w(TAG, "Action long-clicked while mDialog is null.");
@@ -1379,6 +1385,10 @@
if (mDialog != null) {
// don't dismiss the dialog if we're opening the power options menu
if (!(item instanceof PowerOptionsAction)) {
+ // Usually clicking an item shuts down the phone, locks, or starts an
+ // activity. We don't want to animate back into the power button when that
+ // happens, so we disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
}
} else {
@@ -1446,6 +1456,10 @@
final Action action = getItem(position);
if (action instanceof LongPressAction) {
if (mDialog != null) {
+ // Usually clicking an item shuts down the phone, locks, or starts an activity.
+ // We don't want to animate back into the power button when that happens, so we
+ // disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
} else {
Log.w(TAG, "Action long-clicked while mDialog is null.");
@@ -1459,6 +1473,10 @@
Action item = getItem(position);
if (!(item instanceof SilentModeTriStateAction)) {
if (mDialog != null) {
+ // Usually clicking an item shuts down the phone, locks, or starts an activity.
+ // We don't want to animate back into the power button when that happens, so we
+ // disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
} else {
Log.w(TAG, "Action clicked while mDialog is null.");
@@ -1510,6 +1528,10 @@
final Action action = getItem(position);
if (action instanceof LongPressAction) {
if (mDialog != null) {
+ // Usually clicking an item shuts down the phone, locks, or starts an activity.
+ // We don't want to animate back into the power button when that happens, so we
+ // disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
} else {
Log.w(TAG, "Action long-clicked while mDialog is null.");
@@ -1523,6 +1545,10 @@
Action item = getItem(position);
if (!(item instanceof SilentModeTriStateAction)) {
if (mDialog != null) {
+ // Usually clicking an item shuts down the phone, locks, or starts an activity.
+ // We don't want to animate back into the power button when that happens, so we
+ // disable the dialog animation before dismissing.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mDialog.dismiss();
} else {
Log.w(TAG, "Action clicked while mDialog is null.");
@@ -2066,7 +2092,9 @@
case MESSAGE_DISMISS:
if (mDialog != null) {
if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- mDialog.completeDismiss();
+ // Hide instantly.
+ mDialog.hide();
+ mDialog.dismiss();
} else {
mDialog.dismiss();
}
@@ -2113,7 +2141,7 @@
}
@VisibleForTesting
- static class ActionsDialogLite extends Dialog implements DialogInterface,
+ static class ActionsDialogLite extends SystemUIDialog implements DialogInterface,
ColorExtractor.OnColorsChangedListener {
protected final Context mContext;
@@ -2126,13 +2154,12 @@
protected Drawable mBackgroundDrawable;
protected final SysuiColorExtractor mColorExtractor;
private boolean mKeyguardShowing;
- protected boolean mShowing;
protected float mScrimAlpha;
protected final NotificationShadeWindowController mNotificationShadeWindowController;
protected final SysUiState mSysUiState;
private ListPopupWindow mOverflowPopup;
private Dialog mPowerOptionsDialog;
- protected final Runnable mOnRotateCallback;
+ protected final Runnable mOnRefreshCallback;
private UiEventLogger mUiEventLogger;
private GestureDetector mGestureDetector;
private Optional<StatusBar> mStatusBarOptional;
@@ -2151,7 +2178,7 @@
}
@Override
- public boolean onSingleTapConfirmed(MotionEvent e) {
+ public boolean onSingleTapUp(MotionEvent e) {
// Close without opening shade
mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
cancel();
@@ -2189,11 +2216,13 @@
MyOverflowAdapter overflowAdapter,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
+ SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
Optional<StatusBar> statusBarOptional,
KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
- super(context, themeRes);
+ // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
+ // dismiss this dialog when the device is locked.
+ super(context, themeRes, false /* dismissOnDeviceLock */);
mContext = context;
mAdapter = adapter;
mOverflowAdapter = overflowAdapter;
@@ -2202,36 +2231,32 @@
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
mSysUiState = sysuiState;
- mOnRotateCallback = onRotateCallback;
+ mOnRefreshCallback = onRefreshCallback;
mKeyguardShowing = keyguardShowing;
mUiEventLogger = uiEventLogger;
mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
-
mGestureDetector = new GestureDetector(mContext, mGestureListener);
+ }
- // Window initialization
- Window window = getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- // Inflate the decor view, so the attributes below are not overwritten by the theme.
- window.getDecorView();
- window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- window.setLayout(MATCH_PARENT, MATCH_PARENT);
- window.addFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- window.getAttributes().setFitInsetsTypes(0 /* types */);
- setTitle(R.string.global_actions);
-
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
initializeLayout();
}
@Override
+ protected int getWidth() {
+ return MATCH_PARENT;
+ }
+
+ @Override
+ protected int getHeight() {
+ return MATCH_PARENT;
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
}
@@ -2424,122 +2449,32 @@
@Override
public void show() {
super.show();
- // split this up so we can override but still call Dialog.show
- showDialog();
- }
-
- protected void showDialog() {
- mShowing = true;
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
.commitUpdate(mContext.getDisplayId());
-
- ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
- root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- root.setPadding(windowInsets.getStableInsetLeft(),
- windowInsets.getStableInsetTop(),
- windowInsets.getStableInsetRight(),
- windowInsets.getStableInsetBottom());
- return WindowInsets.CONSUMED;
- });
-
- mBackgroundDrawable.setAlpha(0);
- float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
- ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
- alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- alphaAnimator.setDuration(183);
- alphaAnimator.addUpdateListener((animation) -> {
- float animatedValue = animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- });
-
- ObjectAnimator xAnimator =
- ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
- xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- xAnimator.setDuration(350);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, xAnimator);
- animatorSet.start();
}
@Override
public void dismiss() {
- dismissWithAnimation(() -> {
- dismissInternal();
- });
- }
+ dismissOverflow();
+ dismissPowerOptions();
- protected void dismissInternal() {
- mContainer.setTranslationX(0);
- ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
- alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- alphaAnimator.setDuration(233);
- alphaAnimator.addUpdateListener((animation) -> {
- float animatedValue = 1f - animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- });
-
- float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
- ObjectAnimator xAnimator =
- ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
- xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- xAnimator.setDuration(350);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, xAnimator);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- completeDismiss();
- }
- });
-
- animatorSet.start();
-
- // close first, as popup windows will not fade during the animation
- dismissOverflow(false);
- dismissPowerOptions(false);
- }
-
- void dismissWithAnimation(Runnable animation) {
- if (!mShowing) {
- return;
- }
- mShowing = false;
- animation.run();
- }
-
- protected void completeDismiss() {
- mShowing = false;
- dismissOverflow(true);
- dismissPowerOptions(true);
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
.commitUpdate(mContext.getDisplayId());
+
super.dismiss();
}
- protected final void dismissOverflow(boolean immediate) {
+ protected final void dismissOverflow() {
if (mOverflowPopup != null) {
- if (immediate) {
- mOverflowPopup.dismissImmediate();
- } else {
- mOverflowPopup.dismiss();
- }
+ mOverflowPopup.dismiss();
}
}
- protected final void dismissPowerOptions(boolean immediate) {
+ protected final void dismissPowerOptions() {
if (mPowerOptionsDialog != null) {
- if (immediate) {
- mPowerOptionsDialog.dismiss();
- } else {
- mPowerOptionsDialog.dismiss();
- }
+ mPowerOptionsDialog.dismiss();
}
}
@@ -2575,20 +2510,18 @@
}
public void refreshDialog() {
- // ensure dropdown menus are dismissed before re-initializing the dialog
- dismissOverflow(true);
- dismissPowerOptions(true);
+ mOnRefreshCallback.run();
- // re-create dialog
- initializeLayout();
+ // Dismiss the dropdown menus.
+ dismissOverflow();
+ dismissPowerOptions();
+
+ // Update the list as the max number of items per row has probably changed.
mGlobalActionsLayout.updateList();
}
public void onRotate(int from, int to) {
- if (mShowing) {
- mOnRotateCallback.run();
- refreshDialog();
- }
+ refreshDialog();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index c4508e0..96ae646 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -82,7 +82,7 @@
if (mDisabled) return;
mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
- mDeviceProvisionedController.isDeviceProvisioned());
+ mDeviceProvisionedController.isDeviceProvisioned(), null /* view */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 57ac9df..237d077 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -16,11 +16,19 @@
package com.android.systemui.media.dagger;
+import android.content.Context;
+import android.view.WindowManager;
+
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.taptotransfer.MediaTttChipController;
+import com.android.systemui.media.taptotransfer.MediaTttFlags;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
+
+import java.util.Optional;
import javax.inject.Named;
@@ -63,4 +71,18 @@
MediaHostStatesManager statesManager) {
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
+
+ /** */
+ @Provides
+ @SysUISingleton
+ static Optional<MediaTttChipController> providesMediaTttChipController(
+ MediaTttFlags mediaTttFlags,
+ Context context,
+ CommandRegistry commandRegistry,
+ WindowManager windowManager) {
+ if (!mediaTttFlags.isMediaTttEnabled()) {
+ return Optional.empty();
+ }
+ return Optional.of(new MediaTttChipController(context, commandRegistry, windowManager));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
new file mode 100644
index 0000000..85e5b33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * A controller to display and hide the Media Tap-To-Transfer chip. This chip is shown when a user
+ * is currently playing media on a local "media cast sender" device (e.g. a phone) and gets close
+ * enough to a "media cast receiver" device (e.g. a tablet). This chip encourages the user to
+ * transfer the media from the sender device to the receiver device.
+ */
+@SysUISingleton
+class MediaTttChipController @Inject constructor(
+ context: Context,
+ commandRegistry: CommandRegistry,
+ private val windowManager: WindowManager,
+) {
+ init {
+ commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
+ commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
+ }
+
+ private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ width = WindowManager.LayoutParams.MATCH_PARENT
+ height = WindowManager.LayoutParams.MATCH_PARENT
+ gravity = Gravity.CENTER_HORIZONTAL
+ type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
+ title = "Media Tap-To-Transfer Chip View"
+ flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+ format = PixelFormat.TRANSLUCENT
+ setTrustedOverlay()
+ }
+
+ // TODO(b/203800327): Create a layout that matches UX.
+ private val chipView: TextView = TextView(context).apply {
+ text = "Media Tap-To-Transfer Chip"
+ }
+
+ private var chipDisplaying: Boolean = false
+
+ private fun addChip() {
+ if (chipDisplaying) { return }
+ windowManager.addView(chipView, windowLayoutParams)
+ chipDisplaying = true
+ }
+
+ private fun removeChip() {
+ if (!chipDisplaying) { return }
+ windowManager.removeView(chipView)
+ chipDisplaying = false
+ }
+
+ inner class AddChipCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) = addChip()
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG")
+ }
+ }
+
+ inner class RemoveChipCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) = removeChip()
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
+ }
+ }
+
+ companion object {
+ @VisibleForTesting
+ const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add"
+ @VisibleForTesting
+ const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
new file mode 100644
index 0000000..03bc935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** Flags related to media tap-to-transfer. */
+@SysUISingleton
+class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+ /** */
+ fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index fce0c0c..e10e4d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -133,7 +133,7 @@
}
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- globalActionsDialog.showOrHideDialog(false, true)
+ globalActionsDialog.showOrHideDialog(false, true, v)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 883552a..6bba1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -130,6 +130,7 @@
private boolean mCanConfigMobileData;
// Wi-Fi entries
+ private int mWifiNetworkHeight;
@VisibleForTesting
protected WifiEntry mConnectedWifiEntry;
@VisibleForTesting
@@ -187,6 +188,9 @@
window.setWindowAnimations(R.style.Animation_InternetDialog);
+ mWifiNetworkHeight = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height);
+
mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
@@ -335,9 +339,6 @@
mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
mWiFiToggle.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
- if (isChecked) {
- mWifiScanNotifyLayout.setVisibility(View.GONE);
- }
buttonView.setChecked(isChecked);
mWifiManager.setWifiEnabled(isChecked);
});
@@ -427,9 +428,26 @@
mSeeAllLayout.setVisibility(View.GONE);
return;
}
- mWifiRecyclerView.setVisibility(mWifiEntriesCount > 0 ? View.VISIBLE : View.GONE);
- mSeeAllLayout.setVisibility(
- (mConnectedWifiEntry != null || mWifiEntriesCount > 0) ? View.VISIBLE : View.GONE);
+ mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount());
+ mWifiRecyclerView.setVisibility(View.VISIBLE);
+ final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0;
+ mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @VisibleForTesting
+ @MainThread
+ int getWifiListMaxCount() {
+ int count = InternetDialogController.MAX_WIFI_ENTRY_COUNT;
+ if (mEthernetLayout.getVisibility() == View.VISIBLE) {
+ count -= 1;
+ }
+ if (mMobileNetworkLayout.getVisibility() == View.VISIBLE) {
+ count -= 1;
+ }
+ if (mConnectedWifListLayout.getVisibility() == View.VISIBLE) {
+ count -= 1;
+ }
+ return count;
}
@MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index a7f8bca..7bcaf5f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -86,6 +86,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
+import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -162,6 +163,7 @@
private GestureDetector mSwipeDetector;
private SwipeDismissHandler mSwipeDismissHandler;
private InputMonitorCompat mInputMonitor;
+ private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private boolean mShowScrollablePreview;
private String mPackageName = "";
@@ -302,8 +304,8 @@
private void startInputListening() {
stopInputListening();
mInputMonitor = new InputMonitorCompat("Screenshot", Display.DEFAULT_DISPLAY);
- mInputMonitor.getInputReceiver(Looper.getMainLooper(), Choreographer.getInstance(),
- ev -> {
+ mInputEventReceiver = mInputMonitor.getInputReceiver(
+ Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
if (ev instanceof MotionEvent) {
MotionEvent event = (MotionEvent) ev;
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
@@ -320,6 +322,10 @@
mInputMonitor.dispose();
mInputMonitor = null;
}
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
}
@Override // ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 9bf21d1..1432f78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -36,12 +36,15 @@
return false
}
- fun assertLegacyPipelineEnabled(): Nothing =
- error("Old pipeline code running w/ new pipeline enabled")
+ fun assertLegacyPipelineEnabled(): Unit =
+ check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
fun isNewPipelineEnabled(): Boolean =
featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
+ fun isDevLoggingEnabled(): Boolean =
+ featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
+
fun isSmartspaceDedupingEnabled(): Boolean =
featureFlags.isEnabled(Flags.SMARTSPACE) &&
featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 2787975..748c69d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -42,6 +42,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationInteractionTracker;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -87,6 +88,7 @@
private final NotificationInteractionTracker mInteractionTracker;
// used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
+ private final boolean mAlwaysLogList;
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
@@ -119,6 +121,7 @@
@Inject
public ShadeListBuilder(
SystemClock systemClock,
+ NotifPipelineFlags flags,
ShadeListBuilderLogger logger,
DumpManager dumpManager,
NotificationInteractionTracker interactionTracker
@@ -126,6 +129,7 @@
Assert.isMainThread();
mSystemClock = systemClock;
mLogger = logger;
+ mAlwaysLogList = flags.isDevLoggingEnabled();
mInteractionTracker = interactionTracker;
dumpManager.registerDumpable(TAG, this);
@@ -407,7 +411,7 @@
mIterationCount,
mReadOnlyNotifList.size(),
countChildren(mReadOnlyNotifList));
- if (mIterationCount % 10 == 0) {
+ if (mAlwaysLogList || mIterationCount % 10 == 0) {
mLogger.logFinalList(mNotifList);
}
mPipelineState.setState(STATE_IDLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b9f0241..ea25805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6094,6 +6094,14 @@
return mExpandHelperCallback;
}
+ float getAppearFraction() {
+ return mLastSentAppear;
+ }
+
+ float getExpandedHeight() {
+ return mLastSentExpandedHeight;
+ }
+
/** Enum for selecting some or all notification rows (does not included non-notif views). */
@Retention(SOURCE)
@IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index dd68f94..41a80c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -873,6 +873,14 @@
mView.setHeadsUpAppearanceController(controller);
}
+ public float getAppearFraction() {
+ return mView.getAppearFraction();
+ }
+
+ public float getExpandedHeight() {
+ return mView.getExpandedHeight();
+ }
+
public void requestLayout() {
mView.requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 0664900..8a7cf36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.ViewController;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -49,14 +50,15 @@
* Controls the appearance of heads up notifications in the icon area and the header itself.
*/
@StatusBarFragmentScope
-public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
- DarkIconDispatcher.DarkReceiver, NotificationWakeUpCoordinator.WakeUpListener {
+public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
+ implements OnHeadsUpChangedListener,
+ DarkIconDispatcher.DarkReceiver,
+ NotificationWakeUpCoordinator.WakeUpListener {
public static final int CONTENT_FADE_DURATION = 110;
public static final int CONTENT_FADE_DELAY = 100;
private final NotificationIconAreaController mNotificationIconAreaController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationStackScrollLayoutController mStackScrollerController;
- private final HeadsUpStatusBarView mHeadsUpStatusBarView;
private final View mCenteredIconView;
private final View mClockView;
private final View mOperatorNameView;
@@ -72,8 +74,6 @@
@VisibleForTesting
float mExpandedHeight;
@VisibleForTesting
- boolean mIsExpanded;
- @VisibleForTesting
float mAppearFraction;
private ExpandableNotificationRow mTrackedChild;
private boolean mShown;
@@ -127,25 +127,27 @@
View clockView,
View operatorNameView,
View centeredIconView) {
+ super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
mHeadsUpManager = headsUpManager;
- mHeadsUpManager.addListener(this);
- mHeadsUpStatusBarView = headsUpStatusBarView;
mCenteredIconView = centeredIconView;
- headsUpStatusBarView.setOnDrawingRectChangedListener(
- () -> updateIsolatedIconLocation(true /* requireUpdate */));
+
+ // We may be mid-HUN-expansion when this controller is re-created (for example, if the user
+ // has started pulling down the notification shade from the HUN and then the font size
+ // changes). We need to re-fetch these values since they're used to correctly display the
+ // HUN during this shade expansion.
+ mTrackedChild = notificationPanelViewController.getTrackedHeadsUpNotification();
+ mAppearFraction = stackScrollerController.getAppearFraction();
+ mExpandedHeight = stackScrollerController.getExpandedHeight();
+
mStackScrollerController = stackScrollerController;
mNotificationPanelViewController = notificationPanelViewController;
- notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
- notificationPanelViewController.setHeadsUpAppearanceController(this);
- mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
mOperatorNameView = operatorNameView;
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
- mDarkIconDispatcher.addDarkReceiver(this);
- mHeadsUpStatusBarView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
@@ -155,21 +157,32 @@
// trigger scroller to notify the latest panel translation
mStackScrollerController.requestLayout();
}
- mHeadsUpStatusBarView.removeOnLayoutChangeListener(this);
+ mView.removeOnLayoutChangeListener(this);
}
});
mBypassController = bypassController;
mStatusBarStateController = stateController;
mWakeUpCoordinator = wakeUpCoordinator;
- wakeUpCoordinator.addListener(this);
mCommandQueue = commandQueue;
mKeyguardStateController = keyguardStateController;
}
+ @Override
+ protected void onViewAttached() {
+ mHeadsUpManager.addListener(this);
+ mView.setOnDrawingRectChangedListener(
+ () -> updateIsolatedIconLocation(true /* requireUpdate */));
+ mWakeUpCoordinator.addListener(this);
+ mNotificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
+ mNotificationPanelViewController.setHeadsUpAppearanceController(this);
+ mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
+ mDarkIconDispatcher.addDarkReceiver(this);
+ }
- public void destroy() {
+ @Override
+ protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
- mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
+ mView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
mNotificationPanelViewController.setHeadsUpAppearanceController(null);
@@ -179,7 +192,7 @@
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
mNotificationIconAreaController.setIsolatedIconLocation(
- mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
+ mView.getIconDrawingRect(), requireStateUpdate);
}
@Override
@@ -193,20 +206,20 @@
if (shouldBeVisible()) {
newEntry = mHeadsUpManager.getTopEntry();
}
- NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
- mHeadsUpStatusBarView.setEntry(newEntry);
+ NotificationEntry previousEntry = mView.getShowingEntry();
+ mView.setEntry(newEntry);
if (newEntry != previousEntry) {
boolean animateIsolation = false;
if (newEntry == null) {
// no heads up anymore, lets start the disappear animation
setShown(false);
- animateIsolation = !mIsExpanded;
+ animateIsolation = !isExpanded();
} else if (previousEntry == null) {
// We now have a headsUp and didn't have one before. Let's start the disappear
// animation
setShown(true);
- animateIsolation = !mIsExpanded;
+ animateIsolation = !isExpanded();
}
updateIsolatedIconLocation(false /* requireUpdate */);
mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
@@ -219,8 +232,8 @@
mShown = isShown;
if (isShown) {
updateParentClipping(false /* shouldClip */);
- mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
- show(mHeadsUpStatusBarView);
+ mView.setVisibility(View.VISIBLE);
+ show(mView);
hide(mClockView, View.INVISIBLE);
if (mCenteredIconView.getVisibility() != View.GONE) {
hide(mCenteredIconView, View.INVISIBLE);
@@ -236,21 +249,21 @@
if (mOperatorNameView != null) {
show(mOperatorNameView);
}
- hide(mHeadsUpStatusBarView, View.GONE, () -> {
+ hide(mView, View.GONE, () -> {
updateParentClipping(true /* shouldClip */);
});
}
// Show the status bar icons when the view gets shown / hidden
if (mStatusBarStateController.getState() != StatusBarState.SHADE) {
mCommandQueue.recomputeDisableFlags(
- mHeadsUpStatusBarView.getContext().getDisplayId(), false);
+ mView.getContext().getDisplayId(), false);
}
}
}
private void updateParentClipping(boolean shouldClip) {
ViewClippingUtil.setClippingDeactivated(
- mHeadsUpStatusBarView, !shouldClip, mParentClippingParams);
+ mView, !shouldClip, mParentClippingParams);
}
/**
@@ -319,7 +332,7 @@
*/
public boolean shouldBeVisible() {
boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden();
- boolean canShow = !mIsExpanded && notificationsShown;
+ boolean canShow = !isExpanded() && notificationsShown;
if (mBypassController.getBypassEnabled() &&
(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
|| mKeyguardStateController.isKeyguardGoingAway())
@@ -337,17 +350,17 @@
public void setAppearFraction(float expandedHeight, float appearFraction) {
boolean changed = expandedHeight != mExpandedHeight;
+ boolean oldIsExpanded = isExpanded();
+
mExpandedHeight = expandedHeight;
mAppearFraction = appearFraction;
- boolean isExpanded = expandedHeight > 0;
// We only notify if the expandedHeight changed and not on the appearFraction, since
// otherwise we may run into an infinite loop where the panel and this are constantly
// updating themselves over just a small fraction
if (changed) {
updateHeadsUpHeaders();
}
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
+ if (isExpanded() != oldIsExpanded) {
updateTopEntry();
}
}
@@ -367,6 +380,10 @@
}
}
+ private boolean isExpanded() {
+ return mExpandedHeight > 0;
+ }
+
private void updateHeadsUpHeaders() {
mHeadsUpManager.getAllEntries().forEach(entry -> {
updateHeader(entry);
@@ -385,22 +402,13 @@
@Override
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
+ mView.onDarkChanged(area, darkIntensity, tint);
}
public void onStateChanged() {
updateTopEntry();
}
- void readFrom(HeadsUpAppearanceController oldController) {
- if (oldController != null) {
- mTrackedChild = oldController.mTrackedChild;
- mExpandedHeight = oldController.mExpandedHeight;
- mIsExpanded = oldController.mIsExpanded;
- mAppearFraction = oldController.mAppearFraction;
- }
- }
-
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
updateTopEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 16aac4d..8931874 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -480,9 +480,13 @@
private boolean mUserSetupComplete;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
- private ArrayList<Consumer<ExpandableNotificationRow>>
- mTrackingHeadsUpListeners =
- new ArrayList<>();
+ /**
+ * Non-null if there's a heads-up notification that we're currently tracking the position of.
+ */
+ @Nullable
+ private ExpandableNotificationRow mTrackedHeadsUpNotification;
+ private final ArrayList<Consumer<ExpandableNotificationRow>>
+ mTrackingHeadsUpListeners = new ArrayList<>();
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private int mPanelAlpha;
@@ -3198,18 +3202,24 @@
mQsExpandImmediate = false;
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false);
mTwoFingerQsExpandPossible = false;
- notifyListenersTrackingHeadsUp(null);
+ updateTrackingHeadsUp(null);
mExpandingFromHeadsUp = false;
setPanelScrimMinFraction(0.0f);
}
- private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
+ private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) {
+ mTrackedHeadsUpNotification = pickedChild;
for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
listener.accept(pickedChild);
}
}
+ @Nullable
+ public ExpandableNotificationRow getTrackedHeadsUpNotification() {
+ return mTrackedHeadsUpNotification;
+ }
+
private void setListening(boolean listening) {
mKeyguardStatusBarViewController.setBatteryListening(listening);
if (mQs == null) return;
@@ -3446,7 +3456,7 @@
public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
if (pickedChild != null) {
- notifyListenersTrackingHeadsUp(pickedChild);
+ updateTrackingHeadsUp(pickedChild);
mExpandingFromHeadsUp = true;
}
// otherwise we update the state when the expansion is finished
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d26ac29..e88a978 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -666,7 +666,6 @@
private boolean mNoAnimationOnNextBarModeChange;
private final SysuiStatusBarStateController mStatusBarStateController;
- private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
protected StatusBarNotificationPresenter mPresenter;
@@ -1169,21 +1168,6 @@
mNotificationPanelViewController.updatePanelExpansionAndVisibility();
setBouncerShowingForStatusBarComponents(mBouncerShowing);
- HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
- if (mHeadsUpAppearanceController != null) {
- // This view is being recreated, let's destroy the old one
- // TODO(b/205609837): Automatically destroy the old controller so that this
- // class doesn't need to hold a reference to the old one.
- mHeadsUpAppearanceController.destroy();
- }
- // TODO (b/136993073) Separate notification shade and status bar
- // TODO(b/205609837): Migrate this to StatusBarFragmentComponent.
- mHeadsUpAppearanceController =
- statusBarFragmentComponent.getHeadsUpAppearanceController();
- // TODO(b/205609837): Delete this readFrom method so that this class doesn't
- // need to hold a reference to the old controller.
- mHeadsUpAppearanceController.readFrom(oldController);
-
mLightsOutNotifController.setLightsOutNotifView(
mStatusBarView.findViewById(R.id.notification_lights_out));
mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 6e31192..7e39664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -57,6 +57,7 @@
// No one accesses this controller, so we need to make sure we reference it here so it does
// get initialized.
getBatteryMeterViewController().init();
+ getHeadsUpAppearanceController().init();
}
/** */
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
index 6f2c565..ac1a83c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
@@ -80,8 +80,6 @@
// Explicitly disable one handed keyguard.
mTestableResources.addOverride(
R.bool.can_use_one_handed_bouncer, false);
- mTestableResources.addOverride(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, false);
when(mKeyguardSecurityContainerControllerFactory.create(any(
KeyguardSecurityContainer.SecurityCallback.class)))
@@ -149,8 +147,6 @@
// Start disabled.
mTestableResources.addOverride(
R.bool.can_use_one_handed_bouncer, false);
- mTestableResources.addOverride(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, false);
mKeyguardHostViewController.init();
assertEquals(
@@ -160,8 +156,6 @@
// And enable
mTestableResources.addOverride(
R.bool.can_use_one_handed_bouncer, true);
- mTestableResources.addOverride(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, true);
mKeyguardHostViewController.updateResources();
assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 64bdc2e..d04acb5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -251,11 +251,8 @@
}
@Test
- public void showSecurityScreen_oneHandedMode_bothFlagsDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */false);
-
+ public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
+ when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(false);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
@@ -265,39 +262,8 @@
}
@Test
- public void showSecurityScreen_oneHandedMode_deviceFlagDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */true);
-
- when(mKeyguardSecurityViewFlipperController.getSecurityView(
- eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
- .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
-
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).setOneHandedMode(false);
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_sysUiFlagDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */false);
-
- when(mKeyguardSecurityViewFlipperController.getSecurityView(
- eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
- .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
-
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).setOneHandedMode(false);
- }
-
- @Test
- public void showSecurityScreen_oneHandedMode_bothFlagsEnabled_oneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */true);
-
+ public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
+ when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(true);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
@@ -307,11 +273,8 @@
}
@Test
- public void showSecurityScreen_twoHandedMode_bothFlagsEnabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */true);
-
+ public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
+ when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(true);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
@@ -319,15 +282,4 @@
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).setOneHandedMode(false);
}
-
- private void setUpKeyguardFlags(
- boolean deviceConfigCanUseOneHandedKeyguard,
- boolean sysuiResourceCanUseOneHandedKeyguard) {
- when(mResources.getBoolean(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning))
- .thenReturn(deviceConfigCanUseOneHandedKeyguard);
- when(mResources.getBoolean(
- R.bool.can_use_one_handed_bouncer))
- .thenReturn(sysuiResourceCanUseOneHandedKeyguard);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 3b1c5f3..bf5522c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -54,6 +54,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.model.SysUiState;
@@ -115,6 +116,7 @@
@Mock private UserContextProvider mUserContextProvider;
@Mock private StatusBar mStatusBar;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
private TestableLooper mTestableLooper;
@@ -159,8 +161,8 @@
mHandler,
mPackageManager,
Optional.of(mStatusBar),
- mKeyguardUpdateMonitor
- );
+ mKeyguardUpdateMonitor,
+ mDialogLaunchAnimator);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
@@ -218,7 +220,7 @@
GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
- gestureListener.onSingleTapConfirmed(null);
+ gestureListener.onSingleTapUp(null);
verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
}
@@ -444,12 +446,12 @@
// When entering power menu from lockscreen, with smart lock enabled
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- mGlobalActionsDialogLite.showOrHideDialog(true, true);
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
// Then smart lock will be disabled
verify(mLockPatternUtils).requireCredentialEntry(eq(user));
// hide dialog again
- mGlobalActionsDialogLite.showOrHideDialog(true, true);
+ mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
new file mode 100644
index 0000000..efb4931
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer
+
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.Executor
+
+@SmallTest
+class MediaTttChipControllerTest : SysuiTestCase() {
+
+ private lateinit var mediaTttChipController: MediaTttChipController
+
+ private val inlineExecutor = Executor { command -> command.run() }
+ private val commandRegistry = CommandRegistry(context, inlineExecutor)
+ private val pw = PrintWriter(StringWriter())
+
+ @Mock
+ private lateinit var windowManager: WindowManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mediaTttChipController = MediaTttChipController(context, commandRegistry, windowManager)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun constructor_addCommmandAlreadyRegistered() {
+ // Since creating the chip controller should automatically register the add command, it
+ // should throw when registering it again.
+ commandRegistry.registerCommand(
+ MediaTttChipController.ADD_CHIP_COMMAND_TAG
+ ) { EmptyCommand() }
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun constructor_removeCommmandAlreadyRegistered() {
+ // Since creating the chip controller should automatically register the remove command, it
+ // should throw when registering it again.
+ commandRegistry.registerCommand(
+ MediaTttChipController.REMOVE_CHIP_COMMAND_TAG
+ ) { EmptyCommand() }
+ }
+
+ @Test
+ fun addChipCommand_chipAdded() {
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+
+ verify(windowManager).addView(any(), any())
+ }
+
+ @Test
+ fun addChipCommand_twice_chipNotAddedTwice() {
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ reset(windowManager)
+
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun removeChipCommand_chipRemoved() {
+ // First, add the chip
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG))
+
+ // Then, remove it
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun removeChipCommand_noAdd_viewNotRemoved() {
+ commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
+
+ verify(windowManager, never()).removeView(any())
+ }
+
+ class EmptyCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ }
+
+ override fun help(pw: PrintWriter) {
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index b32b4d4..339d5bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -1,5 +1,7 @@
package com.android.systemui.qs.tiles.dialog;
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -219,7 +221,7 @@
}
@Test
- public void updateDialog_wifiOnAndNoWifiEntry_hideWifiEntryAndSeeAll() {
+ public void updateDialog_wifiOnAndNoWifiEntry_showWifiListAndSeeAllArea() {
// The precondition WiFi ON is already in setUp()
mInternetDialog.mConnectedWifiEntry = null;
mInternetDialog.mWifiEntriesCount = 0;
@@ -227,19 +229,21 @@
mInternetDialog.updateDialog(false);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
- assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
- assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ // Show a blank block to fix the dialog height even if there is no WiFi list
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
- public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifiAndSeeAll() {
+ public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.mWifiEntriesCount = 0;
mInternetDialog.updateDialog(false);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ // Show a blank block to fix the dialog height even if there is no WiFi list
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -412,4 +416,54 @@
assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
}
+
+ @Test
+ public void getWifiListMaxCount_returnCountCorrectly() {
+ // Ethernet, MobileData, ConnectedWiFi are all hidden.
+ // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT.
+ setNetworkVisible(false, false, false);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT);
+
+ // Only one of Ethernet, MobileData, ConnectedWiFi is displayed.
+ // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 1.
+ setNetworkVisible(true, false, false);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
+
+ setNetworkVisible(false, true, false);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
+
+ setNetworkVisible(false, false, true);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
+
+ // Only one of Ethernet, MobileData, ConnectedWiFi is hidden.
+ // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 2.
+ setNetworkVisible(true, true, false);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+
+ setNetworkVisible(true, false, true);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+
+ setNetworkVisible(false, true, true);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+
+ // Ethernet, MobileData, ConnectedWiFi are all displayed.
+ // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 3.
+ setNetworkVisible(true, true, true);
+
+ assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 3);
+ }
+
+ private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
+ boolean connectedWifiVisible) {
+ mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE);
+ mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
+ mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index b254ed4..82cd9fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -52,6 +51,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationInteractionTracker;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
@@ -96,7 +96,9 @@
private ShadeListBuilder mListBuilder;
private FakeSystemClock mSystemClock = new FakeSystemClock();
+ @Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private ShadeListBuilderLogger mLogger;
+ @Mock private DumpManager mDumpManager;
@Mock private NotifCollection mNotifCollection;
@Mock private NotificationInteractionTracker mInteractionTracker;
@Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
@@ -122,7 +124,12 @@
allowTestableLooperAsMainThread();
mListBuilder = new ShadeListBuilder(
- mSystemClock, mLogger, mock(DumpManager.class), mInteractionTracker);
+ mSystemClock,
+ mNotifPipelineFlags,
+ mLogger,
+ mDumpManager,
+ mInteractionTracker
+ );
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index bafbccd..db5fd26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -162,8 +162,11 @@
}
@Test
- public void testHeaderReadFromOldController() {
- mHeadsUpAppearanceController.setAppearFraction(1.0f, 1.0f);
+ public void constructor_animationValuesUpdated() {
+ float appearFraction = .75f;
+ float expandedHeight = 400f;
+ when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction);
+ when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight);
HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
@@ -179,14 +182,9 @@
new View(mContext),
new View(mContext),
new View(mContext));
- newController.readFrom(mHeadsUpAppearanceController);
- Assert.assertEquals(mHeadsUpAppearanceController.mExpandedHeight,
- newController.mExpandedHeight, 0.0f);
- Assert.assertEquals(mHeadsUpAppearanceController.mAppearFraction,
- newController.mAppearFraction, 0.0f);
- Assert.assertEquals(mHeadsUpAppearanceController.mIsExpanded,
- newController.mIsExpanded);
+ Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
+ Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
}
@Test
@@ -195,7 +193,9 @@
reset(mDarkIconDispatcher);
reset(mPanelView);
reset(mStackScrollerController);
- mHeadsUpAppearanceController.destroy();
+
+ mHeadsUpAppearanceController.onViewDetached();
+
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
verify(mPanelView).removeTrackingHeadsUpListener(any());
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f3a5d35..52a6dc1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2382,6 +2382,7 @@
somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
somethingChanged |= readHighTextContrastEnabledSettingLocked(userState);
+ somethingChanged |= readAudioDescriptionEnabledSettingLocked(userState);
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
@@ -2454,6 +2455,19 @@
return false;
}
+ private boolean readAudioDescriptionEnabledSettingLocked(AccessibilityUserState userState) {
+ final boolean audioDescriptionByDefaultEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, 0,
+ userState.mUserId) == 1;
+ if (audioDescriptionByDefaultEnabled
+ != userState.isAudioDescriptionByDefaultEnabledLocked()) {
+ userState.setAudioDescriptionByDefaultEnabledLocked(audioDescriptionByDefaultEnabled);
+ return true;
+ }
+ return false;
+ }
+
private void updateTouchExplorationLocked(AccessibilityUserState userState) {
boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
boolean serviceHandlesDoubleTapEnabled = false;
@@ -3418,6 +3432,23 @@
}
}
+ /**
+ * Gets the status of the audio description preference.
+ * @return {@code true} if the audio description is enabled, {@code false} otherwise.
+ */
+ @Override
+ public boolean isAudioDescriptionByDefaultEnabled() {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".isAudioDescriptionByDefaultEnabled",
+ FLAGS_ACCESSIBILITY_MANAGER);
+ }
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+
+ return userState.isAudioDescriptionByDefaultEnabledLocked();
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -3805,6 +3836,9 @@
private final Uri mHighTextContrastUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ private final Uri mAudioDescriptionByDefaultUri = Settings.Secure.getUriFor(
+ Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT);
+
private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
@@ -3851,6 +3885,8 @@
contentResolver.registerContentObserver(
mHighTextContrastUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAudioDescriptionByDefaultUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mShowImeWithHardKeyboardUri, false, this, UserHandle.USER_ALL);
@@ -3904,6 +3940,10 @@
if (readHighTextContrastEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAudioDescriptionByDefaultUri.equals(uri)) {
+ if (readAudioDescriptionEnabledSettingLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mAccessibilitySoftKeyboardModeUri.equals(uri)
|| mShowImeWithHardKeyboardUri.equals(uri)) {
userState.reconcileSoftKeyboardModeWithSettingsLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index e9f5870..bcb3413 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -406,7 +406,7 @@
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
synchronized (mLock) {
- if (mSecurityPolicy.canPerformGestures(this)) {
+ if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
MotionEventInjector motionEventInjector =
mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
if (wmTracingEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 8c3ca34..9324e3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -105,6 +105,7 @@
private String mTargetAssignedToAccessibilityButton;
private boolean mBindInstantServiceAllowed;
+ private boolean mIsAudioDescriptionByDefaultRequested;
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
@@ -411,6 +412,10 @@
if (mIsTextHighContrastEnabled) {
clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
}
+ if (mIsAudioDescriptionByDefaultRequested) {
+ clientState |=
+ AccessibilityManager.STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED;
+ }
clientState |= traceClientState;
@@ -506,6 +511,8 @@
pw.append(", magnificationModes=").append(String.valueOf(mMagnificationModes));
pw.append(", magnificationCapabilities=")
.append(String.valueOf(mMagnificationCapabilities));
+ pw.append(", audioDescriptionByDefaultEnabled=")
+ .append(String.valueOf(mIsAudioDescriptionByDefaultRequested));
pw.append("}");
pw.println();
pw.append(" shortcut key:{");
@@ -824,6 +831,14 @@
mIsTextHighContrastEnabled = enabled;
}
+ public boolean isAudioDescriptionByDefaultEnabledLocked() {
+ return mIsAudioDescriptionByDefaultRequested;
+ }
+
+ public void setAudioDescriptionByDefaultEnabledLocked(boolean enabled) {
+ mIsAudioDescriptionByDefaultRequested = enabled;
+ }
+
public boolean isTouchExplorationEnabledLocked() {
return mIsTouchExplorationEnabled;
}
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 327f087..42a81e1 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -20,7 +20,9 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
+import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -74,6 +76,7 @@
private final PointF mTempPoint = new PointF();
private final Object mLock;
private final Context mContext;
+ @GuardedBy("mLock")
private final SparseArray<DisableMagnificationCallback>
mMagnificationEndRunnableSparseArray = new SparseArray();
@@ -208,7 +211,7 @@
final float scale = mScaleProvider.getScale(displayId);
final DisableMagnificationCallback animationEndCallback =
new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
- scale, magnificationCenter);
+ scale, magnificationCenter, true);
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
screenMagnificationController.reset(displayId, animationEndCallback);
} else {
@@ -218,6 +221,77 @@
setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
}
+ /**
+ * Transitions to the targeting magnification config mode with current center of the
+ * magnification mode if it is available. It disables the current magnifier immediately then
+ * transitions to the targeting magnifier.
+ *
+ * @param displayId The logical display id
+ * @param config The targeting magnification config
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ */
+ public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config,
+ boolean animate) {
+ synchronized (mLock) {
+ final int targetMode = config.getMode();
+ final PointF currentBoundsCenter = getCurrentMagnificationBoundsCenterLocked(displayId,
+ targetMode);
+ final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY());
+ if (currentBoundsCenter != null) {
+ final float centerX = Float.isNaN(config.getCenterX())
+ ? currentBoundsCenter.x
+ : config.getCenterX();
+ final float centerY = Float.isNaN(config.getCenterY())
+ ? currentBoundsCenter.y
+ : config.getCenterY();
+ magnificationCenter.set(centerX, centerY);
+ }
+
+ final DisableMagnificationCallback animationCallback =
+ getDisableMagnificationEndRunnableLocked(displayId);
+ if (animationCallback != null) {
+ Slog.w(TAG, "Discard previous animation request");
+ animationCallback.setExpiredAndRemoveFromListLocked();
+ }
+
+ final FullScreenMagnificationController screenMagnificationController =
+ getFullScreenMagnificationController();
+ final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
+ final float scale = mScaleProvider.getScale(displayId);
+ if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
+ screenMagnificationController.reset(displayId, false);
+ windowMagnificationMgr.enableWindowMagnification(displayId,
+ scale, magnificationCenter.x, magnificationCenter.y,
+ animate ? STUB_ANIMATION_CALLBACK : null);
+ } else if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+ windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
+ if (!screenMagnificationController.isRegistered(displayId)) {
+ screenMagnificationController.register(displayId);
+ }
+ screenMagnificationController.setScaleAndCenter(displayId, scale,
+ magnificationCenter.x, magnificationCenter.y, animate,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+ }
+ }
+
+ /**
+ * Return {@code true} if disable magnification animation callback of the display is running.
+ *
+ * @param displayId The logical display id
+ */
+ public boolean hasDisableMagnificationCallback(int displayId) {
+ synchronized (mLock) {
+ final DisableMagnificationCallback animationCallback =
+ getDisableMagnificationEndRunnableLocked(displayId);
+ if (animationCallback != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void onRequestMagnificationSpec(int displayId, int serviceId) {
final WindowMagnificationManager windowMagnificationManager;
@@ -267,7 +341,8 @@
// Internal request may be for transition, so we just need to check external request.
final boolean isMagnifyByExternalRequest =
fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0;
- if (isMagnifyByExternalRequest) {
+ if (isMagnifyByExternalRequest || isActivated(displayId,
+ ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
fullScreenMagnificationController.reset(displayId, false);
}
}
@@ -282,6 +357,7 @@
mLastActivatedMode = mActivatedMode;
}
logMagnificationModeWithImeOnIfNeeded();
+ disableWindowMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
@@ -293,6 +369,14 @@
updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
}
+ private void disableWindowMagnificationIfNeeded(int displayId) {
+ final WindowMagnificationManager windowMagnificationManager =
+ getWindowMagnificationMgr();
+ if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
+ windowMagnificationManager.disableWindowMagnification(displayId, false);
+ }
+ }
+
@Override
public void onImeWindowVisibilityChanged(boolean shown) {
synchronized (mLock) {
@@ -518,15 +602,17 @@
private final int mCurrentMode;
private final float mCurrentScale;
private final PointF mCurrentCenter = new PointF();
+ private final boolean mAnimate;
- DisableMagnificationCallback(TransitionCallBack transitionCallBack,
- int displayId, int targetMode, float scale, PointF currentCenter) {
+ DisableMagnificationCallback(@Nullable TransitionCallBack transitionCallBack,
+ int displayId, int targetMode, float scale, PointF currentCenter, boolean animate) {
mTransitionCallBack = transitionCallBack;
mDisplayId = displayId;
mTargetMode = targetMode;
mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mCurrentScale = scale;
mCurrentCenter.set(currentCenter);
+ mAnimate = animate;
}
@Override
@@ -544,7 +630,9 @@
applyMagnificationModeLocked(mTargetMode);
}
updateMagnificationButton(mDisplayId, mTargetMode);
- mTransitionCallBack.onResult(mDisplayId, success);
+ if (mTransitionCallBack != null) {
+ mTransitionCallBack.onResult(mDisplayId, success);
+ }
}
}
@@ -569,7 +657,9 @@
setExpiredAndRemoveFromListLocked();
applyMagnificationModeLocked(mCurrentMode);
updateMagnificationButton(mDisplayId, mCurrentMode);
- mTransitionCallBack.onResult(mDisplayId, true);
+ if (mTransitionCallBack != null) {
+ mTransitionCallBack.onResult(mDisplayId, true);
+ }
}
}
@@ -580,9 +670,13 @@
private void applyMagnificationModeLocked(int mode) {
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
- getFullScreenMagnificationController().setScaleAndCenter(mDisplayId,
- mCurrentScale, mCurrentCenter.x,
- mCurrentCenter.y, true,
+ final FullScreenMagnificationController fullScreenMagnificationController =
+ getFullScreenMagnificationController();
+ if (!fullScreenMagnificationController.isRegistered(mDisplayId)) {
+ fullScreenMagnificationController.register(mDisplayId);
+ }
+ fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale,
+ mCurrentCenter.x, mCurrentCenter.y, mAnimate,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
} else {
getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 2324a5a..dda1c4f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -98,6 +98,10 @@
*/
public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config,
boolean animate, int id) {
+ if (transitionModeIfNeeded(displayId, config, animate)) {
+ return true;
+ }
+
int configMode = config.getMode();
if (configMode == DEFAULT_MODE) {
configMode = getControllingMode(displayId);
@@ -114,6 +118,21 @@
}
/**
+ * Returns {@code true} if transition magnification mode needed. And it is no need to transition
+ * mode when the controlling mode is unchanged or the controlling magnifier is not activated.
+ */
+ private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config,
+ boolean animate) {
+ int currentMode = getControllingMode(displayId);
+ if (currentMode == config.getMode()
+ || !mController.hasDisableMagnificationCallback(displayId)) {
+ return false;
+ }
+ mController.transitionMagnificationConfigMode(displayId, config, animate);
+ return true;
+ }
+
+ /**
* Returns the magnification scale. If an animation is in progress,
* this reflects the end state of the animation.
*
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
deleted file mode 100644
index ab87080..0000000
--- a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.transport;
-
-import android.app.backup.BackupAgent;
-import android.app.backup.BackupTransport;
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-
-import com.android.internal.backup.IBackupTransport;
-
-/**
- * Delegates all transport methods to the delegate() implemented in the derived class.
- */
-public abstract class DelegatingTransport extends IBackupTransport.Stub {
- protected abstract IBackupTransport getDelegate() throws RemoteException;
-
- /**
- * Ask the transport for the name under which it should be registered. This will
- * typically be its host service's component name, but need not be.
- */
- @Override
- public String name() throws RemoteException {
- return getDelegate().name();
- }
-
- /**
- * Ask the transport for an Intent that can be used to launch any internal
- * configuration Activity that it wishes to present. For example, the transport
- * may offer a UI for allowing the user to supply login credentials for the
- * transport's off-device backend.
- *
- * If the transport does not supply any user-facing configuration UI, it should
- * return null from this method.
- *
- * @return An Intent that can be passed to Context.startActivity() in order to
- * launch the transport's configuration UI. This method will return null
- * if the transport does not offer any user-facing configuration UI.
- */
- @Override
- public Intent configurationIntent() throws RemoteException {
- return getDelegate().configurationIntent();
- }
-
- /**
- * On demand, supply a one-line string that can be shown to the user that
- * describes the current backend destination. For example, a transport that
- * can potentially associate backup data with arbitrary user accounts should
- * include the name of the currently-active account here.
- *
- * @return A string describing the destination to which the transport is currently
- * sending data. This method should not return null.
- */
- @Override
- public String currentDestinationString() throws RemoteException {
- return getDelegate().currentDestinationString();
- }
-
- /**
- * Ask the transport for an Intent that can be used to launch a more detailed
- * secondary data management activity. For example, the configuration intent might
- * be one for allowing the user to select which account they wish to associate
- * their backups with, and the management intent might be one which presents a
- * UI for managing the data on the backend.
- *
- * <p>In the Settings UI, the configuration intent will typically be invoked
- * when the user taps on the preferences item labeled with the current
- * destination string, and the management intent will be placed in an overflow
- * menu labelled with the management label string.
- *
- * <p>If the transport does not supply any user-facing data management
- * UI, then it should return {@code null} from this method.
- *
- * @return An intent that can be passed to Context.startActivity() in order to
- * launch the transport's data-management UI. This method will return
- * {@code null} if the transport does not offer any user-facing data
- * management UI.
- */
- @Override
- public Intent dataManagementIntent() throws RemoteException {
- return getDelegate().dataManagementIntent();
- }
-
- /**
- * On demand, supply a short {@link CharSequence} that can be shown to the user as the
- * label on
- * an overflow menu item used to invoke the data management UI.
- *
- * @return A {@link CharSequence} to be used as the label for the transport's data management
- * affordance. If the transport supplies a data management intent, this
- * method must not return {@code null}.
- */
- @Override
- public CharSequence dataManagementIntentLabel() throws RemoteException {
- return getDelegate().dataManagementIntentLabel();
- }
-
- /**
- * Ask the transport where, on local device storage, to keep backup state blobs.
- * This is per-transport so that mock transports used for testing can coexist with
- * "live" backup services without interfering with the live bookkeeping. The
- * returned string should be a name that is expected to be unambiguous among all
- * available backup transports; the name of the class implementing the transport
- * is a good choice. This MUST be constant.
- *
- * @return A unique name, suitable for use as a file or directory name, that the
- * Backup Manager could use to disambiguate state files associated with
- * different backup transports.
- */
- @Override
- public String transportDirName() throws RemoteException {
- return getDelegate().transportDirName();
- }
-
- /**
- * Verify that this is a suitable time for a backup pass. This should return zero
- * if a backup is reasonable right now, some positive value otherwise. This method
- * will be called outside of the {@link #startSession}/{@link #endSession} pair.
- *
- * <p>If this is not a suitable time for a backup, the transport should return a
- * backoff delay, in milliseconds, after which the Backup Manager should try again.
- *
- * @return Zero if this is a suitable time for a backup pass, or a positive time delay
- * in milliseconds to suggest deferring the backup pass for a while.
- */
- @Override
- public long requestBackupTime() throws RemoteException {
- return getDelegate().requestBackupTime();
- }
-
- /**
- * Initialize the server side storage for this device, erasing all stored data.
- * The transport may send the request immediately, or may buffer it. After
- * this is called, {@link #finishBackup} must be called to ensure the request
- * is sent and received successfully.
- *
- * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
- * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
- */
- @Override
- public int initializeDevice() throws RemoteException {
- return getDelegate().initializeDevice();
- }
-
- /**
- * Send one application's data to the backup destination. The transport may send
- * the data immediately, or may buffer it. After this is called, {@link #finishBackup}
- * must be called to ensure the data is sent and recorded successfully.
- *
- * @param packageInfo The identity of the application whose data is being backed up.
- * This specifically includes the signature list for the package.
- * @param inFd Descriptor of file with data that resulted from invoking the application's
- * BackupService.doBackup() method. This may be a pipe rather than a file on
- * persistent media, so it may not be seekable.
- * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
- * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
- * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
- * {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
- * become lost due to inactive expiry or some other reason and needs re-initializing)
- */
- @Override
- public int performBackup(PackageInfo packageInfo,
- ParcelFileDescriptor inFd, int flags) throws RemoteException {
- return getDelegate().performBackup(packageInfo, inFd, flags);
- }
-
- /**
- * Erase the give application's data from the backup destination. This clears
- * out the given package's data from the current backup set, making it as though
- * the app had never yet been backed up. After this is called, {@link finishBackup}
- * must be called to ensure that the operation is recorded successfully.
- *
- * @return the same error codes as {@link #performBackup}.
- * @param packageInfo
- */
- @Override
- public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
- return getDelegate().clearBackupData(packageInfo);
- }
-
- /**
- * Finish sending application data to the backup destination. This must be
- * called after {@link #performBackup} or {@link clearBackupData} to ensure that
- * all data is sent. Only when this method returns true can a backup be assumed
- * to have succeeded.
- *
- * @return the same error codes as {@link #performBackup}.
- */
- @Override
- public int finishBackup() throws RemoteException {
- return getDelegate().finishBackup();
- }
-
- /**
- * Get the set of all backups currently available over this transport.
- *
- * @return Descriptions of the set of restore images available for this device,
- * or null if an error occurred (the attempt should be rescheduled).
- **/
- @Override
- public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
- return getDelegate().getAvailableRestoreSets();
- }
-
- /**
- * Get the identifying token of the backup set currently being stored from
- * this device. This is used in the case of applications wishing to restore
- * their last-known-good data.
- *
- * @return A token that can be passed to {@link #startRestore}, or 0 if there
- * is no backup set available corresponding to the current device state.
- */
- @Override
- public long getCurrentRestoreSet() throws RemoteException {
- return getDelegate().getCurrentRestoreSet();
- }
-
- /**
- * Start restoring application data from backup. After calling this function,
- * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
- * to walk through the actual application data.
- *
- * @param token A backup token as returned by {@link #getAvailableRestoreSets}
- * or {@link #getCurrentRestoreSet}.
- * @param packages List of applications to restore (if data is available).
- * Application data will be restored in the order given.
- * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
- * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
- * (an error occurred, the restore should be aborted and rescheduled).
- */
- @Override
- public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
- return getDelegate().startRestore(token, packages);
- }
-
- /**
- * Get the package name of the next application with data in the backup store, plus
- * a description of the structure of the restored archive: either TYPE_KEY_VALUE for
- * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
- *
- * <p>If the package name in the returned RestoreDescription object is the singleton
- * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
- * in the current restore session: all packages described in startRestore() have been
- * processed.
- *
- * <p>If this method returns {@code null}, it means that a transport-level error has
- * occurred and the entire restore operation should be abandoned.
- *
- * @return A RestoreDescription object containing the name of one of the packages
- * supplied to {@link #startRestore} plus an indicator of the data type of that
- * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
- * no more packages can be restored in this session; or {@code null} to indicate
- * a transport-level error.
- */
- @Override
- public RestoreDescription nextRestorePackage() throws RemoteException {
- return getDelegate().nextRestorePackage();
- }
-
- /**
- * Get the data for the application returned by {@link #nextRestorePackage}.
- *
- * @param outFd An open, writable file into which the backup data should be stored.
- * @return the same error codes as {@link #startRestore}.
- */
- @Override
- public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
- return getDelegate().getRestoreData(outFd);
- }
-
- /**
- * End a restore session (aborting any in-process data transfer as necessary),
- * freeing any resources and connections used during the restore process.
- */
- @Override
- public void finishRestore() throws RemoteException {
- getDelegate().finishRestore();
- }
-
- @Override
- public long requestFullBackupTime() throws RemoteException {
- return getDelegate().requestFullBackupTime();
- }
-
- @Override
- public int performFullBackup(PackageInfo targetPackage,
- ParcelFileDescriptor socket, int flags) throws RemoteException {
- return getDelegate().performFullBackup(targetPackage, socket, flags);
- }
-
- @Override
- public int checkFullBackupSize(long size) throws RemoteException {
- return getDelegate().checkFullBackupSize(size);
- }
-
- @Override
- public int sendBackupData(int numBytes) throws RemoteException {
- return getDelegate().sendBackupData(numBytes);
- }
-
- @Override
- public void cancelFullBackup() throws RemoteException {
- getDelegate().cancelFullBackup();
- }
-
- /**
- * Ask the transport whether this app is eligible for backup.
- *
- * @param targetPackage The identity of the application.
- * @param isFullBackup If set, transport should check if app is eligible for full data backup,
- * otherwise to check if eligible for key-value backup.
- * @return Whether this app is eligible for backup.
- */
- @Override
- public boolean isAppEligibleForBackup(PackageInfo targetPackage,
- boolean isFullBackup) throws RemoteException {
- return getDelegate().isAppEligibleForBackup(targetPackage, isFullBackup);
- }
-
- /**
- * Ask the transport about current quota for backup size of the package.
- *
- * @param packageName ID of package to provide the quota.
- * @param isFullBackup If set, transport should return limit for full data backup, otherwise
- * for key-value backup.
- * @return Current limit on full data backup size in bytes.
- */
- @Override
- public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
- return getDelegate().getBackupQuota(packageName, isFullBackup);
- }
-
- /**
- * Ask the transport to provide data for the "current" package being restored. This
- * is the package that was just reported by {@link #nextRestorePackage()} as having
- * {@link RestoreDescription#TYPE_FULL_STREAM} data.
- *
- * The transport writes some data to the socket supplied to this call, and returns
- * the number of bytes written. The system will then read that many bytes and
- * stream them to the application's agent for restore, then will call this method again
- * to receive the next chunk of the archive. This sequence will be repeated until the
- * transport returns zero indicating that all of the package's data has been delivered
- * (or returns a negative value indicating some sort of hard error condition at the
- * transport level).
- *
- * <p>After this method returns zero, the system will then call
- * {@link #getNextFullRestorePackage()} to begin the restore process for the next
- * application, and the sequence begins again.
- *
- * <p>The transport should always close this socket when returning from this method.
- * Do not cache this socket across multiple calls or you may leak file descriptors.
- *
- * @param socket The file descriptor that the transport will use for delivering the
- * streamed archive. The transport must close this socket in all cases when returning
- * from this method.
- * @return 0 when no more data for the current package is available. A positive value
- * indicates the presence of that many bytes to be delivered to the app. Any negative
- * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
- * indicating a fatal error condition that precludes further restore operations
- * on the current dataset.
- */
- @Override
- public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
- return getDelegate().getNextFullRestoreDataChunk(socket);
- }
-
- /**
- * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
- * data for restore, it will invoke this method to tell the transport that it should
- * abandon the data download for the current package. The OS will then either call
- * {@link #nextRestorePackage()} again to move on to restoring the next package in the
- * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
- * operation.
- *
- * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
- * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
- * transport-level failure. If the transport reports an error here, the entire restore
- * operation will immediately be finished with no further attempts to restore app data.
- */
- @Override
- public int abortFullRestore() throws RemoteException {
- return getDelegate().abortFullRestore();
- }
-
- /**
- * Returns flags with additional information about the transport, which is accessible to the
- * {@link BackupAgent}. This allows the agent to decide what to backup or
- * restore based on properties of the transport.
- *
- * <p>For supported flags see {@link BackupAgent}.
- */
- @Override
- public int getTransportFlags() throws RemoteException {
- return getDelegate().getTransportFlags();
- }
-}
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
index 72b1ee7..f907e08 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
@@ -41,12 +41,6 @@
*/
public class TransportClientManager {
private static final String TAG = "TransportClientManager";
- private static final String SERVICE_ACTION_ENCRYPTING_TRANSPORT =
- "android.encryption.BACKUP_ENCRYPTION";
- private static final ComponentName ENCRYPTING_TRANSPORT = new ComponentName(
- "com.android.server.backup.encryption",
- "com.android.server.backup.encryption.BackupEncryptionService");
- private static final String ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY = "transport";
private final @UserIdInt int mUserId;
private final Context mContext;
@@ -57,17 +51,6 @@
private final Function<ComponentName, Intent> mIntentFunction;
/**
- * Return an {@link Intent} which resolves to an intermediate {@link IBackupTransport} that
- * encrypts (or decrypts) the data when sending it (or receiving it) from the {@link
- * IBackupTransport} for the given {@link ComponentName}.
- */
- public static Intent getEncryptingTransportIntent(ComponentName tranportComponent) {
- return new Intent(SERVICE_ACTION_ENCRYPTING_TRANSPORT)
- .setComponent(ENCRYPTING_TRANSPORT)
- .putExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY, tranportComponent);
- }
-
- /**
* Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link
* ComponentName}.
*/
@@ -75,32 +58,6 @@
return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
}
- /**
- * Given a {@link Intent} originally created by {@link
- * #getEncryptingTransportIntent(ComponentName)}, returns the {@link Intent} which resolves to
- * the {@link IBackupTransport} for that {@link ComponentName}.
- */
- public static Intent getRealTransportIntent(Intent encryptingTransportIntent) {
- ComponentName transportComponent = encryptingTransportIntent.getParcelableExtra(
- ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
- Intent intent = getRealTransportIntent(transportComponent)
- .putExtras(encryptingTransportIntent.getExtras());
- intent.removeExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
- return intent;
- }
-
- /**
- * Create a {@link TransportClientManager} such that {@link #getTransportClient(ComponentName,
- * Bundle, String)} returns a {@link TransportClient} which connects to an intermediate {@link
- * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
- * the {@link IBackupTransport} for the given {@link ComponentName}.
- */
- public static TransportClientManager createEncryptingClientManager(@UserIdInt int userId,
- Context context, TransportStats transportStats) {
- return new TransportClientManager(userId, context, transportStats,
- TransportClientManager::getEncryptingTransportIntent);
- }
-
public TransportClientManager(@UserIdInt int userId, Context context,
TransportStats transportStats) {
this(userId, context, transportStats, TransportClientManager::getRealTransportIntent);
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 2645f3f..7d22e9a 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -16,29 +16,26 @@
package com.android.server.companion;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import static com.android.internal.util.CollectionUtils.filter;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
-import static com.android.server.companion.CompanionDeviceManagerService.getCallingUserId;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
+import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.PermissionsUtils.enforceRequestDeviceProfilePermissions;
+import static com.android.server.companion.PermissionsUtils.enforceRequestSelfManagedPermission;
+import static com.android.server.companion.RolesUtils.isRoleHolder;
-import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.role.RoleManager;
+import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
+import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceDiscoveryService;
-import android.companion.IFindDeviceCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -46,8 +43,6 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
import android.util.PackageUtils;
import android.util.Slog;
@@ -60,26 +55,12 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
class AssociationRequestsProcessor {
private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
- private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
- static {
- final Map<String, String> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
- map.put(DEVICE_PROFILE_APP_STREAMING,
- Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
- map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
- Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
-
- DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
- }
-
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
".CompanionDeviceDiscoveryService");
@@ -89,19 +70,15 @@
private final Context mContext;
private final CompanionDeviceManagerService mService;
+ private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
private AssociationRequest mRequest;
- private IFindDeviceCallback mFindDeviceCallback;
- private String mCallingPackage;
+ private IAssociationRequestCallback mAppCallback;
private AndroidFuture<?> mOngoingDeviceDiscovery;
- private RoleManager mRoleManager;
- private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
-
- AssociationRequestsProcessor(CompanionDeviceManagerService service, RoleManager roleManager) {
+ AssociationRequestsProcessor(CompanionDeviceManagerService service) {
mContext = service.getContext();
mService = service;
- mRoleManager = roleManager;
final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -115,30 +92,63 @@
};
}
- void process(AssociationRequest request, IFindDeviceCallback callback, String callingPackage)
- throws RemoteException {
+ /**
+ * Handle incoming {@link AssociationRequest}s, sent via
+ * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
+ */
+ void process(@NonNull AssociationRequest request, @NonNull String packageName,
+ @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) {
+ requireNonNull(request, "Request MUST NOT be null");
+ requireNonNull(packageName, "Package name MUST NOT be null");
+ requireNonNull(callback, "Callback MUST NOT be null");
+
if (DEBUG) {
- Slog.d(TAG, "process(request=" + request + ", from=" + callingPackage + ")");
+ Slog.d(TAG, "process() "
+ + "request=" + request + ", "
+ + "package=u" + userId + "/" + packageName);
}
- checkNotNull(request, "Request cannot be null");
- checkNotNull(callback, "Callback cannot be null");
- mService.checkCallerIsSystemOr(callingPackage);
- int userId = getCallingUserId();
- mService.checkUsesFeature(callingPackage, userId);
+ enforceCallerCanInteractWithUserId(mContext, userId);
+ enforceCallerIsSystemOr(userId, packageName);
+
+ mService.checkUsesFeature(packageName, userId);
+
+ if (request.isSelfManaged()) {
+ enforceRequestSelfManagedPermission(mContext);
+ }
+
final String deviceProfile = request.getDeviceProfile();
- validateDeviceProfileAndCheckPermission(deviceProfile);
+ enforceRequestDeviceProfilePermissions(mContext, deviceProfile);
- mFindDeviceCallback = callback;
- mRequest = request;
- mCallingPackage = callingPackage;
- request.setCallingPackage(callingPackage);
+ synchronized (mService.mLock) {
+ if (mRequest != null) {
+ Slog.w(TAG, "CDM is already processing another AssociationRequest.");
- if (mayAssociateWithoutPrompt(callingPackage, userId)) {
+ try {
+ callback.onFailure("Busy.");
+ } catch (RemoteException e) {
+ // OK to ignore.
+ }
+ return;
+ }
+
+ try {
+ callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0);
+ } catch (RemoteException e) {
+ // The process has died by now: do not proceed.
+ return;
+ }
+
+ mRequest = request;
+ }
+
+ mAppCallback = callback;
+ request.setCallingPackage(packageName);
+
+ if (mayAssociateWithoutPrompt(packageName, userId)) {
Slog.i(TAG, "setSkipPrompt(true)");
request.setSkipPrompt(true);
}
- callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0);
mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
.thenComposeAsync(description -> {
@@ -155,17 +165,16 @@
}
AndroidFuture<String> future = new AndroidFuture<>();
- service.startDiscovery(request, callingPackage, callback, future);
+ service.startDiscovery(request, packageName, callback, future);
return future;
}).cancelTimeout();
}, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
if (err == null) {
mService.createAssociationInternal(
- userId, deviceAddress, callingPackage, deviceProfile);
- mServiceConnectors.forUser(userId).post(service -> {
- service.onAssociationCreated();
- });
+ userId, deviceAddress, packageName, deviceProfile);
+ mServiceConnectors.forUser(userId).post(
+ ICompanionDeviceDiscoveryService::onAssociationCreated);
} else {
Slog.e(TAG, "Failed to discover device(s)", err);
callback.onFailure("No devices found: " + err.getMessage());
@@ -174,54 +183,6 @@
}));
}
- private boolean isRoleHolder(int userId, String packageName, String role) {
- final long identity = Binder.clearCallingIdentity();
- try {
- List<String> holders = mRoleManager.getRoleHoldersAsUser(role, UserHandle.of(userId));
- return holders.contains(packageName);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) {
- if (DEBUG) {
- Slog.d(TAG, "stopScan(request = " + request + ")");
- }
- if (Objects.equals(request, mRequest)
- && Objects.equals(callback, mFindDeviceCallback)
- && Objects.equals(callingPackage, mCallingPackage)) {
- cleanup();
- }
- }
-
- private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
- // Device profile can be null.
- if (deviceProfile == null) return;
-
- if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
- // TODO: remove, when properly supporting this profile.
- throw new UnsupportedOperationException(
- "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
- }
-
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- // TODO: remove, when properly supporting this profile.
- throw new UnsupportedOperationException(
- "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
- }
-
- if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
- throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
- }
-
- final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
- if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
- throw new SecurityException("Application must hold " + permission + " to associate "
- + "with a device with " + deviceProfile + " profile.");
- }
- }
-
private void cleanup() {
if (DEBUG) {
Slog.d(TAG, "cleanup(); discovery = "
@@ -232,20 +193,23 @@
if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
ongoingDeviceDiscovery.cancel(true);
}
- if (mFindDeviceCallback != null) {
- mFindDeviceCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
- mFindDeviceCallback = null;
+ if (mAppCallback != null) {
+ mAppCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
+ mAppCallback = null;
}
mRequest = null;
- mCallingPackage = null;
}
}
private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
- if (mRequest.getDeviceProfile() != null
- && isRoleHolder(userId, packageName, mRequest.getDeviceProfile())) {
- // Don't need to collect user's consent since app already holds the role.
- return true;
+ final String deviceProfile = mRequest.getDeviceProfile();
+ if (deviceProfile != null) {
+ final boolean isRoleHolder = Binder.withCleanCallingIdentity(
+ () -> isRoleHolder(mContext, userId, packageName, deviceProfile));
+ if (isRoleHolder) {
+ // Don't need to collect user's consent since app already holds the role.
+ return true;
+ }
}
String[] sameOemPackages = mContext.getResources()
@@ -261,7 +225,7 @@
// Throttle frequent associations
long now = System.currentTimeMillis();
Set<AssociationInfo> recentAssociations = filter(
- mService.getAllAssociations(userId, packageName),
+ mService.getAssociations(userId, packageName),
a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS);
if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index d5357dc..f8be165 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,11 +17,14 @@
package com.android.server.companion;
+import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
-import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
+import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.add;
import static com.android.internal.util.CollectionUtils.any;
@@ -29,11 +32,16 @@
import static com.android.internal.util.CollectionUtils.find;
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.CollectionUtils.map;
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
+import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.checkCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
+import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
@@ -48,7 +56,6 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
@@ -58,10 +65,10 @@
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
-import android.companion.DeviceId;
import android.companion.DeviceNotAssociatedException;
+import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
-import android.companion.IFindDeviceCallback;
+import android.companion.IOnAssociationsChangedListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -74,13 +81,13 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.Parcel;
import android.os.PowerWhitelistManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -117,7 +124,6 @@
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -159,7 +165,6 @@
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private PowerWhitelistManager mPowerWhitelistManager;
private IAppOpsService mAppOpsManager;
- private RoleManager mRoleManager;
private BluetoothAdapter mBluetoothAdapter;
private UserManager mUserManager;
@@ -202,7 +207,6 @@
mPersistentDataStore = new PersistentDataStore();
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
- mRoleManager = context.getSystemService(RoleManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
@@ -212,7 +216,7 @@
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
mCompanionDevicePresenceController = new CompanionDevicePresenceController();
- mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mRoleManager);
+ mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
registerPackageMonitor();
}
@@ -221,26 +225,28 @@
new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
- Slog.d(LOG_TAG, "onPackageRemoved(packageName = " + packageName
- + ", uid = " + uid + ")");
- int userId = getChangingUserId();
- updateAssociations(
- set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
- userId);
+ final int userId = getChangingUserId();
+ Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
- mCompanionDevicePresenceController.unbindDevicePresenceListener(
- packageName, userId);
+ clearAssociationForPackage(userId, packageName);
+ }
+
+ @Override
+ public void onPackageDataCleared(String packageName, int uid) {
+ final int userId = getChangingUserId();
+ Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName);
+
+ clearAssociationForPackage(userId, packageName);
}
@Override
public void onPackageModified(String packageName) {
- Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")");
- int userId = getChangingUserId();
- forEach(getAllAssociations(userId, packageName), association -> {
- updateSpecialAccessPermissionForAssociatedPackage(association);
- });
- }
+ final int userId = getChangingUserId();
+ Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName);
+ forEach(getAssociations(userId, packageName), association ->
+ updateSpecialAccessPermissionForAssociatedPackage(association));
+ }
}.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
@@ -269,18 +275,82 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- int userHandle = user.getUserIdentifier();
- Set<AssociationInfo> associations = getAllAssociations(userHandle);
- if (associations == null || associations.isEmpty()) {
- return;
- }
- updateAtm(userHandle, associations);
+ final int userId = user.getUserIdentifier();
+ final Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
+
+ if (associations.isEmpty()) return;
+
+ updateAtm(userId, associations);
BackgroundThread.getHandler().sendMessageDelayed(
obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
MINUTES.toMillis(10));
}
+ @NonNull
+ Set<AssociationInfo> getAllAssociationsForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ readPersistedStateForUserIfNeededLocked(userId);
+ // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
+ // we just called adds an empty set, if there was no previously saved data.
+ return mCachedAssociations.get(userId);
+ }
+ }
+
+ @NonNull
+ Set<AssociationInfo> getAssociations(@UserIdInt int userId, @NonNull String packageName) {
+ return filter(getAllAssociationsForUser(userId),
+ a -> a.belongsToPackage(userId, packageName));
+ }
+
+ @Nullable
+ private AssociationInfo getAssociation(int associationId) {
+ return find(getAllAssociations(), association -> association.getId() == associationId);
+ }
+
+ @Nullable
+ AssociationInfo getAssociation(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
+ return find(getAssociations(userId, packageName), a -> a.isLinkedTo(macAddress));
+ }
+
+ @Nullable
+ AssociationInfo getAssociationWithCallerChecks(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
+ return sanitizeWithCallerChecks(getAssociation(userId, packageName, macAddress));
+ }
+
+ @Nullable
+ AssociationInfo getAssociationWithCallerChecks(int associationId) {
+ return sanitizeWithCallerChecks(getAssociation(associationId));
+ }
+
+ @Nullable
+ private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) {
+ if (association == null) return null;
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
+ return null;
+ }
+
+ return association;
+ }
+
+ private Set<AssociationInfo> getAllAssociations() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Set<AssociationInfo> result = new ArraySet<>();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ result.addAll(getAllAssociationsForUser(user.id));
+ }
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
void maybeGrantAutoRevokeExemptions() {
Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()");
PackageManager pm = getContext().getPackageManager();
@@ -293,7 +363,7 @@
}
try {
- Set<AssociationInfo> associations = getAllAssociations(userId);
+ Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
if (associations == null) {
continue;
}
@@ -325,67 +395,92 @@
}
@Override
- public void associate(
- AssociationRequest request,
- IFindDeviceCallback callback,
- String callingPackage) throws RemoteException {
- Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
- + ", callingPackage = " + callingPackage + ")");
- mAssociationRequestsProcessor.process(request, callback, callingPackage);
+ public void associate(AssociationRequest request, IAssociationRequestCallback callback,
+ String packageName, int userId) throws RemoteException {
+ Slog.i(LOG_TAG, "associate() "
+ + "request=" + request + ", "
+ + "package=u" + userId + "/" + packageName);
+ mAssociationRequestsProcessor.process(request, packageName, userId, callback);
}
@Override
- public void stopScan(AssociationRequest request,
- IFindDeviceCallback callback,
- String callingPackage) {
- Slog.i(LOG_TAG, "stopScan(request = " + request + ")");
- mAssociationRequestsProcessor.stopScan(request, callback, callingPackage);
- }
-
- @Override
- public List<String> getAssociations(String callingPackage, int userId)
- throws RemoteException {
- if (!callerCanManageCompanionDevices()) {
- checkCallerIsSystemOr(callingPackage, userId);
- checkUsesFeature(callingPackage, getCallingUserId());
- }
- return new ArrayList<>(map(
- getAllAssociations(userId, callingPackage),
- a -> a.getDeviceMacAddress()));
- }
-
- @Override
- public List<AssociationInfo> getAssociationsForUser(int userId) {
- if (!callerCanManageCompanionDevices()) {
- throw new SecurityException("Caller must hold "
- + android.Manifest.permission.MANAGE_COMPANION_DEVICES);
+ public List<AssociationInfo> getAssociations(String packageName, int userId) {
+ final int callingUid = getCallingUserId();
+ if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
+ throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
+ + "permissions to get associations for u" + userId + "/" + packageName);
}
- return new ArrayList<>(getAllAssociations(userId, null /* packageFilter */));
- }
+ if (!checkCallerCanManagerCompanionDevice(getContext())) {
+ // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
+ // request the feature (also: the caller is the app itself).
+ checkUsesFeature(packageName, getCallingUserId());
+ }
- //TODO also revoke notification access
- @Override
- public void disassociate(String deviceMacAddress, String callingPackage)
- throws RemoteException {
- checkNotNull(deviceMacAddress);
- checkCallerIsSystemOr(callingPackage);
- checkUsesFeature(callingPackage, getCallingUserId());
- removeAssociation(getCallingUserId(), callingPackage, deviceMacAddress);
- }
-
- private boolean callerCanManageCompanionDevices() {
- return getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_COMPANION_DEVICES)
- == PERMISSION_GRANTED;
+ return new ArrayList<>(
+ CompanionDeviceManagerService.this.getAssociations(userId, packageName));
}
@Override
- public PendingIntent requestNotificationAccess(ComponentName component)
+ public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");
+
+ return new ArrayList<>(
+ CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
+ }
+
+ @Override
+ public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
+ int userId) {
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(),
+ "addOnAssociationsChangedListener");
+
+ //TODO: Implement.
+ }
+
+ @Override
+ public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
+ int userId) {
+ //TODO: Implement.
+ }
+
+ @Override
+ public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
+ requireNonNull(deviceMacAddress);
+ requireNonNull(packageName);
+
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
+ if (association == null) {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs ot a different package or a different user).");
+ }
+
+ disassociateInternal(userId, association.getId());
+ }
+
+ @Override
+ public void disassociate(int associationId) {
+ final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (association == null) {
+ throw new IllegalArgumentException("Association with ID " + associationId + " "
+ + "does not exist "
+ + "or belongs to a different package "
+ + "or belongs to a different user");
+ }
+
+ disassociateInternal(association.getUserId(), associationId);
+ }
+
+ @Override
+ public PendingIntent requestNotificationAccess(ComponentName component, int userId)
throws RemoteException {
String callingPackage = component.getPackageName();
checkCanCallNotificationApi(callingPackage);
- int userId = getCallingUserId();
+ //TODO: check userId.
String packageTitle = BidiFormatter.getInstance().unicodeWrap(
getPackageInfo(callingPackage, userId)
.applicationInfo
@@ -425,7 +520,7 @@
public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress,
int userId) {
getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_COMPANION_DEVICES, "isDeviceAssociated");
+ MANAGE_COMPANION_DEVICES, "isDeviceAssociated");
boolean bypassMacPermission = getContext().getPackageManager().checkPermission(
android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName)
@@ -434,23 +529,22 @@
return true;
}
- return any(
- getAllAssociations(userId, packageName),
- a -> Objects.equals(a.getDeviceMacAddress(), macAddress));
+ return any(CompanionDeviceManagerService.this.getAssociations(userId, packageName),
+ a -> a.isLinkedTo(macAddress));
}
@Override
- public void registerDevicePresenceListenerService(
- String packageName, String deviceAddress)
- throws RemoteException {
- registerDevicePresenceListenerActive(packageName, deviceAddress, true);
+ public void registerDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ //TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
}
@Override
- public void unregisterDevicePresenceListenerService(
- String packageName, String deviceAddress)
- throws RemoteException {
- registerDevicePresenceListenerActive(packageName, deviceAddress, false);
+ public void unregisterDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ //TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
}
@Override
@@ -464,12 +558,12 @@
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE,
"[un]registerDevicePresenceListenerService");
- checkCallerIsSystemOr(packageName);
+ final int userId = getCallingUserId();
+ enforceCallerIsSystemOr(userId, packageName);
- int userId = getCallingUserId();
Set<AssociationInfo> deviceAssociations = filter(
- getAllAssociations(userId, packageName),
- association -> deviceAddress.equals(association.getDeviceMacAddress()));
+ CompanionDeviceManagerService.this.getAssociations(userId, packageName),
+ a -> a.isLinkedTo(deviceAddress));
if (deviceAssociations.isEmpty()) {
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
@@ -478,8 +572,8 @@
}
updateAssociations(associations -> map(associations, association -> {
- if (Objects.equals(association.getPackageName(), packageName)
- && Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
+ if (association.belongsToPackage(userId, packageName)
+ && association.isLinkedTo(deviceAddress)) {
association.setNotifyOnDeviceNearby(active);
}
return association;
@@ -503,29 +597,31 @@
createAssociationInternal(userId, macAddress, packageName, null);
}
- private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
- checkCallerIsSystemOr(callingPackage);
- int userId = getCallingUserId();
- checkState(!ArrayUtils.isEmpty(getAllAssociations(userId, callingPackage)),
+ private void checkCanCallNotificationApi(String callingPackage) {
+ final int userId = getCallingUserId();
+ enforceCallerIsSystemOr(userId, callingPackage);
+
+ checkState(!ArrayUtils.isEmpty(
+ CompanionDeviceManagerService.this.getAssociations(userId, callingPackage)),
"App must have an association before calling this API");
checkUsesFeature(callingPackage, userId);
}
@Override
- public boolean canPairWithoutPrompt(
- String packageName, String deviceMacAddress, int userId) {
- return any(
- getAllAssociations(userId, packageName, deviceMacAddress),
- a -> System.currentTimeMillis() - a.getTimeApprovedMs()
- < PAIR_WITHOUT_PROMPT_WINDOW_MS);
+ public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
+ final AssociationInfo association = getAssociation(userId, packageName, macAddress);
+ if (association == null) {
+ return false;
+ }
+ return System.currentTimeMillis() - association.getTimeApprovedMs()
+ < PAIR_WITHOUT_PROMPT_WINDOW_MS;
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_COMPANION_DEVICES, null);
+ enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand");
new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -575,55 +671,14 @@
}
}
- void checkCallerIsSystemOr(String pkg) throws RemoteException {
- checkCallerIsSystemOr(pkg, getCallingUserId());
- }
-
- private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException {
- if (isCallerSystem()) {
- return;
- }
-
- checkArgument(getCallingUserId() == userId,
- "Must be called by either same user or system");
- int callingUid = Binder.getCallingUid();
- if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) {
- throw new SecurityException(pkg + " doesn't belong to uid " + callingUid);
- }
- }
-
- static int getCallingUserId() {
- return UserHandle.getUserId(Binder.getCallingUid());
- }
-
- private static boolean isCallerSystem() {
- return Binder.getCallingUid() == Process.SYSTEM_UID;
- }
-
- void checkUsesFeature(String pkg, int userId) {
- if (isCallerSystem()) {
- // Drop the requirement for calls from system process
- return;
- }
-
- FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures;
- String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
- int numFeatures = ArrayUtils.size(reqFeatures);
- for (int i = 0; i < numFeatures; i++) {
- if (requiredFeature.equals(reqFeatures[i].name)) return;
- }
- throw new IllegalStateException("Must declare uses-feature "
- + requiredFeature
- + " in manifest to use this API");
- }
-
void createAssociationInternal(
int userId, String deviceMacAddress, String packageName, String deviceProfile) {
final AssociationInfo association = new AssociationInfo(
getNewAssociationIdForPackage(userId, packageName),
userId,
packageName,
- Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)),
+ MacAddress.fromString(deviceMacAddress),
+ null,
deviceProfile,
/* managedByCompanionApp */false,
/* notifyOnDeviceNearby */ false ,
@@ -648,8 +703,8 @@
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
- for (AssociationInfo it : getAllAssociations(userId)) {
- usedIds.put(it.getAssociationId(), true);
+ for (AssociationInfo it : getAllAssociationsForUser(userId)) {
+ usedIds.put(it.getId(), true);
}
// Second: collect all IDs that have been previously used for this package (and user).
@@ -674,19 +729,29 @@
}
}
- void removeAssociation(int userId, String packageName, String deviceMacAddress) {
- updateAssociations(associations -> filterOut(associations, it -> {
- final boolean match = it.belongsToPackage(userId, packageName)
- && Objects.equals(it.getDeviceMacAddress(), deviceMacAddress);
- if (match) {
- onAssociationPreRemove(it);
- markIdAsPreviouslyUsedForPackage(it.getAssociationId(), userId, packageName);
- }
- return match;
- }), userId);
+ //TODO also revoke notification access
+ void disassociateInternal(@UserIdInt int userId, int associationId) {
+ updateAssociations(associations ->
+ filterOut(associations, it -> {
+ if (it.getId() != associationId) return false;
+
+ onAssociationPreRemove(it);
+ markIdAsPreviouslyUsedForPackage(
+ it.getId(), it.getUserId(), it.getPackageName());
+ return true;
+ }), userId);
+
restartBleScan();
}
+ void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName);
+
+ mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId);
+ updateAssociations(set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)),
+ userId);
+ }
+
private void markIdAsPreviouslyUsedForPackage(
int associationId, @UserIdInt int userId, @NonNull String packageName) {
synchronized (mLock) {
@@ -707,32 +772,15 @@
String deviceProfile = association.getDeviceProfile();
if (deviceProfile != null) {
AssociationInfo otherAssociationWithDeviceProfile = find(
- getAllAssociations(association.getUserId()),
+ getAllAssociationsForUser(association.getUserId()),
a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile()));
if (otherAssociationWithDeviceProfile != null) {
Slog.i(LOG_TAG, "Not revoking " + deviceProfile
+ " for " + association
+ " - profile still present in " + otherAssociationWithDeviceProfile);
} else {
- final long identity = Binder.clearCallingIdentity();
- try {
- mRoleManager.removeRoleHolderAsUser(
- association.getDeviceProfile(),
- association.getPackageName(),
- RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
- UserHandle.of(association.getUserId()),
- getContext().getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(LOG_TAG, "Failed to revoke device profile role "
- + association.getDeviceProfile()
- + " to " + association.getPackageName()
- + " for user " + association.getUserId());
- }
- });
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ Binder.withCleanCallingIdentity(
+ () -> removeRoleHolderForAssociation(getContext(), association));
}
}
}
@@ -780,9 +828,9 @@
exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
- if (!association.isManagedByCompanionApp()) {
- if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddress())) {
- grantDeviceProfile(association);
+ if (!association.isSelfManaged()) {
+ if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) {
+ addRoleHolderForAssociation(getContext(), association);
}
if (association.isNotifyOnDeviceNearby()) {
@@ -810,17 +858,10 @@
@Nullable
private PackageInfo getPackageInfo(String packageName, int userId) {
- return Binder.withCleanCallingIdentity(PooledLambda.obtainSupplier((context, pkg, id) -> {
- try {
- return context.getPackageManager().getPackageInfoAsUser(
- pkg,
- PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS,
- id);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + pkg, e);
- return null;
- }
- }, getContext(), packageName, userId).recycleOnUse());
+ final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS;
+ return Binder.withCleanCallingIdentity(
+ () -> getContext().getPackageManager()
+ .getPackageInfoAsUser(packageName, flags , userId));
}
private void recordAssociation(AssociationInfo association, int userId) {
@@ -833,7 +874,7 @@
synchronized (mLock) {
if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set...");
- final Set<AssociationInfo> prevAssociations = getAllAssociations(userId);
+ final Set<AssociationInfo> prevAssociations = getAllAssociationsForUser(userId);
if (DEBUG) Slog.d(LOG_TAG, " > Before : " + prevAssociations + "...");
final Set<AssociationInfo> updatedAssociations = update.apply(
@@ -871,15 +912,6 @@
}
}
- @NonNull Set<AssociationInfo> getAllAssociations(int userId) {
- synchronized (mLock) {
- readPersistedStateForUserIfNeededLocked(userId);
- // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method
- // we just called adds an empty set, if there was no previously saved data.
- return mCachedAssociations.get(userId);
- }
- }
-
@GuardedBy("mLock")
private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) {
if (mCachedAssociations.get(userId) != null) return;
@@ -908,49 +940,20 @@
}
}
- Set<AssociationInfo> getAllAssociations(int userId, @Nullable String packageFilter) {
- return filter(
- getAllAssociations(userId),
- // Null filter == get all associations
- a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
- }
-
- private Set<AssociationInfo> getAllAssociations() {
- final long identity = Binder.clearCallingIdentity();
- try {
- ArraySet<AssociationInfo> result = new ArraySet<>();
- for (UserInfo user : mUserManager.getAliveUsers()) {
- result.addAll(getAllAssociations(user.id));
- }
- return result;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private Set<AssociationInfo> getAllAssociations(
- int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
- return filter(
- getAllAssociations(userId),
- // Null filter == get all associations
- a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName()))
- && (addressFilter == null
- || Objects.equals(addressFilter, a.getDeviceMacAddress())));
- }
-
void onDeviceConnected(String address) {
Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
mCurrentlyConnectedDevices.add(address);
for (UserInfo user : getAllUsers()) {
- for (AssociationInfo association : getAllAssociations(user.id)) {
- if (Objects.equals(address, association.getDeviceMacAddress())) {
+ for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
+ if (association.isLinkedTo(address)) {
if (association.getDeviceProfile() != null) {
Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+ " to " + association.getPackageName()
+ " due to device connected: " + association.getDeviceMacAddress());
- grantDeviceProfile(association);
+
+ addRoleHolderForAssociation(getContext(), association);
}
}
}
@@ -959,27 +962,6 @@
onDeviceNearby(address);
}
- private void grantDeviceProfile(AssociationInfo association) {
- Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");
-
- if (association.getDeviceProfile() != null) {
- mRoleManager.addRoleHolderAsUser(
- association.getDeviceProfile(),
- association.getPackageName(),
- RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
- UserHandle.of(association.getUserId()),
- getContext().getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(LOG_TAG, "Failed to grant device profile role "
- + association.getDeviceProfile()
- + " to " + association.getPackageName()
- + " for user " + association.getUserId());
- }
- });
- }
- }
-
void onDeviceDisconnected(String address) {
Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")");
@@ -1113,8 +1095,8 @@
Set<AssociationInfo> result = new ArraySet<>();
for (int i = 0, size = aliveUsers.size(); i < size; i++) {
UserInfo user = aliveUsers.get(i);
- for (AssociationInfo association : getAllAssociations(user.id)) {
- if (Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
+ for (AssociationInfo association : getAllAssociationsForUser(user.id)) {
+ if (association.isLinkedTo(deviceAddress)) {
result.add(association);
}
}
@@ -1224,10 +1206,10 @@
ArrayList<ScanFilter> result = new ArrayList<>();
ArraySet<String> addressesSeen = new ArraySet<>();
for (AssociationInfo association : getAllAssociations()) {
- if (association.isManagedByCompanionApp()) {
+ if (association.isSelfManaged()) {
continue;
}
- String address = association.getDeviceMacAddress();
+ String address = association.getDeviceMacAddressAsString();
if (addressesSeen.contains(address)) {
continue;
}
@@ -1273,4 +1255,19 @@
forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value)));
return copy;
}
+
+ void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
+ if (getCallingUserId() == SYSTEM_UID) return;
+
+ final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures;
+ if (requestedFeatures != null) {
+ for (int i = 0; i < requestedFeatures.length; i++) {
+ if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return;
+ }
+ }
+
+ throw new IllegalStateException("Must declare uses-feature "
+ + FEATURE_COMPANION_DEVICE_SETUP
+ + " in manifest to use this API");
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
index a79db2c..3e00846 100644
--- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
+++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
@@ -67,7 +67,7 @@
Slog.i(LOG_TAG,
"Sending onDeviceAppeared to " + association.getPackageName() + ")");
primaryConnector.run(
- service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
+ s -> s.onDeviceAppeared(association.getDeviceMacAddressAsString()));
}
}
@@ -78,7 +78,7 @@
Slog.i(LOG_TAG,
"Sending onDeviceDisappeared to " + association.getPackageName() + ")");
primaryConnector.run(
- service -> service.onDeviceDisappeared(association.getDeviceMacAddress()));
+ s -> s.onDeviceDisappeared(association.getDeviceMacAddressAsString()));
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e143f5e..3efbffc 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import android.companion.AssociationInfo;
import android.util.Log;
import android.util.Slog;
@@ -37,7 +38,7 @@
switch (cmd) {
case "list": {
forEach(
- mService.getAllAssociations(getNextArgInt()),
+ mService.getAllAssociationsForUser(getNextArgInt()),
a -> getOutPrintWriter()
.println(a.getPackageName() + " "
+ a.getDeviceMacAddress()));
@@ -53,8 +54,14 @@
break;
case "disassociate": {
- mService.removeAssociation(getNextArgInt(), getNextArgRequired(),
- getNextArgRequired());
+ final int userId = getNextArgInt();
+ final String packageName = getNextArgRequired();
+ final String address = getNextArgRequired();
+ final AssociationInfo association =
+ mService.getAssociationWithCallerChecks(userId, packageName, address);
+ if (association != null) {
+ mService.disassociateInternal(userId, association.getId());
+ }
}
break;
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
new file mode 100644
index 0000000..a84bfa51
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Binder.getCallingUid;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.getCallingUserId;
+
+import static java.util.Collections.unmodifiableMap;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+
+import com.android.internal.app.IAppOpsService;
+
+import java.util.Map;
+
+/**
+ * Utility methods for checking permissions required for accessing {@link CompanionDeviceManager}
+ * APIs (such as {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH},
+ * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING},
+ * {@link Manifest.permission#REQUEST_COMPANION_SELF_MANAGED} etc.)
+ */
+final class PermissionsUtils {
+
+ private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
+ static {
+ final Map<String, String> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
+ map.put(DEVICE_PROFILE_APP_STREAMING,
+ Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
+ map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
+ Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
+
+ DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
+ }
+
+ static void enforceRequestDeviceProfilePermissions(
+ @NonNull Context context, @Nullable String deviceProfile) {
+ // Device profile can be null.
+ if (deviceProfile == null) return;
+
+ if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
+ throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
+ }
+
+ if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
+ // TODO: remove, when properly supporting this profile.
+ throw new UnsupportedOperationException(
+ "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
+ }
+
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ // TODO: remove, when properly supporting this profile.
+ throw new UnsupportedOperationException(
+ "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
+ }
+
+ final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
+ if (context.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+ throw new SecurityException("Application must hold " + permission + " to associate "
+ + "with a device with " + deviceProfile + " profile.");
+ }
+ }
+
+ static void enforceRequestSelfManagedPermission(@NonNull Context context) {
+ if (context.checkCallingOrSelfPermission(REQUEST_COMPANION_SELF_MANAGED)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Application does not hold "
+ + REQUEST_COMPANION_SELF_MANAGED);
+ }
+ }
+
+ static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ if (getCallingUserId() == userId) return true;
+
+ return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
+ }
+
+ static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ if (getCallingUserId() == userId) return;
+
+ context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
+ }
+
+ static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return true;
+
+ if (getCallingUserId() != userId) return false;
+
+ if (!checkPackage(callingUid, packageName)) return false;
+
+ return true;
+ }
+
+ static void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return;
+
+ final int callingUserId = getCallingUserId();
+ if (getCallingUserId() != userId) {
+ throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
+ + "the expected UserId (" + userId + ")");
+ }
+
+ if (!checkPackage(callingUid, packageName)) {
+ throw new SecurityException(packageName + " doesn't belong to calling uid ("
+ + callingUid + ")");
+ }
+ }
+
+ static boolean checkCallerCanManagerCompanionDevice(@NonNull Context context) {
+ if (getCallingUserId() == SYSTEM_UID) return true;
+
+ return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
+ }
+
+ static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
+ @Nullable String message) {
+ if (getCallingUserId() == SYSTEM_UID) return;
+
+ context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
+ }
+
+ static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ if (checkCallerIsSystemOr(userId, packageName)) return true;
+
+ if (!checkCallerCanInteractWithUserId(context, userId)) return false;
+
+ return checkCallerCanManagerCompanionDevice(context);
+ }
+
+ private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
+ try {
+ return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Can't happen: AppOpsManager is running in the same process.
+ return true;
+ }
+ }
+
+ private static IAppOpsService getAppOpsService() {
+ if (sAppOpsService == null) {
+ synchronized (PermissionsUtils.class) {
+ if (sAppOpsService == null) {
+ sAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+ }
+ }
+ return sAppOpsService;
+ }
+
+ // DO NOT USE DIRECTLY! Access via getAppOpsService().
+ private static IAppOpsService sAppOpsService = null;
+
+ private PermissionsUtils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 5b8d7e5..87558df 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -16,8 +16,6 @@
package com.android.server.companion;
-import static android.companion.DeviceId.TYPE_MAC_ADDRESS;
-
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -35,7 +33,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
-import android.companion.DeviceId;
+import android.net.MacAddress;
import android.os.Environment;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
@@ -53,10 +51,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -101,8 +96,8 @@
* Since Android T the data is stored using the v1 schema.
* In the v1 schema, a list of the previously used IDs is storead along with the association
* records.
- * In the v1 schema, we no longer store MAC addresses, instead each assocition record may have a
- * number of DeviceIds.
+ * V1 schema adds a new optional `display_name` attribute, and makes the `mac_address` attribute
+ * optional.
*
* @see #CURRENT_PERSISTENCE_VERSION
* @see #readAssociationsV1(TypedXmlPullParser, int, Set)
@@ -116,21 +111,19 @@
* <association
* id="1"
* package="com.sample.companion.app"
- * managed_by_app="false"
+ * mac_address="AA:BB:CC:DD:EE:00"
+ * self_managed="false"
* notify_device_nearby="false"
- * time_approved="1634389553216">
- * <device-id type="mac_address" value="AA:BB:CC:DD:EE:00" />
- * </association>
+ * time_approved="1634389553216"/>
*
* <association
* id="3"
* profile="android.app.role.COMPANION_DEVICE_WATCH"
* package="com.sample.companion.another.app"
- * managed_by_app="false"
+ * display_name="Jhon's Chromebook"
+ * self_managed="true"
* notify_device_nearby="false"
- * time_approved="1634641160229">
- * <device-id type="mac_address" value="AA:BB:CC:DD:EE:FF" />
- * </association>
+ * time_approved="1634641160229"/>
* </associations>
*
* <previously-used-ids>
@@ -153,7 +146,6 @@
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
private static final String XML_TAG_ASSOCIATION = "association";
- private static final String XML_TAG_DEVICE_ID = "device-id";
private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
private static final String XML_TAG_PACKAGE = "package";
private static final String XML_TAG_ID = "id";
@@ -164,13 +156,14 @@
private static final String XML_ATTR_PACKAGE_NAME = "package_name";
// Used in <association> elements, nested within <associations> elements.
private static final String XML_ATTR_PACKAGE = "package";
- private static final String XML_ATTR_DEVICE = "device";
+ private static final String XML_ATTR_MAC_ADDRESS = "mac_address";
+ private static final String XML_ATTR_DISPLAY_NAME = "display_name";
private static final String XML_ATTR_PROFILE = "profile";
- private static final String XML_ATTR_MANAGED_BY_APP = "managed_by_app";
+ private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
- private static final String XML_ATTR_TYPE = "type";
- private static final String XML_ATTR_VALUE = "value";
+
+ private static final String LEGACY_XML_ATTR_DEVICE = "device";
private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
new ConcurrentHashMap<>();
@@ -353,9 +346,7 @@
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- // In v0, CDM did not have a notion of a DeviceId yet, instead each Association had a MAC
- // address.
- final String deviceAddress = readStringAttribute(parser, XML_ATTR_DEVICE);
+ final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
if (appPackage == null || deviceAddress == null) return;
@@ -363,10 +354,8 @@
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- // "Convert" MAC address into a DeviceId.
- final List<DeviceId> deviceIds = Arrays.asList(
- new DeviceId(TYPE_MAC_ADDRESS, deviceAddress));
- out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile,
+ out.add(new AssociationInfo(associationId, userId, appPackage,
+ MacAddress.fromString(deviceAddress), null, profile,
/* managedByCompanionApp */false, notify, timeApproved));
}
@@ -391,23 +380,18 @@
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final boolean managedByApp = readBooleanAttribute(parser, XML_ATTR_MANAGED_BY_APP);
+ final MacAddress macAddress = stringToMacAddress(
+ readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
+ final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
+ final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- final List<DeviceId> deviceIds = new ArrayList<>();
- while (true) {
- parser.nextTag();
- if (isEndOfTag(parser, XML_TAG_ASSOCIATION)) break;
- if (!isStartOfTag(parser, XML_TAG_DEVICE_ID)) continue;
-
- final String type = readStringAttribute(parser, XML_ATTR_TYPE);
- final String value = readStringAttribute(parser, XML_ATTR_VALUE);
- deviceIds.add(new DeviceId(type, value));
+ final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
+ appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved);
+ if (associationInfo != null) {
+ out.add(associationInfo);
}
-
- out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile,
- managedByApp, notify, timeApproved));
}
private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser,
@@ -447,32 +431,19 @@
throws IOException {
final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATION);
- writeIntAttribute(serializer, XML_ATTR_ID, a.getAssociationId());
+ writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
- writeBooleanAttribute(serializer, XML_ATTR_MANAGED_BY_APP, a.isManagedByCompanionApp());
+ writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
+ writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
+ writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
- final List<DeviceId> deviceIds = a.getDeviceIds();
- for (int i = 0, size = deviceIds.size(); i < size; i++) {
- writeDeviceId(serializer, deviceIds.get(i));
- }
-
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
- private static void writeDeviceId(@NonNull XmlSerializer parent, @NonNull DeviceId deviceId)
- throws IOException {
- final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
-
- writeStringAttribute(serializer, XML_ATTR_TYPE, deviceId.getType());
- writeStringAttribute(serializer, XML_ATTR_VALUE, deviceId.getValue());
-
- serializer.endTag(null, XML_TAG_DEVICE_ID);
- }
-
private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException {
final XmlSerializer serializer = parent.startTag(null, XML_TAG_PREVIOUSLY_USED_IDS);
@@ -509,4 +480,22 @@
throw new XmlPullParserException(
"Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
}
+
+ private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
+ return address != null ? MacAddress.fromString(address) : null;
+ }
+
+ private static AssociationInfo createAssociationInfoNoThrow(int associationId,
+ @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
+ @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
+ boolean notify, long timeApproved) {
+ AssociationInfo associationInfo = null;
+ try {
+ associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
+ displayName, profile, selfManaged, notify, timeApproved);
+ } catch (Exception e) {
+ if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e);
+ }
+ return associationInfo;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
new file mode 100644
index 0000000..76340fc
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
+
+import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
+import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.role.RoleManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.util.List;
+
+/** Utility methods for accessing {@link RoleManager} APIs. */
+final class RolesUtils {
+
+ static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
+ @NonNull String packageName, @NonNull String role) {
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+ final List<String> roleHolders = roleManager.getRoleHoldersAsUser(
+ role, UserHandle.of(userId));
+ return roleHolders.contains(packageName);
+ }
+
+ static void addRoleHolderForAssociation(
+ @NonNull Context context, @NonNull AssociationInfo associationInfo) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
+ }
+
+ final String deviceProfile = associationInfo.getDeviceProfile();
+ if (deviceProfile == null) return;
+
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+
+ final String packageName = associationInfo.getPackageName();
+ final int userId = associationInfo.getUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
+
+ roleManager.addRoleHolderAsUser(deviceProfile, packageName,
+ MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+ success -> {
+ if (!success) {
+ Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName
+ + " to the list of " + deviceProfile + " holders.");
+ }
+ });
+ }
+
+ static void removeRoleHolderForAssociation(
+ @NonNull Context context, @NonNull AssociationInfo associationInfo) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
+ }
+
+ final String deviceProfile = associationInfo.getDeviceProfile();
+ if (deviceProfile == null) return;
+
+ final RoleManager roleManager = context.getSystemService(RoleManager.class);
+
+ final String packageName = associationInfo.getPackageName();
+ final int userId = associationInfo.getUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
+
+ roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
+ MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+ success -> {
+ if (!success) {
+ Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName
+ + " from the list of " + deviceProfile + " holders.");
+ }
+ });
+ }
+
+ private RolesUtils() {};
+}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 74a21a7..beb4d5b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -60,17 +60,62 @@
private static final Plog PLOG = Plog.createSystemPlog(TAG);
+ /**
+ * Creates a BrightnessMappingStrategy for active (normal) mode.
+ * @param resources
+ * @param displayDeviceConfig
+ * @return the BrightnessMappingStrategy
+ */
@Nullable
public static BrightnessMappingStrategy create(Resources resources,
DisplayDeviceConfig displayDeviceConfig) {
+ return create(resources, displayDeviceConfig, /* isForIdleMode= */ false);
+ }
- // Display independent values
- float[] luxLevels = getLuxLevels(resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevels));
+ /**
+ * Creates a BrightnessMappingStrategy for idle screen brightness mode.
+ * @param resources
+ * @param displayDeviceConfig
+ * @return the BrightnessMappingStrategy
+ */
+ @Nullable
+ public static BrightnessMappingStrategy createForIdleMode(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig) {
+ return create(resources, displayDeviceConfig, /* isForIdleMode= */ true);
+ }
+
+ /**
+ * Creates a BrightnessMapping strategy for either active or idle screen brightness mode.
+ * We do not create a simple mapping strategy for idle mode.
+ *
+ * @param resources
+ * @param displayDeviceConfig
+ * @param isForIdleMode determines whether the configurations loaded are for idle screen
+ * brightness mode or active screen brightness mode.
+ * @return the BrightnessMappingStrategy
+ */
+ @Nullable
+ private static BrightnessMappingStrategy create(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig, boolean isForIdleMode) {
+
+ // Display independent, mode dependent values
+ float[] brightnessLevelsNits;
+ float[] luxLevels;
+ if (isForIdleMode) {
+ brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
+ luxLevels = getLuxLevels(resources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevelsIdle));
+ } else {
+ brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
+ luxLevels = getLuxLevels(resources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels));
+ }
+
+ // Display independent, mode independent values
int[] brightnessLevelsBacklight = resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
- float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
1, 1);
@@ -91,7 +136,7 @@
builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
autoBrightnessAdjustmentMaxGamma);
- } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) {
+ } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
} else {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5f79f72..7f78cac 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -54,6 +54,7 @@
import android.util.TimeUtils;
import android.view.Display;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.display.BrightnessSynchronizer;
@@ -386,8 +387,17 @@
private Sensor mLightSensor;
// The mapper between ambient lux, display backlight values, and display brightness.
+ // This mapper holds the current one that is being used. We will switch between the idle
+ // mapper and active mapper here.
@Nullable
- private BrightnessMappingStrategy mBrightnessMapper;
+ private BrightnessMappingStrategy mCurrentBrightnessMapper;
+
+ // Mapper used for active (normal) screen brightness mode
+ @Nullable
+ private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+ // Mapper used for idle screen brightness mode
+ @Nullable
+ private BrightnessMappingStrategy mIdleModeBrightnessMapper;
// The current brightness configuration.
@Nullable
@@ -408,7 +418,7 @@
// The temporary screen brightness. Typically set when a user is interacting with the
// brightness slider but hasn't settled on a choice yet. Set to
- // PowerManager.BRIGHNTESS_INVALID_FLOAT when there's no temporary brightness set.
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
private float mTemporaryScreenBrightness;
// The current screen brightness while in VR mode.
@@ -600,7 +610,7 @@
}
private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
- if (mBrightnessMapper == null) {
+ if (mCurrentBrightnessMapper == null) {
Log.w(TAG, "No brightness mapping available to recalculate splines");
return;
}
@@ -609,7 +619,8 @@
for (int i = 0; i < mNitsRange.length; i++) {
adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
}
- mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
+ mCurrentBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(),
+ adjustedNits);
mPendingRbcOnOrChanged = strengthChanged || justActivated;
@@ -867,9 +878,17 @@
return;
}
- mBrightnessMapper = BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig);
+ final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ R.bool.config_enableIdleScreenBrightnessMode);
+ mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mDisplayDeviceConfig);
+ if (isIdleScreenBrightnessEnabled) {
+ mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
+ mDisplayDeviceConfig);
+ }
+ mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
- if (mBrightnessMapper != null) {
+ if (mCurrentBrightnessMapper != null) {
final float dozeScaleFactor = resources.getFraction(
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
@@ -920,7 +939,7 @@
mAutomaticBrightnessController.stop();
}
mAutomaticBrightnessController = new AutomaticBrightnessController(this,
- handler.getLooper(), mSensorManager, mLightSensor, mBrightnessMapper,
+ handler.getLooper(), mSensorManager, mLightSensor, mCurrentBrightnessMapper,
lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
@@ -2143,8 +2162,8 @@
}
private float convertToNits(float brightness) {
- if (mBrightnessMapper != null) {
- return mBrightnessMapper.convertToNits(brightness);
+ if (mCurrentBrightnessMapper != null) {
+ return mCurrentBrightnessMapper.convertToNits(brightness);
} else {
return -1.0f;
}
@@ -2296,6 +2315,11 @@
pw.println(" mReportedToPolicy=" +
reportedToPolicyToString(mReportedScreenStateToPolicy));
+ if (mIdleModeBrightnessMapper != null) {
+ pw.println(" mIdleModeBrightnessMapper= ");
+ mIdleModeBrightnessMapper.dump(pw);
+ }
+
if (mScreenBrightnessRampAnimator != null) {
pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" +
mScreenBrightnessRampAnimator.isAnimating());
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 1fa6241..7e71589 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -265,13 +265,20 @@
// to request Short Audio Descriptor. Since ARC and SAM are independent,
// we can turn on ARC anyways when audio system device just boots up.
initArcOnFromAvr();
- int systemAudioControlOnPowerOnProp =
- SystemProperties.getInt(
- PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
- ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
- boolean lastSystemAudioControlStatus =
- SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
- systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
+
+ // This prevents turning on of System Audio Mode during a quiescent boot. If the quiescent
+ // boot is exited just after this check, this code will be executed only at the next
+ // wake-up.
+ if (!mService.isScreenOff()) {
+ int systemAudioControlOnPowerOnProp =
+ SystemProperties.getInt(
+ PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
+ ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
+ boolean lastSystemAudioControlStatus =
+ SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
+ systemAudioControlOnPowerOn(
+ systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
+ }
mService.getHdmiCecNetwork().clearDeviceList();
launchDeviceDiscovery();
startQueuedActions();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c08d343..6dd9aa0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -37,6 +37,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiHotplugEvent;
@@ -81,6 +82,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -413,6 +415,9 @@
@Nullable
private Looper mIoLooper;
+ @Nullable
+ private DisplayManager mDisplayManager;
+
@HdmiControlManager.HdmiCecVersion
private int mCecVersion;
@@ -679,6 +684,11 @@
}, mServiceThreadExecutor);
}
+ /** Returns true if the device screen is off */
+ boolean isScreenOff() {
+ return mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_OFF;
+ }
+
private void bootCompleted() {
// on boot, if device is interactive, set HDMI CEC state as powered on as well
if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
@@ -734,6 +744,7 @@
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mDisplayManager = getContext().getSystemService(DisplayManager.class);
mTvInputManager = (TvInputManager) getContext().getSystemService(
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index b8bb399..f70ad05 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -224,7 +224,7 @@
public Context getSettingsContext(int displayId) {
if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
final Context systemUiContext = ActivityThread.currentActivityThread()
- .createSystemUiContext(displayId);
+ .getSystemUiContext(displayId);
final Context windowContext = systemUiContext.createWindowContext(
WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
mSettingsContext = new ContextThemeWrapper(
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 7db234a..5fe7710 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -71,7 +71,8 @@
private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
-
+ public static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD =
+ "ENABLE_PSDS_PERIODIC_DOWNLOAD";
// Limit on NI emergency mode time extension after emergency sessions ends
private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
@@ -194,6 +195,13 @@
}
/**
+ * Returns true if PSDS periodic download is enabled, false otherwise.
+ */
+ boolean isPsdsPeriodicDownloadEnabled() {
+ return getBooleanConfig(CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD, false);
+ }
+
+ /**
* Updates the GNSS HAL satellite denylist.
*/
void setSatelliteBlocklist(int[] constellations, int[] svids) {
@@ -374,6 +382,14 @@
}
}
+ private boolean getBooleanConfig(String configParameter, boolean defaultValue) {
+ String valueString = mProperties.getProperty(configParameter);
+ if (TextUtils.isEmpty(valueString)) {
+ return defaultValue;
+ }
+ return Boolean.parseBoolean(valueString);
+ }
+
private static boolean isConfigEsExtensionSecSupported(
HalInterfaceVersion gnssConfiguartionIfaceVersion) {
// ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6c1df7f..f114184 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,8 @@
package com.android.server.location.gnss;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
@@ -55,6 +57,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.location.GnssCapabilities;
import android.location.GnssStatus;
@@ -142,6 +145,9 @@
private static final int AGPS_SUPL_MODE_MSA = 0x02;
private static final int AGPS_SUPL_MODE_MSB = 0x01;
+ // PSDS stands for Predicted Satellite Data Service
+ private static final int DOWNLOAD_PSDS_DATA = 6;
+
// TCP/IP constants.
// Valid TCP/UDP port range is (0, 65535].
private static final int TCP_MIN_PORT = 0;
@@ -651,6 +657,14 @@
mPsdsBackOff.reset();
}
});
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(FEATURE_WATCH)
+ && mGnssConfiguration.isPsdsPeriodicDownloadEnabled()) {
+ if (DEBUG) Log.d(TAG, "scheduling next Psds download");
+ mHandler.removeMessages(DOWNLOAD_PSDS_DATA);
+ mHandler.sendEmptyMessageDelayed(DOWNLOAD_PSDS_DATA,
+ GnssPsdsDownloader.PSDS_INTERVAL);
+ }
} else {
// Try download PSDS data again later according to backoff time.
// Since this is delayed and not urgent, we do not hold a wake lock here.
diff --git a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
index 443a6c0a..dce9a12 100644
--- a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
+++ b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
@@ -38,6 +38,10 @@
*/
class GnssPsdsDownloader {
+ // how often to request PSDS download, in milliseconds
+ // current setting 24 hours
+ static final long PSDS_INTERVAL = 24 * 60 * 60 * 1000;
+
private static final String TAG = "GnssPsdsDownloader";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long MAXIMUM_CONTENT_LENGTH_BYTES = 1000000; // 1MB.
diff --git a/services/core/java/com/android/server/location/gnss/gps_debug.conf b/services/core/java/com/android/server/location/gnss/gps_debug.conf
index 34ce96f..90daf8c 100644
--- a/services/core/java/com/android/server/location/gnss/gps_debug.conf
+++ b/services/core/java/com/android/server/location/gnss/gps_debug.conf
@@ -50,3 +50,12 @@
# Set bit 0x2 if NI GPS functionalities are to be locked
# default - non is locked for backward compatibility
#GPS_LOCK = 0
+
+################################
+##### PSDS download settings #####
+################################
+# For wear devices only.
+# Enable periodic PSDS download once a day.
+# true: Enable periodic PSDS download
+# false: Disable periodic PSDS download
+#ENABLE_PSDS_PERIODIC_DOWNLOAD=false
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index df1eb6d..d17dbde 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -322,8 +322,11 @@
gateway = InetAddresses.parseNumericAddress(in.readUTF());
}
// If the destination is a default IPv4 route, use the gateway
- // address unless already set.
- if (dest.getAddress() instanceof Inet4Address
+ // address unless already set. If there is no destination, assume
+ // it is default route and use the gateway address in all cases.
+ if (dest == null) {
+ gatewayAddress = gateway;
+ } else if (dest.getAddress() instanceof Inet4Address
&& dest.getPrefixLength() == 0 && gatewayAddress == null) {
gatewayAddress = gateway;
} else {
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index f4b72a1..c876d41 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -150,6 +150,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BinderUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -2097,14 +2098,18 @@
@Override
public void notifyAlertReached() throws RemoteException {
- mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+ // This binder object can only have been obtained by a process that holds
+ // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required.
+ BinderUtils.withCleanCallingIdentity(() ->
+ mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */));
}
@Override
public void notifyWarningOrLimitReached() {
Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
- LocalServices.getService(NetworkPolicyManagerInternal.class)
- .onStatsProviderWarningOrLimitReached(mTag);
+ BinderUtils.withCleanCallingIdentity(() ->
+ LocalServices.getService(NetworkPolicyManagerInternal.class)
+ .onStatsProviderWarningOrLimitReached(mTag));
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e117cc6..99fdb2d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -9639,7 +9639,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- List<String> associations = mCompanionManager.getAssociations(
+ List<?> associations = mCompanionManager.getAssociations(
info.component.getPackageName(), info.userid);
if (!ArrayUtils.isEmpty(associations)) {
return true;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 131e587..f61b562 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -521,7 +521,6 @@
private boolean mPendingKeyguardOccluded;
private boolean mKeyguardOccludedChanged;
- private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
Intent mHomeIntent;
Intent mCarDockIntent;
Intent mDeskDockIntent;
@@ -1819,9 +1818,6 @@
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
mLogger = new MetricsLogger();
- mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
- .createSleepTokenAcquirer("ScreenOff");
-
Resources res = mContext.getResources();
mWakeOnDpadKeyPress =
res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4488,7 +4484,6 @@
if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
if (displayId == DEFAULT_DISPLAY) {
- updateScreenOffSleepToken(true);
mRequestedOrSleepingDefaultDisplay = false;
mDefaultDisplayPolicy.screenTurnedOff();
synchronized (mLock) {
@@ -4521,7 +4516,6 @@
if (displayId == DEFAULT_DISPLAY) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
- updateScreenOffSleepToken(false);
mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
mBootAnimationDismissable = false;
@@ -5042,15 +5036,6 @@
}
}
- // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
- private void updateScreenOffSleepToken(boolean acquire) {
- if (acquire) {
- mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
- } else {
- mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
- }
- }
-
/** {@inheritDoc} */
@Override
public void enableScreenAfterBoot() {
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 477ebf6..d24a3df 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -65,6 +65,7 @@
KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
+ KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
})
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.SOURCE)
@@ -139,6 +140,14 @@
"location_time_zone_detection_setting_enabled_default";
/**
+ * The key to control support for time zone detection falling back to telephony detection under
+ * certain circumstances.
+ */
+ public static final @DeviceConfigKey String
+ KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED =
+ "time_zone_detector_telephony_fallback_supported";
+
+ /**
* The key to override the time detector origin priorities configuration. A comma-separated list
* of strings that will be passed to {@link TimeDetectorStrategy#stringToOrigin(String)}.
* All values must be recognized or the override value will be ignored.
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 1a5945e..65f077e 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -39,6 +39,7 @@
private final boolean mTelephonyDetectionSupported;
private final boolean mGeoDetectionSupported;
+ private final boolean mTelephonyFallbackSupported;
private final boolean mAutoDetectionEnabledSetting;
private final @UserIdInt int mUserId;
private final boolean mUserConfigAllowed;
@@ -48,6 +49,7 @@
private ConfigurationInternal(Builder builder) {
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
mGeoDetectionSupported = builder.mGeoDetectionSupported;
+ mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
mUserId = builder.mUserId;
@@ -71,6 +73,14 @@
return mGeoDetectionSupported;
}
+ /**
+ * Returns true if the device supports time zone detection falling back to telephony detection
+ * under certain circumstances.
+ */
+ public boolean isTelephonyFallbackSupported() {
+ return mTelephonyFallbackSupported;
+ }
+
/** Returns the value of the auto time zone detection enabled setting. */
public boolean getAutoDetectionEnabledSetting() {
return mAutoDetectionEnabledSetting;
@@ -216,6 +226,7 @@
&& mUserConfigAllowed == that.mUserConfigAllowed
&& mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
&& mGeoDetectionSupported == that.mGeoDetectionSupported
+ && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
&& mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
&& mLocationEnabledSetting == that.mLocationEnabledSetting
&& mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -224,8 +235,8 @@
@Override
public int hashCode() {
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
- mGeoDetectionSupported, mAutoDetectionEnabledSetting, mLocationEnabledSetting,
- mGeoDetectionEnabledSetting);
+ mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
+ mLocationEnabledSetting, mGeoDetectionEnabledSetting);
}
@Override
@@ -235,6 +246,7 @@
+ ", mUserConfigAllowed=" + mUserConfigAllowed
+ ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
+ ", mGeoDetectionSupported=" + mGeoDetectionSupported
+ + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+ ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+ ", mLocationEnabledSetting=" + mLocationEnabledSetting
+ ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -251,6 +263,7 @@
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
+ private boolean mTelephonyFallbackSupported;
private boolean mAutoDetectionEnabledSetting;
private boolean mLocationEnabledSetting;
private boolean mGeoDetectionEnabledSetting;
@@ -269,6 +282,7 @@
this.mUserId = toCopy.mUserId;
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
+ this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
@@ -300,6 +314,15 @@
}
/**
+ * Sets whether time zone detection supports falling back to telephony detection under
+ * certain circumstances.
+ */
+ public Builder setTelephonyFallbackSupported(boolean supported) {
+ mTelephonyFallbackSupported = supported;
+ return this;
+ }
+
+ /**
* Sets the value of the automatic time zone detection enabled setting for this device.
*/
public Builder setAutoDetectionEnabledSetting(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java
new file mode 100644
index 0000000..62092ec
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.NonNull;
+
+/**
+ * The interface for the class that is responsible for detecting device activities relevant to
+ * time zone detection. This interface exists to decouple parts of the time zone detector from each
+ * other and to enable easier testing.
+ *
+ * @hide
+ */
+interface DeviceActivityMonitor extends Dumpable {
+
+ /** Adds a listener. */
+ void addListener(@NonNull Listener listener);
+
+ /**
+ * A listener for device activities. See {@link DeviceActivityMonitor#addListener(Listener)}.
+ */
+ interface Listener {
+ /** A flight has completed. */
+ void onFlightComplete();
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
new file mode 100644
index 0000000..8c9bd3b
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link DeviceActivityMonitor}.
+ */
+class DeviceActivityMonitorImpl implements DeviceActivityMonitor {
+
+ private static final String LOG_TAG = TimeZoneDetectorService.TAG;
+ private static final boolean DBG = TimeZoneDetectorService.DBG;
+
+ static DeviceActivityMonitor create(@NonNull Context context, @NonNull Handler handler) {
+ return new DeviceActivityMonitorImpl(context, handler);
+ }
+
+ @GuardedBy("this")
+ @NonNull
+ private final List<Listener> mListeners = new ArrayList<>();
+
+ private DeviceActivityMonitorImpl(@NonNull Context context, @NonNull Handler handler) {
+ // The way this "detects" a flight concluding is by the user explicitly turning off airplane
+ // mode. Smarter heuristics would be nice.
+ ContentResolver contentResolver = context.getContentResolver();
+ ContentObserver airplaneModeObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean unused) {
+ try {
+ int state = Settings.Global.getInt(
+ contentResolver, Settings.Global.AIRPLANE_MODE_ON);
+ if (state == 0) {
+ notifyFlightComplete();
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ Slog.e(LOG_TAG, "Unable to read airplane mode state", e);
+ }
+ }
+ };
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
+ true /* notifyForDescendants */,
+ airplaneModeObserver);
+ }
+
+ @Override
+ public synchronized void addListener(Listener listener) {
+ Objects.requireNonNull(listener);
+ mListeners.add(listener);
+ }
+
+ private synchronized void notifyFlightComplete() {
+ if (DBG) {
+ Slog.d(LOG_TAG, "notifyFlightComplete");
+ }
+
+ for (Listener listener : mListeners) {
+ listener.onFlightComplete();
+ }
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ // No-op right now: no state to dump.
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index ec620b5..0ec8826c 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -16,11 +16,13 @@
package com.android.server.timezonedetector;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.Context;
import android.os.Handler;
+import android.os.SystemClock;
import android.os.SystemProperties;
import java.util.Objects;
@@ -79,4 +81,9 @@
AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
alarmManager.setTimeZone(zoneId);
}
+
+ @Override
+ public @ElapsedRealtimeLong long elapsedRealtimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index f8ba0d2..0c20586 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -111,6 +111,11 @@
return mConfigurationInternal.isGeoDetectionSupported();
}
+ /** Returns true if the device supports telephony time zone detection fallback. */
+ public boolean isTelephonyTimeZoneFallbackSupported() {
+ return mConfigurationInternal.isTelephonyFallbackSupported();
+ }
+
/** Returns true if user's location can be used generally. */
public boolean getUserLocationEnabledSetting() {
return mConfigurationInternal.getLocationEnabledSetting();
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index be4b6bd..b9885d2 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -64,6 +64,7 @@
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+ ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
}));
/**
@@ -294,6 +295,7 @@
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
+ .setTelephonyFallbackSupported(isTelephonyFallbackSupported())
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setUserConfigAllowed(isUserConfigAllowed(userId))
.setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -549,6 +551,13 @@
mRecordProviderStateChanges = false;
}
+ private boolean isTelephonyFallbackSupported() {
+ return mServerFlags.getBoolean(
+ ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+ getConfigBoolean(
+ com.android.internal.R.bool.config_supportTelephonyTimeZoneFallback));
+ }
+
private boolean getConfigBoolean(int providerEnabledConfigId) {
Resources resources = mContext.getResources();
return resources.getBoolean(providerEnabledConfigId);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index e0c39ad..14784cf 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,6 +83,16 @@
ServiceConfigAccessorImpl.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+ DeviceActivityMonitor deviceActivityMonitor =
+ DeviceActivityMonitorImpl.create(context, handler);
+
+ // Wire up the telephony fallback behavior to activity detection.
+ deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() {
+ @Override
+ public void onFlightComplete() {
+ timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ }
+ });
// Create and publish the local service for use by internal callers.
TimeZoneDetectorInternal internal =
@@ -93,6 +103,10 @@
// permissioned) processes.
TimeZoneDetectorService service = TimeZoneDetectorService.create(
context, handler, serviceConfigAccessor, timeZoneDetectorStrategy);
+
+ // Dump the device activity monitor when the service is dumped.
+ service.addDumpable(deviceActivityMonitor);
+
publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
}
}
@@ -332,6 +346,15 @@
}
/**
+ * Sends a signal to enable telephony fallback. Provided for command-line access for use
+ * during tests. This is not exposed as a binder API.
+ */
+ void enableTelephonyFallback() {
+ enforceManageTimeZoneDetectorPermission();
+ mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ }
+
+ /**
* Registers the supplied {@link Dumpable} for dumping. When the service is dumped
* {@link Dumpable#dump(IndentingPrintWriter, String[])} will be called on the {@code dumpable}.
*/
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index a4a46a3..2b912ad 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,6 +15,7 @@
*/
package com.android.server.timezonedetector;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -30,6 +31,7 @@
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
+import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED;
import android.app.time.LocationTimeZoneManager;
import android.app.time.TimeZoneConfiguration;
@@ -76,6 +78,8 @@
return runSuggestManualTimeZone();
case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
return runSuggestTelephonyTimeZone();
+ case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
+ return runEnableTelephonyFallback();
default: {
return handleDefaultCommands(cmd);
}
@@ -169,6 +173,11 @@
}
}
+ private int runEnableTelephonyFallback() {
+ mInterface.enableTelephonyFallback();
+ return 1;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -190,6 +199,13 @@
+ "\n");
pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED);
pw.printf(" Sets the geolocation time zone detection enabled setting.\n");
+ pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
+ pw.printf(" Signals that telephony time zone detection fall back can be used if"
+ + " geolocation detection is supported and enabled. This is a temporary state until"
+ + " geolocation detection becomes \"certain\". To have an effect this requires that"
+ + " the telephony fallback feature is supported on the device, see below for"
+ + " for device_config flags.\n");
+ pw.println();
pw.printf(" %s <geolocation suggestion opts>\n",
SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
pw.printf(" %s <manual suggestion opts>\n",
@@ -216,6 +232,9 @@
pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE);
pw.printf(" Used to override the device's 'geolocation time zone detection enabled'"
+ " setting [*].\n");
+ pw.printf(" %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
+ pw.printf(" Used to enable / disable support for telephony detection fallback. Also see"
+ + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
pw.println();
pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
+ " detection = on.\n");
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index ede52ba..6b04adf 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -69,6 +69,20 @@
* users enter areas without the necessary signals. Ultimately, with no perfect algorithm available,
* the user is left to choose which algorithm works best for their circumstances.
*
+ * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during
+ * international travel, it makes sense to prioritize speed of detection via telephony (when
+ * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can
+ * sometimes be slow to get a location fix and can require network connectivity (which cannot be
+ * assumed when users are travelling) for server-assisted location detection or time zone lookup.
+ * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms,
+ * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device
+ * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in
+ * response to signals outside of the scope of this class. Telephony fallback allows the use of
+ * telephony suggestions to help with faster detection but only until geolocation detection
+ * provides a concrete, "certain" suggestion. After geolocation has made the first certain
+ * suggestion, telephony fallback is disabled until the next call to {@link
+ * #enableTelephonyTimeZoneFallback()}.
+ *
* <p>Threading:
*
* <p>Implementations of this class must be thread-safe as calls calls like {@link
@@ -100,6 +114,13 @@
*/
void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
+ /**
+ * Tells the strategy that it can fall back to telephony detection while geolocation detection
+ * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
+ * disable it again. See {@link TimeZoneDetectorStrategy} for details.
+ */
+ void enableTelephonyTimeZoneFallback();
+
/** Generates a state snapshot for metrics. */
@NonNull
MetricsTimeZoneDetectorState generateMetricsState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 3b6c1ea..92dddac 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -22,6 +22,7 @@
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -31,6 +32,7 @@
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.content.Context;
import android.os.Handler;
+import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Slog;
@@ -38,6 +40,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.time.Duration;
import java.util.List;
import java.util.Objects;
@@ -83,6 +86,13 @@
* Sets the device's time zone.
*/
void setDeviceTimeZone(@NonNull String zoneId);
+
+ /**
+ * Returns the time according to the elapsed realtime clock, the same as {@link
+ * android.os.SystemClock#elapsedRealtime()}.
+ */
+ @ElapsedRealtimeLong
+ long elapsedRealtimeMillis();
}
private static final String LOG_TAG = TimeZoneDetectorService.TAG;
@@ -191,6 +201,21 @@
private ConfigurationInternal mCurrentConfigurationInternal;
/**
+ * Whether telephony time zone detection fallback is currently enabled (when device config also
+ * allows).
+ *
+ * <p>This field is only actually used when telephony time zone fallback is supported, but the
+ * value is maintained even when it isn't supported as it can be turned on at any time via
+ * server flags. The reference time is the elapsed realtime when the mode last changed to help
+ * ordering between fallback mode switches and suggestions.
+ *
+ * <p>See {@link TimeZoneDetectorStrategy} for more information.
+ */
+ @GuardedBy("this")
+ @NonNull
+ private TimestampedValue<Boolean> mTelephonyTimeZoneFallbackEnabled;
+
+ /**
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
public static TimeZoneDetectorStrategyImpl create(
@@ -205,6 +230,10 @@
public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
mEnvironment = Objects.requireNonNull(environment);
+ // Start with telephony fallback enabled.
+ mTelephonyTimeZoneFallbackEnabled =
+ new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
+
synchronized (this) {
mEnvironment.setConfigurationInternalChangeListener(
this::handleConfigurationInternalChanged);
@@ -233,6 +262,10 @@
// are made in a sensible order and the most recent is always the best one to use.
mLatestGeoLocationSuggestion.set(suggestion);
+ // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
+ // will usually disable telephony fallback mode if it is currently enabled.
+ disableTelephonyFallbackIfNeeded();
+
// Now perform auto time zone detection. The new suggestion may be used to modify the
// time zone setting.
String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
@@ -304,6 +337,43 @@
}
@Override
+ public synchronized void enableTelephonyTimeZoneFallback() {
+ // Only do any work if fallback is currently not enabled.
+ if (!mTelephonyTimeZoneFallbackEnabled.getValue()) {
+ ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
+ if (DBG) {
+ Slog.d(LOG_TAG, "enableTelephonyTimeZoneFallbackMode"
+ + ": currentUserConfig=" + currentUserConfig);
+ }
+
+ final boolean fallbackEnabled = true;
+ mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
+ mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
+
+ // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
+ // If there is currently a certain geolocation suggestion, then the telephony fallback
+ // value needs to be considered after changing it.
+ // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
+ // above, and the fact that geolocation suggestions should never have a time in the
+ // future, the following call will be a no-op, and telephony fallback will remain
+ // enabled. This comment / call is left as a reminder that it is possible for there to
+ // be a current, "certain" geolocation suggestion when this signal arrives and it is
+ // intentional that fallback stays enabled in this case. The choice to do this
+ // is mostly for symmetry WRT the case where fallback is enabled and an old "certain"
+ // geolocation is received; that would also leave telephony fallback enabled.
+ // This choice means that telephony fallback will remain enabled until a new "certain"
+ // geolocation suggestion is received. If, instead, the next geolocation is "uncertain",
+ // then telephony fallback will occur.
+ disableTelephonyFallbackIfNeeded();
+
+ if (currentUserConfig.isTelephonyFallbackSupported()) {
+ String reason = "enableTelephonyTimeZoneFallbackMode";
+ doAutoTimeZoneDetection(currentUserConfig, reason);
+ }
+ }
+ }
+
+ @Override
@NonNull
public synchronized MetricsTimeZoneDetectorState generateMetricsState() {
// Just capture one telephony suggestion: the one that would be used right now if telephony
@@ -361,7 +431,32 @@
// Use the correct algorithm based on the user's current configuration. If it changes, then
// detection will be re-run.
if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
- doGeolocationTimeZoneDetection(detectionReason);
+ boolean isGeoDetectionCertain = doGeolocationTimeZoneDetection(detectionReason);
+
+ // When geolocation detection is uncertain of the time zone, telephony detection
+ // can be used if telephony fallback is enabled and supported.
+ if (!isGeoDetectionCertain
+ && mTelephonyTimeZoneFallbackEnabled.getValue()
+ && currentUserConfig.isTelephonyFallbackSupported()) {
+
+ // This "only look at telephony if geolocation is uncertain" approach is
+ // deliberate to try to keep the logic simple and keep telephony and geolocation
+ // detection decoupled: when geolocation detection is in use, it is fully
+ // trusted and the most recent "certain" geolocation suggestion available will
+ // be used, even if the information it is based on is quite old.
+ // There could be newer telephony suggestions available, but telephony
+ // suggestions tend not to be withdrawn when they should be, and are based on
+ // combining information like MCC and NITZ signals, which could have been
+ // received at different times; thus it is hard to say what time the suggestion
+ // is actually "for" and reason clearly about ordering between telephony and
+ // geolocation suggestions.
+ //
+ // This approach is reliant on the location_time_zone_manager (and the location
+ // time zone providers it manages) correctly sending "uncertain" suggestions
+ // when the current location is unknown so that telephony fallback will actually be
+ // used.
+ doTelephonyTimeZoneDetection(detectionReason + ", telephony fallback mode");
+ }
} else {
doTelephonyTimeZoneDetection(detectionReason);
}
@@ -371,21 +466,29 @@
* Detects the time zone using the latest available geolocation time zone suggestion, if one is
* available. The outcome can be that this strategy becomes / remains un-opinionated and nothing
* is set.
+ *
+ * @return true if geolocation time zone detection was certain of the time zone, false if it is
+ * uncertain
*/
@GuardedBy("this")
- private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
+ private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
mLatestGeoLocationSuggestion.get();
if (latestGeolocationSuggestion == null) {
- return;
+ return false;
}
List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
- if (zoneIds == null || zoneIds.isEmpty()) {
- // This means the client has become uncertain about the time zone or it is certain there
- // is no known zone. In either case we must leave the existing time zone setting as it
- // is.
- return;
+ if (zoneIds == null) {
+ // This means the originator of the suggestion is uncertain about the time zone. The
+ // existing time zone setting must be left as it is but detection can go on looking for
+ // a different answer elsewhere.
+ return false;
+ } else if (zoneIds.isEmpty()) {
+ // This means the originator is certain there is no time zone. The existing time zone
+ // setting must be left as it is and detection must not go looking for a different
+ // answer elsewhere.
+ return true;
}
// GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
@@ -404,6 +507,34 @@
zoneId = zoneIds.get(0);
}
setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+ return true;
+ }
+
+ /**
+ * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
+ * suggestion is a "certain" suggestion that comes after the time when telephony fallback was
+ * enabled.
+ */
+ @GuardedBy("this")
+ private void disableTelephonyFallbackIfNeeded() {
+ GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
+ boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
+ if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
+ // This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
+ // true -> false. See mTelephonyTimeZoneFallbackEnabled javadocs for details.
+
+ // Telephony fallback will be disabled after a "certain" suggestion is processed
+ // if and only if the location information it is based on is from after telephony
+ // fallback was enabled.
+ boolean latestSuggestionIsNewerThanFallbackEnabled =
+ suggestion.getEffectiveFromElapsedMillis()
+ > mTelephonyTimeZoneFallbackEnabled.getReferenceTimeMillis();
+ if (latestSuggestionIsNewerThanFallbackEnabled) {
+ final boolean fallbackEnabled = false;
+ mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>(
+ mEnvironment.elapsedRealtimeMillis(), fallbackEnabled);
+ }
+ }
}
/**
@@ -556,6 +687,12 @@
+ mEnvironment.isDeviceTimeZoneInitialized());
ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
+ ipw.println("Misc state:");
+ ipw.increaseIndent(); // level 2
+ ipw.println("mTelephonyTimeZoneFallbackEnabled="
+ + formatDebugString(mTelephonyTimeZoneFallbackEnabled));
+ ipw.decreaseIndent(); // level 2
+
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
mTimeZoneChangesLog.dump(ipw);
@@ -603,6 +740,11 @@
return mLatestGeoLocationSuggestion.get();
}
+ @VisibleForTesting
+ public synchronized boolean isTelephonyFallbackEnabledForTests() {
+ return mTelephonyTimeZoneFallbackEnabled.getValue();
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@@ -652,4 +794,8 @@
+ '}';
}
}
+
+ private static String formatDebugString(TimestampedValue<?> value) {
+ return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
deleted file mode 100644
index b9da2eb..0000000
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2020 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.timezonedetector.location;
-
-import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
-import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
-import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
-
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-
-import android.annotation.DurationMillisLong;
-import android.annotation.ElapsedRealtimeLong;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.service.timezone.TimeZoneProviderEvent;
-import android.service.timezone.TimeZoneProviderSuggestion;
-import android.util.IndentingPrintWriter;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationInternal;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
-import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
-
-import java.time.Duration;
-import java.util.Objects;
-
-/**
- * A real implementation of {@link LocationTimeZoneProviderController} that supports a primary and a
- * secondary {@link LocationTimeZoneProvider}.
- *
- * <p>The primary is used until it fails or becomes uncertain. The secondary will then be started.
- * The controller will immediately make suggestions based on "certain" {@link
- * TimeZoneProviderEvent}s, i.e. events that demonstrate the provider is certain what the time zone
- * is. The controller will not make immediate suggestions based on "uncertain" events, giving
- * providers time to change their mind. This also gives the secondary provider time to initialize
- * when the primary becomes uncertain.
- */
-class ControllerImpl extends LocationTimeZoneProviderController {
-
- @NonNull private final LocationTimeZoneProvider mPrimaryProvider;
-
- @NonNull private final LocationTimeZoneProvider mSecondaryProvider;
-
- @GuardedBy("mSharedLock")
- // Non-null after initialize()
- private ConfigurationInternal mCurrentUserConfiguration;
-
- @GuardedBy("mSharedLock")
- // Non-null after initialize()
- private Environment mEnvironment;
-
- @GuardedBy("mSharedLock")
- // Non-null after initialize()
- private Callback mCallback;
-
- /** Indicates both providers have completed initialization. */
- @GuardedBy("mSharedLock")
- private boolean mProvidersInitialized;
-
- /**
- * Used for scheduling uncertainty timeouts, i.e after a provider has reported uncertainty.
- * This timeout is not provider-specific: it is started when the controller becomes uncertain
- * due to events it has received from one or other provider.
- */
- @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
-
- /** Contains the last suggestion actually made, if there is one. */
- @GuardedBy("mSharedLock")
- @Nullable
- private GeolocationTimeZoneSuggestion mLastSuggestion;
-
- ControllerImpl(@NonNull ThreadingDomain threadingDomain,
- @NonNull LocationTimeZoneProvider primaryProvider,
- @NonNull LocationTimeZoneProvider secondaryProvider) {
- super(threadingDomain);
- mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
- mPrimaryProvider = Objects.requireNonNull(primaryProvider);
- mSecondaryProvider = Objects.requireNonNull(secondaryProvider);
- }
-
- @Override
- void initialize(@NonNull Environment environment, @NonNull Callback callback) {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- debugLog("initialize()");
- mEnvironment = Objects.requireNonNull(environment);
- mCallback = Objects.requireNonNull(callback);
- mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
-
- LocationTimeZoneProvider.ProviderListener providerListener =
- ControllerImpl.this::onProviderStateChange;
- mPrimaryProvider.initialize(providerListener);
- mSecondaryProvider.initialize(providerListener);
- mProvidersInitialized = true;
-
- alterProvidersStartedStateIfRequired(
- null /* oldConfiguration */, mCurrentUserConfiguration);
- }
- }
-
- @Override
- void onConfigurationInternalChanged() {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- debugLog("onConfigChanged()");
-
- ConfigurationInternal oldConfig = mCurrentUserConfiguration;
- ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal();
- mCurrentUserConfiguration = newConfig;
-
- if (!newConfig.equals(oldConfig)) {
- if (newConfig.getUserId() != oldConfig.getUserId()) {
- // If the user changed, stop the providers if needed. They may be re-started
- // for the new user immediately afterwards if their settings allow.
- debugLog("User changed. old=" + oldConfig.getUserId()
- + ", new=" + newConfig.getUserId() + ": Stopping providers");
- stopProviders();
-
- alterProvidersStartedStateIfRequired(null /* oldConfiguration */, newConfig);
- } else {
- alterProvidersStartedStateIfRequired(oldConfig, newConfig);
- }
- }
- }
- }
-
- @Override
- boolean isUncertaintyTimeoutSet() {
- return mUncertaintyTimeoutQueue.hasQueued();
- }
-
- @Override
- @DurationMillisLong
- long getUncertaintyTimeoutDelayMillis() {
- return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
- }
-
- @Override
- void destroy() {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- stopProviders();
- mPrimaryProvider.destroy();
- mSecondaryProvider.destroy();
- }
- }
-
- @GuardedBy("mSharedLock")
- private void stopProviders() {
- stopProviderIfStarted(mPrimaryProvider);
- stopProviderIfStarted(mSecondaryProvider);
-
- // By definition, if both providers are stopped, the controller is uncertain.
- cancelUncertaintyTimeout();
-
- // If a previous "certain" suggestion has been made, then a new "uncertain"
- // suggestion must now be made to indicate the controller {does not / no longer has}
- // an opinion and will not be sending further updates (until at least the providers are
- // re-started).
- if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(), "Providers are stopping");
- makeSuggestion(suggestion);
- }
- }
-
- @GuardedBy("mSharedLock")
- private void stopProviderIfStarted(@NonNull LocationTimeZoneProvider provider) {
- if (provider.getCurrentState().isStarted()) {
- stopProvider(provider);
- }
- }
-
- @GuardedBy("mSharedLock")
- private void stopProvider(@NonNull LocationTimeZoneProvider provider) {
- ProviderState providerState = provider.getCurrentState();
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_STOPPED: {
- debugLog("No need to stop " + provider + ": already stopped");
- break;
- }
- case PROVIDER_STATE_STARTED_INITIALIZING:
- case PROVIDER_STATE_STARTED_CERTAIN:
- case PROVIDER_STATE_STARTED_UNCERTAIN: {
- debugLog("Stopping " + provider);
- provider.stopUpdates();
- break;
- }
- case PROVIDER_STATE_PERM_FAILED:
- case PROVIDER_STATE_DESTROYED: {
- debugLog("Unable to stop " + provider + ": it is terminated.");
- break;
- }
- default: {
- warnLog("Unknown provider state: " + provider);
- break;
- }
- }
- }
-
- /**
- * Sets the providers into the correct started/stopped state for the {@code newConfiguration}
- * and, if there is a provider state change, makes any suggestions required to inform the
- * downstream time zone detection code.
- *
- * <p>This is a utility method that exists to avoid duplicated logic for the various cases when
- * provider started / stopped state may need to be set or changed, e.g. during initialization
- * or when a new configuration has been received.
- */
- @GuardedBy("mSharedLock")
- private void alterProvidersStartedStateIfRequired(
- @Nullable ConfigurationInternal oldConfiguration,
- @NonNull ConfigurationInternal newConfiguration) {
-
- // Provider started / stopped states only need to be changed if geoDetectionEnabled has
- // changed.
- boolean oldGeoDetectionEnabled = oldConfiguration != null
- && oldConfiguration.getGeoDetectionEnabledBehavior();
- boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
- if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
- return;
- }
-
- // The check above ensures that the logic below only executes if providers are going from
- // {started *} -> {stopped}, or {stopped} -> {started initializing}. If this changes in
- // future and there could be {started *} -> {started *} cases, or cases where the provider
- // can't be assumed to go straight to the {started initializing} state, then the logic below
- // would need to cover extra conditions, for example:
- // 1) If the primary is in {started uncertain}, the secondary should be started.
- // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty
- // timeout started when the primary entered {started uncertain} should be cancelled.
-
- if (newGeoDetectionEnabled) {
- // Try to start the primary provider.
- tryStartProvider(mPrimaryProvider, newConfiguration);
-
- // The secondary should only ever be started if the primary now isn't started (i.e. it
- // couldn't become {started initializing} because it is {perm failed}).
- ProviderState newPrimaryState = mPrimaryProvider.getCurrentState();
- if (!newPrimaryState.isStarted()) {
- // If the primary provider is {perm failed} then the controller must try to start
- // the secondary.
- tryStartProvider(mSecondaryProvider, newConfiguration);
-
- ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
- if (!newSecondaryState.isStarted()) {
- // If both providers are {perm failed} then the controller immediately
- // becomes uncertain.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Providers are failed:"
- + " primary=" + mPrimaryProvider.getCurrentState()
- + " secondary=" + mPrimaryProvider.getCurrentState());
- makeSuggestion(suggestion);
- }
- }
- } else {
- stopProviders();
- }
- }
-
- @GuardedBy("mSharedLock")
- private void tryStartProvider(@NonNull LocationTimeZoneProvider provider,
- @NonNull ConfigurationInternal configuration) {
- ProviderState providerState = provider.getCurrentState();
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_STOPPED: {
- debugLog("Enabling " + provider);
- provider.startUpdates(configuration,
- mEnvironment.getProviderInitializationTimeout(),
- mEnvironment.getProviderInitializationTimeoutFuzz(),
- mEnvironment.getProviderEventFilteringAgeThreshold());
- break;
- }
- case PROVIDER_STATE_STARTED_INITIALIZING:
- case PROVIDER_STATE_STARTED_CERTAIN:
- case PROVIDER_STATE_STARTED_UNCERTAIN: {
- debugLog("No need to start " + provider + ": already started");
- break;
- }
- case PROVIDER_STATE_PERM_FAILED:
- case PROVIDER_STATE_DESTROYED: {
- debugLog("Unable to start " + provider + ": it is terminated");
- break;
- }
- default: {
- throw new IllegalStateException("Unknown provider state:"
- + " provider=" + provider);
- }
- }
- }
-
- void onProviderStateChange(@NonNull ProviderState providerState) {
- mThreadingDomain.assertCurrentThread();
- LocationTimeZoneProvider provider = providerState.provider;
- assertProviderKnown(provider);
-
- synchronized (mSharedLock) {
- // Ignore provider state changes during initialization. e.g. if the primary provider
- // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not
- // be ready to take over yet.
- if (!mProvidersInitialized) {
- warnLog("onProviderStateChange: Ignoring provider state change because both"
- + " providers have not yet completed initialization."
- + " providerState=" + providerState);
- return;
- }
-
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_STARTED_INITIALIZING:
- case PROVIDER_STATE_STOPPED:
- case PROVIDER_STATE_DESTROYED: {
- // This should never happen: entering initializing, stopped or destroyed are
- // triggered by the controller so and should not trigger a state change
- // callback.
- warnLog("onProviderStateChange: Unexpected state change for provider,"
- + " provider=" + provider);
- break;
- }
- case PROVIDER_STATE_STARTED_CERTAIN:
- case PROVIDER_STATE_STARTED_UNCERTAIN: {
- // These are valid and only happen if an event is received while the provider is
- // started.
- debugLog("onProviderStateChange: Received notification of a state change while"
- + " started, provider=" + provider);
- handleProviderStartedStateChange(providerState);
- break;
- }
- case PROVIDER_STATE_PERM_FAILED: {
- debugLog("Received notification of permanent failure for"
- + " provider=" + provider);
- handleProviderFailedStateChange(providerState);
- break;
- }
- default: {
- warnLog("onProviderStateChange: Unexpected provider=" + provider);
- }
- }
- }
- }
-
- private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) {
- if (provider != mPrimaryProvider && provider != mSecondaryProvider) {
- throw new IllegalArgumentException("Unknown provider: " + provider);
- }
- }
-
- /**
- * Called when a provider has reported that it has failed permanently.
- */
- @GuardedBy("mSharedLock")
- private void handleProviderFailedStateChange(@NonNull ProviderState providerState) {
- LocationTimeZoneProvider failedProvider = providerState.provider;
- ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState();
- ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState();
-
- // If a provider has failed, the other may need to be started.
- if (failedProvider == mPrimaryProvider) {
- if (!secondaryCurrentState.isTerminated()) {
- // Try to start the secondary. This does nothing if the provider is already
- // started, and will leave the provider in {started initializing} if the provider is
- // stopped.
- tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
- }
- } else if (failedProvider == mSecondaryProvider) {
- // No-op: The secondary will only be active if the primary is uncertain or is
- // terminated. So, there the primary should not need to be started when the secondary
- // fails.
- if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN
- && !primaryCurrentState.isTerminated()) {
- warnLog("Secondary provider unexpected reported a failure:"
- + " failed provider=" + failedProvider.getName()
- + ", primary provider=" + mPrimaryProvider
- + ", secondary provider=" + mSecondaryProvider);
- }
- }
-
- // If both providers are now terminated, the controller needs to tell the next component in
- // the time zone detection process.
- if (primaryCurrentState.isTerminated() && secondaryCurrentState.isTerminated()) {
-
- // If both providers are newly terminated then the controller is uncertain by definition
- // and it will never recover so it can send a suggestion immediately.
- cancelUncertaintyTimeout();
-
- // If both providers are now terminated, then a suggestion must be sent informing the
- // time zone detector that there are no further updates coming in future.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Both providers are terminated:"
- + " primary=" + primaryCurrentState.provider
- + ", secondary=" + secondaryCurrentState.provider);
- makeSuggestion(suggestion);
- }
- }
-
- /**
- * Called when a provider has changed state but just moved from one started state to another
- * started state, usually as a result of a new {@link TimeZoneProviderEvent} being received.
- * However, there are rare cases where the event can also be null.
- */
- @GuardedBy("mSharedLock")
- private void handleProviderStartedStateChange(@NonNull ProviderState providerState) {
- LocationTimeZoneProvider provider = providerState.provider;
- TimeZoneProviderEvent event = providerState.event;
- if (event == null) {
- // Implicit uncertainty, i.e. where the provider is started, but a problem has been
- // detected without having received an event. For example, if the process has detected
- // the loss of a binder-based provider, or initialization took too long. This is treated
- // the same as explicit uncertainty, i.e. where the provider has explicitly told this
- // process it is uncertain.
- long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis();
- handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
- "provider=" + provider + ", implicit uncertainty, event=null");
- return;
- }
-
- if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) {
- // This should not happen: the provider should not be in an started state if the user
- // does not have geodetection enabled.
- warnLog("Provider=" + provider + " is started, but"
- + " currentUserConfiguration=" + mCurrentUserConfiguration
- + " suggests it shouldn't be.");
- }
-
- switch (event.getType()) {
- case EVENT_TYPE_PERMANENT_FAILURE: {
- // This shouldn't happen. A provider cannot be started and have this event type.
- warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be");
- break;
- }
- case EVENT_TYPE_UNCERTAIN: {
- long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis();
- handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
- "provider=" + provider + ", explicit uncertainty. event=" + event);
- break;
- }
- case EVENT_TYPE_SUGGESTION: {
- handleProviderSuggestion(provider, event);
- break;
- }
- default: {
- warnLog("Unknown eventType=" + event.getType());
- break;
- }
- }
- }
-
- /**
- * Called when a provider has become "certain" about the time zone(s).
- */
- @GuardedBy("mSharedLock")
- private void handleProviderSuggestion(
- @NonNull LocationTimeZoneProvider provider,
- @NonNull TimeZoneProviderEvent providerEvent) {
-
- // By definition, the controller is now certain.
- cancelUncertaintyTimeout();
-
- if (provider == mPrimaryProvider) {
- stopProviderIfStarted(mSecondaryProvider);
- }
-
- TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
-
- // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
- // suggestion (which indicates the time when the provider detected the location used to
- // establish the time zone).
- //
- // An alternative would be to use the current time or the providerEvent creation time, but
- // this would hinder the ability for the time_zone_detector to judge which suggestions are
- // based on newer information when comparing suggestions between different sources.
- long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
- GeolocationTimeZoneSuggestion geoSuggestion =
- GeolocationTimeZoneSuggestion.createCertainSuggestion(
- effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
-
- String debugInfo = "Event received provider=" + provider
- + ", providerEvent=" + providerEvent
- + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
- geoSuggestion.addDebugInfo(debugInfo);
- makeSuggestion(geoSuggestion);
- }
-
- @Override
- public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
- synchronized (mSharedLock) {
- ipw.println("LocationTimeZoneProviderController:");
-
- ipw.increaseIndent(); // level 1
- ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
- ipw.println("providerInitializationTimeout="
- + mEnvironment.getProviderInitializationTimeout());
- ipw.println("providerInitializationTimeoutFuzz="
- + mEnvironment.getProviderInitializationTimeoutFuzz());
- ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
- ipw.println("mLastSuggestion=" + mLastSuggestion);
-
- ipw.println("Primary Provider:");
- ipw.increaseIndent(); // level 2
- mPrimaryProvider.dump(ipw, args);
- ipw.decreaseIndent(); // level 2
-
- ipw.println("Secondary Provider:");
- ipw.increaseIndent(); // level 2
- mSecondaryProvider.dump(ipw, args);
- ipw.decreaseIndent(); // level 2
-
- ipw.decreaseIndent(); // level 1
- }
- }
-
- /** Sends an immediate suggestion, updating mLastSuggestion. */
- @GuardedBy("mSharedLock")
- private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
- debugLog("makeSuggestion: suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mLastSuggestion = suggestion;
- }
-
- /** Clears the uncertainty timeout. */
- @GuardedBy("mSharedLock")
- private void cancelUncertaintyTimeout() {
- mUncertaintyTimeoutQueue.cancel();
- }
-
- /**
- * Called when a provider has become "uncertain" about the time zone.
- *
- * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
- * this enables the most flexibility for the controller to start other providers when there are
- * multiple ones available. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion to the downstream time zone detector.
- *
- * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
- * triggered later if nothing else preempts it. It can be preempted if the provider becomes
- * certain (or does anything else that calls {@link
- * #makeSuggestion(GeolocationTimeZoneSuggestion)}) within {@link
- * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
- * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
- * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
- * is not reset each time).
- */
- @GuardedBy("mSharedLock")
- void handleProviderUncertainty(
- @NonNull LocationTimeZoneProvider provider,
- @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
- @NonNull String reason) {
- Objects.requireNonNull(provider);
-
- // Start the uncertainty timeout if needed to ensure the controller will eventually make an
- // uncertain suggestion if no success event arrives in time to counteract it.
- if (!mUncertaintyTimeoutQueue.hasQueued()) {
- debugLog("Starting uncertainty timeout: reason=" + reason);
-
- Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
- mUncertaintyTimeoutQueue.runDelayed(
- () -> onProviderUncertaintyTimeout(
- provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
- uncertaintyDelay.toMillis());
- }
-
- if (provider == mPrimaryProvider) {
- // (Try to) start the secondary. It could already be started, or enabling might not
- // succeed if the provider has previously reported it is perm failed. The uncertainty
- // timeout (set above) is used to ensure that an uncertain suggestion will be made if
- // the secondary cannot generate a success event in time.
- tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
- }
- }
-
- private void onProviderUncertaintyTimeout(
- @NonNull LocationTimeZoneProvider provider,
- @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
- @NonNull Duration uncertaintyDelay) {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
-
- // For the effectiveFromElapsedMillis suggestion property, use the
- // uncertaintyStartedElapsedMillis. This is the time when the provider first reported
- // uncertainty, i.e. before the uncertainty timeout.
- //
- // afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when
- // the location_time_zone_manager finally confirms that the time zone was uncertain,
- // but the suggestion property allows the information to be back-dated, which should
- // help when comparing suggestions from different sources.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- uncertaintyStartedElapsedMillis,
- "Uncertainty timeout triggered for " + provider.getName() + ":"
- + " primary=" + mPrimaryProvider
- + ", secondary=" + mSecondaryProvider
- + ", uncertaintyStarted="
- + Duration.ofMillis(uncertaintyStartedElapsedMillis)
- + ", afterUncertaintyTimeout="
- + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
- + ", uncertaintyDelay=" + uncertaintyDelay
- );
- makeSuggestion(suggestion);
- }
- }
-
- @NonNull
- private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull String reason) {
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createUncertainSuggestion(
- effectiveFromElapsedMillis);
- suggestion.addDebugInfo(reason);
- return suggestion;
- }
-
- /**
- * Clears recorded provider state changes (for use during tests).
- */
- void clearRecordedProviderStates() {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- mPrimaryProvider.clearRecordedStates();
- mSecondaryProvider.clearRecordedStates();
- }
- }
-
- /**
- * Returns a snapshot of the current controller state for tests.
- */
- @NonNull
- LocationTimeZoneManagerServiceState getStateForTests() {
- mThreadingDomain.assertCurrentThread();
-
- synchronized (mSharedLock) {
- LocationTimeZoneManagerServiceState.Builder builder =
- new LocationTimeZoneManagerServiceState.Builder();
- if (mLastSuggestion != null) {
- builder.setLastSuggestion(mLastSuggestion);
- }
- builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates())
- .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates());
- return builder.build();
- }
- }
-}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index ddbeac4..af8cf6e 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -147,16 +147,16 @@
/** The shared lock from {@link #mThreadingDomain}. */
@NonNull private final Object mSharedLock;
- @NonNull
- private final ServiceConfigAccessor mServiceConfigAccessor;
+ @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
// Lazily initialized. Can be null if the service has been stopped.
@GuardedBy("mSharedLock")
- private ControllerImpl mLocationTimeZoneDetectorController;
+ private LocationTimeZoneProviderController mLocationTimeZoneProviderController;
// Lazily initialized. Can be null if the service has been stopped.
@GuardedBy("mSharedLock")
- private ControllerEnvironmentImpl mEnvironment;
+ private LocationTimeZoneProviderControllerEnvironmentImpl
+ mLocationTimeZoneProviderControllerEnvironment;
LocationTimeZoneManagerService(@NonNull Context context,
@NonNull ServiceConfigAccessor serviceConfigAccessor) {
@@ -190,7 +190,7 @@
// Avoid starting the service if it is currently stopped. This is required because
// server flags are used by tests to set behavior with the service stopped, and we don't
// want the service being restarted after each flag is set.
- if (mLocationTimeZoneDetectorController != null) {
+ if (mLocationTimeZoneProviderController != null) {
// Stop and start the service, waiting until completion.
stopOnDomainThread();
startOnDomainThread();
@@ -278,19 +278,22 @@
return;
}
- if (mLocationTimeZoneDetectorController == null) {
+ if (mLocationTimeZoneProviderController == null) {
LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider();
LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider();
- ControllerImpl controller =
- new ControllerImpl(mThreadingDomain, primary, secondary);
- ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
- mThreadingDomain, mServiceConfigAccessor, controller);
- ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
+ LocationTimeZoneProviderController controller =
+ new LocationTimeZoneProviderController(
+ mThreadingDomain, primary, secondary);
+ LocationTimeZoneProviderControllerEnvironmentImpl environment =
+ new LocationTimeZoneProviderControllerEnvironmentImpl(
+ mThreadingDomain, mServiceConfigAccessor, controller);
+ LocationTimeZoneProviderControllerCallbackImpl callback =
+ new LocationTimeZoneProviderControllerCallbackImpl(mThreadingDomain);
controller.initialize(environment, callback);
- mEnvironment = environment;
- mLocationTimeZoneDetectorController = controller;
+ mLocationTimeZoneProviderControllerEnvironment = environment;
+ mLocationTimeZoneProviderController = controller;
}
}
}
@@ -312,11 +315,11 @@
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
- if (mLocationTimeZoneDetectorController != null) {
- mLocationTimeZoneDetectorController.destroy();
- mLocationTimeZoneDetectorController = null;
- mEnvironment.destroy();
- mEnvironment = null;
+ if (mLocationTimeZoneProviderController != null) {
+ mLocationTimeZoneProviderController.destroy();
+ mLocationTimeZoneProviderController = null;
+ mLocationTimeZoneProviderControllerEnvironment.destroy();
+ mLocationTimeZoneProviderControllerEnvironment = null;
// Clear test state so it won't be used the next time the service is started.
mServiceConfigAccessor.resetVolatileTestConfig();
@@ -338,8 +341,8 @@
mThreadingDomain.postAndWait(() -> {
synchronized (mSharedLock) {
- if (mLocationTimeZoneDetectorController != null) {
- mLocationTimeZoneDetectorController.clearRecordedProviderStates();
+ if (mLocationTimeZoneProviderController != null) {
+ mLocationTimeZoneProviderController.clearRecordedProviderStates();
}
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -357,10 +360,10 @@
return mThreadingDomain.postAndWait(
() -> {
synchronized (mSharedLock) {
- if (mLocationTimeZoneDetectorController == null) {
+ if (mLocationTimeZoneProviderController == null) {
return null;
}
- return mLocationTimeZoneDetectorController.getStateForTests();
+ return mLocationTimeZoneProviderController.getStateForTests();
}
},
BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -390,10 +393,10 @@
mSecondaryProviderConfig.dump(ipw, args);
ipw.decreaseIndent();
- if (mLocationTimeZoneDetectorController == null) {
+ if (mLocationTimeZoneProviderController == null) {
ipw.println("{Stopped}");
} else {
- mLocationTimeZoneDetectorController.dump(ipw, args);
+ mLocationTimeZoneProviderController.dump(ipw, args);
}
ipw.decreaseIndent();
}
@@ -450,7 +453,6 @@
mServiceConfigAccessor.getRecordProviderStateChanges());
}
- @GuardedBy("mSharedLock")
@Override
public void dump(IndentingPrintWriter ipw, String[] args) {
ipw.printf("getMode()=%s\n", getMode());
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index 4dff02e..dbd3877 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -16,31 +16,57 @@
package com.android.server.timezonedetector.location;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+
import android.annotation.DurationMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.os.Handler;
+import android.annotation.Nullable;
+import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderSuggestion;
+import android.util.IndentingPrintWriter;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
-import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
+import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
import java.time.Duration;
import java.util.Objects;
/**
- * An base class for the component responsible handling events from {@link
- * LocationTimeZoneProvider}s and synthesizing time zone ID suggestions for sending to the time zone
- * detector. This interface primarily exists to extract testable detection logic, i.e. with
- * a minimal number of threading considerations or dependencies on Android infrastructure.
+ * The component responsible handling events from {@link LocationTimeZoneProvider}s and synthesizing
+ * time zone ID suggestions for sending to the time zone detector.
+ *
+ * <p>This class primarily exists to extract unit-testable logic from the surrounding service class,
+ * i.e. with a minimal number of threading considerations or direct dependencies on Android
+ * infrastructure.
+ *
+ * <p>This class supports a primary and a secondary {@link LocationTimeZoneProvider}. The primary is
+ * used until it fails or becomes uncertain. The secondary will then be started. The controller will
+ * immediately make suggestions based on "certain" {@link TimeZoneProviderEvent}s, i.e. events that
+ * demonstrate the provider is certain what the time zone is. The controller will not make immediate
+ * suggestions based on "uncertain" events, giving providers time to change their mind. This also
+ * gives the secondary provider time to initialize when the primary becomes uncertain.
*
* <p>The controller interacts with the following components:
* <ul>
- * <li>The surrounding service, which calls {@link #initialize(Environment, Callback)} and
- * {@link #onConfigurationInternalChanged()}.</li>
- * <li>The {@link Environment} through which obtains information it needs.</li>
+ * <li>The surrounding service, which calls {@link #initialize(Environment, Callback)}.
+ * <li>The {@link Environment} through which it obtains information it needs.</li>
* <li>The {@link Callback} through which it makes time zone suggestions.</li>
* <li>Any {@link LocationTimeZoneProvider} instances it owns, which communicate via the
* {@link LocationTimeZoneProvider.ProviderListener#onProviderStateChange(ProviderState)}
@@ -49,8 +75,9 @@
*
* <p>All incoming calls except for {@link
* LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
- * made on the {@link Handler} thread of the {@link ThreadingDomain} passed to {@link
- * #LocationTimeZoneProviderController(ThreadingDomain)}.
+ * made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link
+ * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider,
+ * LocationTimeZoneProvider)}.
*
* <p>Provider / controller integration notes:
*
@@ -59,43 +86,631 @@
* different from the certainty that there are no time zone IDs for the current location. A provider
* can be certain about there being no time zone IDs for a location for good reason, e.g. for
* disputed areas and oceans. Distinguishing uncertainty allows the controller to try other
- * providers (or give up), where as certainty means it should not.
+ * providers (or give up), whereas certainty means it should not.
*
* <p>A provider can fail permanently. A permanent failure will stop the provider until next
* boot.
*/
-abstract class LocationTimeZoneProviderController implements Dumpable {
+class LocationTimeZoneProviderController implements Dumpable {
- @NonNull protected final ThreadingDomain mThreadingDomain;
- @NonNull protected final Object mSharedLock;
+ @NonNull private final ThreadingDomain mThreadingDomain;
+ @NonNull private final Object mSharedLock;
+ /**
+ * Used for scheduling uncertainty timeouts, i.e. after a provider has reported uncertainty.
+ * This timeout is not provider-specific: it is started when the controller becomes uncertain
+ * due to events it has received from one or other provider.
+ */
+ @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
- LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain) {
+ @NonNull private final LocationTimeZoneProvider mPrimaryProvider;
+ @NonNull private final LocationTimeZoneProvider mSecondaryProvider;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private ConfigurationInternal mCurrentUserConfiguration;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private Environment mEnvironment;
+
+ @GuardedBy("mSharedLock")
+ // Non-null after initialize()
+ private Callback mCallback;
+
+ /** Indicates both providers have completed initialization. */
+ @GuardedBy("mSharedLock")
+ private boolean mProvidersInitialized;
+
+
+ /** Contains the last suggestion actually made, if there is one. */
+ @GuardedBy("mSharedLock")
+ @Nullable
+ private GeolocationTimeZoneSuggestion mLastSuggestion;
+
+ LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain,
+ @NonNull LocationTimeZoneProvider primaryProvider,
+ @NonNull LocationTimeZoneProvider secondaryProvider) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mSharedLock = threadingDomain.getLockObject();
+ mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
+ mPrimaryProvider = Objects.requireNonNull(primaryProvider);
+ mSecondaryProvider = Objects.requireNonNull(secondaryProvider);
}
/**
* Called to initialize the controller during boot. Called once only.
* {@link LocationTimeZoneProvider#initialize} must be called by this method.
*/
- abstract void initialize(@NonNull Environment environment, @NonNull Callback callback);
+ void initialize(@NonNull Environment environment, @NonNull Callback callback) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ debugLog("initialize()");
+ mEnvironment = Objects.requireNonNull(environment);
+ mCallback = Objects.requireNonNull(callback);
+ mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
+
+ LocationTimeZoneProvider.ProviderListener providerListener =
+ LocationTimeZoneProviderController.this::onProviderStateChange;
+ mPrimaryProvider.initialize(providerListener);
+ mSecondaryProvider.initialize(providerListener);
+ mProvidersInitialized = true;
+
+ alterProvidersStartedStateIfRequired(
+ null /* oldConfiguration */, mCurrentUserConfiguration);
+ }
+ }
/**
* Called when the content of the {@link ConfigurationInternal} may have changed. The receiver
* should call {@link Environment#getCurrentUserConfigurationInternal()} to get the current
* user's config. This call must be made on the {@link ThreadingDomain} handler thread.
*/
- abstract void onConfigurationInternalChanged();
+ void onConfigurationInternalChanged() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ debugLog("onConfigChanged()");
+
+ ConfigurationInternal oldConfig = mCurrentUserConfiguration;
+ ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal();
+ mCurrentUserConfiguration = newConfig;
+
+ if (!newConfig.equals(oldConfig)) {
+ if (newConfig.getUserId() != oldConfig.getUserId()) {
+ // If the user changed, stop the providers if needed. They may be re-started
+ // for the new user immediately afterwards if their settings allow.
+ debugLog("User changed. old=" + oldConfig.getUserId()
+ + ", new=" + newConfig.getUserId() + ": Stopping providers");
+ stopProviders();
+
+ alterProvidersStartedStateIfRequired(null /* oldConfiguration */, newConfig);
+ } else {
+ alterProvidersStartedStateIfRequired(oldConfig, newConfig);
+ }
+ }
+ }
+ }
@VisibleForTesting
- abstract boolean isUncertaintyTimeoutSet();
+ boolean isUncertaintyTimeoutSet() {
+ return mUncertaintyTimeoutQueue.hasQueued();
+ }
@VisibleForTesting
@DurationMillisLong
- abstract long getUncertaintyTimeoutDelayMillis();
+ long getUncertaintyTimeoutDelayMillis() {
+ return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
+ }
/** Called if the geolocation time zone detection is being reconfigured. */
- abstract void destroy();
+ void destroy() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ stopProviders();
+ mPrimaryProvider.destroy();
+ mSecondaryProvider.destroy();
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void stopProviders() {
+ stopProviderIfStarted(mPrimaryProvider);
+ stopProviderIfStarted(mSecondaryProvider);
+
+ // By definition, if both providers are stopped, the controller is uncertain.
+ cancelUncertaintyTimeout();
+
+ // If a previous "certain" suggestion has been made, then a new "uncertain"
+ // suggestion must now be made to indicate the controller {does not / no longer has}
+ // an opinion and will not be sending further updates (until at least the providers are
+ // re-started).
+ if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ mEnvironment.elapsedRealtimeMillis(), "Providers are stopping");
+ makeSuggestion(suggestion);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void stopProviderIfStarted(@NonNull LocationTimeZoneProvider provider) {
+ if (provider.getCurrentState().isStarted()) {
+ stopProvider(provider);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void stopProvider(@NonNull LocationTimeZoneProvider provider) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_STOPPED: {
+ debugLog("No need to stop " + provider + ": already stopped");
+ break;
+ }
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ case PROVIDER_STATE_STARTED_UNCERTAIN: {
+ debugLog("Stopping " + provider);
+ provider.stopUpdates();
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED:
+ case PROVIDER_STATE_DESTROYED: {
+ debugLog("Unable to stop " + provider + ": it is terminated.");
+ break;
+ }
+ default: {
+ warnLog("Unknown provider state: " + provider);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Sets the providers into the correct started/stopped state for the {@code newConfiguration}
+ * and, if there is a provider state change, makes any suggestions required to inform the
+ * downstream time zone detection code.
+ *
+ * <p>This is a utility method that exists to avoid duplicated logic for the various cases when
+ * provider started / stopped state may need to be set or changed, e.g. during initialization
+ * or when a new configuration has been received.
+ */
+ @GuardedBy("mSharedLock")
+ private void alterProvidersStartedStateIfRequired(
+ @Nullable ConfigurationInternal oldConfiguration,
+ @NonNull ConfigurationInternal newConfiguration) {
+
+ // Provider started / stopped states only need to be changed if geoDetectionEnabled has
+ // changed.
+ boolean oldGeoDetectionEnabled = oldConfiguration != null
+ && oldConfiguration.getGeoDetectionEnabledBehavior();
+ boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
+ if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
+ return;
+ }
+
+ // The check above ensures that the logic below only executes if providers are going from
+ // {started *} -> {stopped}, or {stopped} -> {started initializing}. If this changes in
+ // future and there could be {started *} -> {started *} cases, or cases where the provider
+ // can't be assumed to go straight to the {started initializing} state, then the logic below
+ // would need to cover extra conditions, for example:
+ // 1) If the primary is in {started uncertain}, the secondary should be started.
+ // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty
+ // timeout started when the primary entered {started uncertain} should be cancelled.
+
+ if (newGeoDetectionEnabled) {
+ // Try to start the primary provider.
+ tryStartProvider(mPrimaryProvider, newConfiguration);
+
+ // The secondary should only ever be started if the primary now isn't started (i.e. it
+ // couldn't become {started initializing} because it is {perm failed}).
+ ProviderState newPrimaryState = mPrimaryProvider.getCurrentState();
+ if (!newPrimaryState.isStarted()) {
+ // If the primary provider is {perm failed} then the controller must try to start
+ // the secondary.
+ tryStartProvider(mSecondaryProvider, newConfiguration);
+
+ ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
+ if (!newSecondaryState.isStarted()) {
+ // If both providers are {perm failed} then the controller immediately
+ // becomes uncertain.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ mEnvironment.elapsedRealtimeMillis(),
+ "Providers are failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState()
+ + " secondary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
+ }
+ } else {
+ stopProviders();
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void tryStartProvider(@NonNull LocationTimeZoneProvider provider,
+ @NonNull ConfigurationInternal configuration) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_STOPPED: {
+ debugLog("Enabling " + provider);
+ provider.startUpdates(configuration,
+ mEnvironment.getProviderInitializationTimeout(),
+ mEnvironment.getProviderInitializationTimeoutFuzz(),
+ mEnvironment.getProviderEventFilteringAgeThreshold());
+ break;
+ }
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ case PROVIDER_STATE_STARTED_UNCERTAIN: {
+ debugLog("No need to start " + provider + ": already started");
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED:
+ case PROVIDER_STATE_DESTROYED: {
+ debugLog("Unable to start " + provider + ": it is terminated");
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Unknown provider state:"
+ + " provider=" + provider);
+ }
+ }
+ }
+
+ void onProviderStateChange(@NonNull ProviderState providerState) {
+ mThreadingDomain.assertCurrentThread();
+ LocationTimeZoneProvider provider = providerState.provider;
+ assertProviderKnown(provider);
+
+ synchronized (mSharedLock) {
+ // Ignore provider state changes during initialization. e.g. if the primary provider
+ // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not
+ // be ready to take over yet.
+ if (!mProvidersInitialized) {
+ warnLog("onProviderStateChange: Ignoring provider state change because both"
+ + " providers have not yet completed initialization."
+ + " providerState=" + providerState);
+ return;
+ }
+
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ case PROVIDER_STATE_STOPPED:
+ case PROVIDER_STATE_DESTROYED: {
+ // This should never happen: entering initializing, stopped or destroyed are
+ // triggered by the controller so and should not trigger a state change
+ // callback.
+ warnLog("onProviderStateChange: Unexpected state change for provider,"
+ + " provider=" + provider);
+ break;
+ }
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ case PROVIDER_STATE_STARTED_UNCERTAIN: {
+ // These are valid and only happen if an event is received while the provider is
+ // started.
+ debugLog("onProviderStateChange: Received notification of a state change while"
+ + " started, provider=" + provider);
+ handleProviderStartedStateChange(providerState);
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("Received notification of permanent failure for"
+ + " provider=" + provider);
+ handleProviderFailedStateChange(providerState);
+ break;
+ }
+ default: {
+ warnLog("onProviderStateChange: Unexpected provider=" + provider);
+ }
+ }
+ }
+ }
+
+ private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) {
+ if (provider != mPrimaryProvider && provider != mSecondaryProvider) {
+ throw new IllegalArgumentException("Unknown provider: " + provider);
+ }
+ }
+
+ /**
+ * Called when a provider has reported that it has failed permanently.
+ */
+ @GuardedBy("mSharedLock")
+ private void handleProviderFailedStateChange(@NonNull ProviderState providerState) {
+ LocationTimeZoneProvider failedProvider = providerState.provider;
+ ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState();
+ ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState();
+
+ // If a provider has failed, the other may need to be started.
+ if (failedProvider == mPrimaryProvider) {
+ if (!secondaryCurrentState.isTerminated()) {
+ // Try to start the secondary. This does nothing if the provider is already
+ // started, and will leave the provider in {started initializing} if the provider is
+ // stopped.
+ tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
+ }
+ } else if (failedProvider == mSecondaryProvider) {
+ // No-op: The secondary will only be active if the primary is uncertain or is
+ // terminated. So, there the primary should not need to be started when the secondary
+ // fails.
+ if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN
+ && !primaryCurrentState.isTerminated()) {
+ warnLog("Secondary provider unexpected reported a failure:"
+ + " failed provider=" + failedProvider.getName()
+ + ", primary provider=" + mPrimaryProvider
+ + ", secondary provider=" + mSecondaryProvider);
+ }
+ }
+
+ // If both providers are now terminated, the controller needs to tell the next component in
+ // the time zone detection process.
+ if (primaryCurrentState.isTerminated() && secondaryCurrentState.isTerminated()) {
+
+ // If both providers are newly terminated then the controller is uncertain by definition
+ // and it will never recover so it can send a suggestion immediately.
+ cancelUncertaintyTimeout();
+
+ // If both providers are now terminated, then a suggestion must be sent informing the
+ // time zone detector that there are no further updates coming in future.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ mEnvironment.elapsedRealtimeMillis(),
+ "Both providers are terminated:"
+ + " primary=" + primaryCurrentState.provider
+ + ", secondary=" + secondaryCurrentState.provider);
+ makeSuggestion(suggestion);
+ }
+ }
+
+ /**
+ * Called when a provider has changed state but just moved from one started state to another
+ * started state, usually as a result of a new {@link TimeZoneProviderEvent} being received.
+ * However, there are rare cases where the event can also be null.
+ */
+ @GuardedBy("mSharedLock")
+ private void handleProviderStartedStateChange(@NonNull ProviderState providerState) {
+ LocationTimeZoneProvider provider = providerState.provider;
+ TimeZoneProviderEvent event = providerState.event;
+ if (event == null) {
+ // Implicit uncertainty, i.e. where the provider is started, but a problem has been
+ // detected without having received an event. For example, if the process has detected
+ // the loss of a binder-based provider, or initialization took too long. This is treated
+ // the same as explicit uncertainty, i.e. where the provider has explicitly told this
+ // process it is uncertain.
+ long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+ handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
+ "provider=" + provider + ", implicit uncertainty, event=null");
+ return;
+ }
+
+ if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) {
+ // This should not happen: the provider should not be in an started state if the user
+ // does not have geodetection enabled.
+ warnLog("Provider=" + provider + " is started, but"
+ + " currentUserConfiguration=" + mCurrentUserConfiguration
+ + " suggests it shouldn't be.");
+ }
+
+ switch (event.getType()) {
+ case EVENT_TYPE_PERMANENT_FAILURE: {
+ // This shouldn't happen. A provider cannot be started and have this event type.
+ warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be");
+ break;
+ }
+ case EVENT_TYPE_UNCERTAIN: {
+ long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis();
+ handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis,
+ "provider=" + provider + ", explicit uncertainty. event=" + event);
+ break;
+ }
+ case EVENT_TYPE_SUGGESTION: {
+ handleProviderSuggestion(provider, event);
+ break;
+ }
+ default: {
+ warnLog("Unknown eventType=" + event.getType());
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called when a provider has become "certain" about the time zone(s).
+ */
+ @GuardedBy("mSharedLock")
+ private void handleProviderSuggestion(
+ @NonNull LocationTimeZoneProvider provider,
+ @NonNull TimeZoneProviderEvent providerEvent) {
+
+ // By definition, the controller is now certain.
+ cancelUncertaintyTimeout();
+
+ if (provider == mPrimaryProvider) {
+ stopProviderIfStarted(mSecondaryProvider);
+ }
+
+ TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
+
+ // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
+ // suggestion (which indicates the time when the provider detected the location used to
+ // establish the time zone).
+ //
+ // An alternative would be to use the current time or the providerEvent creation time, but
+ // this would hinder the ability for the time_zone_detector to judge which suggestions are
+ // based on newer information when comparing suggestions between different sources.
+ long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
+ GeolocationTimeZoneSuggestion geoSuggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
+
+ String debugInfo = "Event received provider=" + provider
+ + ", providerEvent=" + providerEvent
+ + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
+ geoSuggestion.addDebugInfo(debugInfo);
+ makeSuggestion(geoSuggestion);
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("LocationTimeZoneProviderController:");
+
+ ipw.increaseIndent(); // level 1
+ ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
+ ipw.println("providerInitializationTimeout="
+ + mEnvironment.getProviderInitializationTimeout());
+ ipw.println("providerInitializationTimeoutFuzz="
+ + mEnvironment.getProviderInitializationTimeoutFuzz());
+ ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
+ ipw.println("mLastSuggestion=" + mLastSuggestion);
+
+ ipw.println("Primary Provider:");
+ ipw.increaseIndent(); // level 2
+ mPrimaryProvider.dump(ipw, args);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.println("Secondary Provider:");
+ ipw.increaseIndent(); // level 2
+ mSecondaryProvider.dump(ipw, args);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.decreaseIndent(); // level 1
+ }
+ }
+
+ /** Sends an immediate suggestion, updating mLastSuggestion. */
+ @GuardedBy("mSharedLock")
+ private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ debugLog("makeSuggestion: suggestion=" + suggestion);
+ mCallback.suggest(suggestion);
+ mLastSuggestion = suggestion;
+ }
+
+ /** Clears the uncertainty timeout. */
+ @GuardedBy("mSharedLock")
+ private void cancelUncertaintyTimeout() {
+ mUncertaintyTimeoutQueue.cancel();
+ }
+
+ /**
+ * Called when a provider has become "uncertain" about the time zone.
+ *
+ * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
+ * this enables the most flexibility for the controller to start other providers when there are
+ * multiple ones available. The controller is therefore responsible for deciding when to make a
+ * "uncertain" suggestion to the downstream time zone detector.
+ *
+ * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
+ * triggered later if nothing else preempts it. It can be preempted if the provider becomes
+ * certain (or does anything else that calls {@link
+ * #makeSuggestion(GeolocationTimeZoneSuggestion)}) within {@link
+ * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
+ * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
+ * is not reset each time).
+ */
+ @GuardedBy("mSharedLock")
+ void handleProviderUncertainty(
+ @NonNull LocationTimeZoneProvider provider,
+ @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
+ @NonNull String reason) {
+ Objects.requireNonNull(provider);
+
+ // Start the uncertainty timeout if needed to ensure the controller will eventually make an
+ // uncertain suggestion if no success event arrives in time to counteract it.
+ if (!mUncertaintyTimeoutQueue.hasQueued()) {
+ debugLog("Starting uncertainty timeout: reason=" + reason);
+
+ Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay();
+ mUncertaintyTimeoutQueue.runDelayed(
+ () -> onProviderUncertaintyTimeout(
+ provider, uncertaintyStartedElapsedMillis, uncertaintyDelay),
+ uncertaintyDelay.toMillis());
+ }
+
+ if (provider == mPrimaryProvider) {
+ // (Try to) start the secondary. It could already be started, or enabling might not
+ // succeed if the provider has previously reported it is perm failed. The uncertainty
+ // timeout (set above) is used to ensure that an uncertain suggestion will be made if
+ // the secondary cannot generate a success event in time.
+ tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration);
+ }
+ }
+
+ private void onProviderUncertaintyTimeout(
+ @NonNull LocationTimeZoneProvider provider,
+ @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis,
+ @NonNull Duration uncertaintyDelay) {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+
+ // For the effectiveFromElapsedMillis suggestion property, use the
+ // uncertaintyStartedElapsedMillis. This is the time when the provider first reported
+ // uncertainty, i.e. before the uncertainty timeout.
+ //
+ // afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when
+ // the location_time_zone_manager finally confirms that the time zone was uncertain,
+ // but the suggestion property allows the information to be back-dated, which should
+ // help when comparing suggestions from different sources.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ uncertaintyStartedElapsedMillis,
+ "Uncertainty timeout triggered for " + provider.getName() + ":"
+ + " primary=" + mPrimaryProvider
+ + ", secondary=" + mSecondaryProvider
+ + ", uncertaintyStarted="
+ + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ + ", afterUncertaintyTimeout="
+ + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ + ", uncertaintyDelay=" + uncertaintyDelay
+ );
+ makeSuggestion(suggestion);
+ }
+ }
+
+ @NonNull
+ private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis,
+ @NonNull String reason) {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ effectiveFromElapsedMillis);
+ suggestion.addDebugInfo(reason);
+ return suggestion;
+ }
+
+ /**
+ * Clears recorded provider state changes (for use during tests).
+ */
+ void clearRecordedProviderStates() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ mPrimaryProvider.clearRecordedStates();
+ mSecondaryProvider.clearRecordedStates();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the current controller state for tests.
+ */
+ @NonNull
+ LocationTimeZoneManagerServiceState getStateForTests() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ LocationTimeZoneManagerServiceState.Builder builder =
+ new LocationTimeZoneManagerServiceState.Builder();
+ if (mLastSuggestion != null) {
+ builder.setLastSuggestion(mLastSuggestion);
+ }
+ builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates())
+ .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates());
+ return builder.build();
+ }
+ }
/**
* Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
similarity index 82%
rename from services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 46eaad0..0c751aa 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
@@ -24,11 +24,12 @@
/**
* The real implementation of {@link LocationTimeZoneProviderController.Callback} used by
- * {@link ControllerImpl} to interact with other server components.
+ * {@link LocationTimeZoneProviderController} to interact with other server components.
*/
-class ControllerCallbackImpl extends LocationTimeZoneProviderController.Callback {
+class LocationTimeZoneProviderControllerCallbackImpl
+ extends LocationTimeZoneProviderController.Callback {
- ControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) {
+ LocationTimeZoneProviderControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) {
super(threadingDomain);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
similarity index 90%
rename from services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index 33cdc5f..e7d16c8 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -29,14 +29,15 @@
/**
* The real implementation of {@link LocationTimeZoneProviderController.Environment} used by
- * {@link ControllerImpl} to interact with other server components.
+ * {@link LocationTimeZoneProviderController} to interact with other server components.
*/
-class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
+class LocationTimeZoneProviderControllerEnvironmentImpl
+ extends LocationTimeZoneProviderController.Environment {
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
- ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
+ LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull LocationTimeZoneProviderController controller) {
super(threadingDomain);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 71a6b22..f82f99d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -186,7 +186,7 @@
registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
- registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER));
+ registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER));
registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE));
registerSettingsObserver(
Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
@@ -366,7 +366,7 @@
public void updateSettings() {
synchronized (mLock) {
mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
- mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0;
+ mApplyRampingRinger = getSystemSetting(Settings.System.APPLY_RAMPING_RINGER, 0) != 0;
mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
getDefaultIntensity(USAGE_TOUCH));
mHardwareFeedbackIntensity = getSystemSetting(
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2a1bb0b..ffc70da 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1376,9 +1376,7 @@
/** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */
private boolean shouldStartChangeTransition(
@Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) {
- if (mWmService.mDisableTransitionAnimation
- || mDisplayContent == null || newParent == null || oldParent == null
- || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) {
+ if (newParent == null || oldParent == null || !canStartChangeTransition()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7fa9861..e38e9c1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -126,35 +126,6 @@
}
/**
- * Sleep tokens cause the activity manager to put the top activity to sleep.
- * They are used by components such as dreams that may hide and block interaction
- * with underlying activities.
- * The Acquirer provides an interface that encapsulates the underlying work, so the user does
- * not need to handle the token by him/herself.
- */
- public interface SleepTokenAcquirer {
-
- /**
- * Acquires a sleep token.
- * @param displayId The display to apply to.
- */
- void acquire(int displayId);
-
- /**
- * Releases the sleep token.
- * @param displayId The display to apply to.
- */
- void release(int displayId);
- }
-
- /**
- * Creates a sleep token acquirer for the specified display with the specified tag.
- *
- * @param tag A string identifying the purpose (eg. "Dream").
- */
- public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
-
- /**
* Returns home activity for the specified user.
*
* @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0a85ba1..e4ed04de 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4544,17 +4544,25 @@
reason);
}
- final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
+ /**
+ * Sleep tokens cause the activity manager to put the top activity to sleep.
+ * They are used by components such as dreams that may hide and block interaction
+ * with underlying activities.
+ */
+ final class SleepTokenAcquirer {
private final String mTag;
private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
new SparseArray<>();
- SleepTokenAcquirerImpl(@NonNull String tag) {
+ SleepTokenAcquirer(@NonNull String tag) {
mTag = tag;
}
- @Override
- public void acquire(int displayId) {
+ /**
+ * Acquires a sleep token.
+ * @param displayId The display to apply to.
+ */
+ void acquire(int displayId) {
synchronized (mGlobalLock) {
if (!mSleepTokens.contains(displayId)) {
mSleepTokens.append(displayId,
@@ -4564,8 +4572,11 @@
}
}
- @Override
- public void release(int displayId) {
+ /**
+ * Releases the sleep token.
+ * @param displayId The display to apply to.
+ */
+ void release(int displayId) {
synchronized (mGlobalLock) {
final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
if (token != null) {
@@ -5255,12 +5266,6 @@
final class LocalService extends ActivityTaskManagerInternal {
@Override
- public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
- Objects.requireNonNull(tag);
- return new SleepTokenAcquirerImpl(tag);
- }
-
- @Override
public ComponentName getHomeActivityForUser(int userId) {
synchronized (mGlobalLock) {
final ActivityRecord homeActivity =
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1f1fd34..68eff9a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -34,6 +34,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Build.VERSION_CODES.N;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.util.RotationUtils.deltaRotation;
@@ -43,6 +44,8 @@
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static android.view.Display.STATE_UNKNOWN;
+import static android.view.Display.isSuspendedState;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -61,6 +64,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@@ -317,11 +321,6 @@
*/
private Rect mLastMirroredDisplayAreaBounds = null;
- /**
- * The last state of the display.
- */
- private int mLastDisplayState;
-
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
// window containers together and move them in-sync if/when needed. We use a subclass of
@@ -665,7 +664,7 @@
/** All tokens used to put activities on this root task to sleep (including mOffToken) */
final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
/** The token acquirer to put root tasks on the display to sleep */
- private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
+ private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer;
private boolean mSleeping;
@@ -4667,12 +4666,9 @@
mWmService.requestTraversal();
}
+ @Override
boolean okToDisplay() {
- return okToDisplay(false);
- }
-
- boolean okToDisplay(boolean ignoreFrozen) {
- return okToDisplay(ignoreFrozen, false /* ignoreScreenOn */);
+ return okToDisplay(false /* ignoreFrozen */, false /* ignoreScreenOn */);
}
boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
@@ -4684,18 +4680,12 @@
return mDisplayInfo.state == Display.STATE_ON;
}
- boolean okToAnimate() {
- return okToAnimate(false);
- }
-
- boolean okToAnimate(boolean ignoreFrozen) {
- return okToAnimate(ignoreFrozen, false /* ignoreScreenOn */);
- }
-
+ @Override
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
return okToDisplay(ignoreFrozen, ignoreScreenOn)
&& (mDisplayId != DEFAULT_DISPLAY
- || mWmService.mPolicy.okToAnimate(ignoreScreenOn));
+ || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+ && getDisplayPolicy().isScreenOnFully();
}
static final class TaskForResizePointSearchResult implements Predicate<Task> {
@@ -4949,6 +4939,12 @@
reconfigureDisplayLocked();
onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
+ // Attach the SystemUiContext to this DisplayContent the get latest configuration.
+ // Note that the SystemUiContext will be removed automatically if this DisplayContent
+ // is detached.
+ mWmService.mWindowContextListenerController.registerWindowContainerListener(
+ getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID,
+ INVALID_WINDOW_TYPE, null /* options */);
}
}
@@ -5483,29 +5479,44 @@
return mMetricsLogger;
}
+ void acquireScreenOffToken(boolean acquire) {
+ if (acquire) {
+ mOffTokenAcquirer.acquire(mDisplayId);
+ } else {
+ mOffTokenAcquirer.release(mDisplayId);
+ }
+ }
+
void onDisplayChanged() {
mDisplay.getRealSize(mTmpDisplaySize);
setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
+ final int lastDisplayState = mDisplayInfo.state;
updateDisplayInfo();
// The window policy is responsible for stopping activities on the default display.
final int displayId = mDisplay.getDisplayId();
+ final int displayState = mDisplayInfo.state;
if (displayId != DEFAULT_DISPLAY) {
- final int displayState = mDisplay.getState();
if (displayState == Display.STATE_OFF) {
- mOffTokenAcquirer.acquire(mDisplayId);
+ acquireScreenOffToken(true /* acquire */);
} else if (displayState == Display.STATE_ON) {
- mOffTokenAcquirer.release(mDisplayId);
+ acquireScreenOffToken(false /* acquire */);
}
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Display %d state is now (%d), so update layer mirroring?",
mDisplayId, displayState);
- if (mLastDisplayState != displayState) {
+ if (lastDisplayState != displayState) {
// If state is on due to surface being added, then start layer mirroring.
// If state is off due to surface being removed, then stop layer mirroring.
updateMirroring();
}
- mLastDisplayState = displayState;
+ }
+ // Dispatch pending Configuration to WindowContext if the associated display changes to
+ // un-suspended state from suspended.
+ if (isSuspendedState(lastDisplayState)
+ && !isSuspendedState(displayState) && displayState != STATE_UNKNOWN) {
+ mWmService.mWindowContextListenerController
+ .dispatchPendingConfigurationIfNeeded(mDisplayId);
}
mWmService.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6d3e8d5..cbb9d5d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -426,7 +426,7 @@
: service.mContext.createDisplayContext(displayContent.getDisplay());
mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext
: service.mAtmService.mSystemThread
- .createSystemUiContext(displayContent.getDisplayId());
+ .getSystemUiContext(displayContent.getDisplayId());
mDisplayContent = displayContent;
mLock = service.getWindowManagerLock();
@@ -752,6 +752,10 @@
public void setAwake(boolean awake) {
mAwake = awake;
+ // The screen off token for non-default display is controlled by DisplayContent.
+ if (mDisplayContent.isDefaultDisplay) {
+ mDisplayContent.acquireScreenOffToken(!awake);
+ }
}
public boolean isAwake() {
@@ -1887,7 +1891,8 @@
// user's package info (see ContextImpl.createDisplayContext)
final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo(
uiContext.getPackageName(), null, 0, userId);
- mCurrentUserResources = ResourcesManager.getInstance().getResources(null,
+ mCurrentUserResources = ResourcesManager.getInstance().getResources(
+ uiContext.getWindowContextToken(),
pi.getResDir(),
null /* splitResDirs */,
pi.getOverlayDirs(),
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index fee9884..a843909d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -75,14 +75,14 @@
private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
private final ActivityTaskManagerService mService;
private RootWindowContainer mRootWindowContainer;
- private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
+ private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardController(ActivityTaskManagerService service,
ActivityTaskSupervisor taskSupervisor) {
mService = service;
mTaskSupervisor = taskSupervisor;
- mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
+ mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG);
}
void setWindowManager(WindowManagerService windowManager) {
@@ -513,10 +513,10 @@
private boolean mRequestDismissKeyguard;
private final ActivityTaskManagerService mService;
- private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
+ private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
- ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
+ ActivityTaskManagerService.SleepTokenAcquirer acquirer) {
mService = service;
mDisplayId = displayId;
mSleepTokenAcquirer = acquirer;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ccee458..117b22a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -225,7 +225,7 @@
private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
/** The token acquirer to put root tasks on the displays to sleep */
- final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+ final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer;
/**
* The modes which affect which tasks are returned when calling
@@ -470,7 +470,7 @@
mService = service.mAtmService;
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
- mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+ mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG);
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -2496,7 +2496,7 @@
// starts. Instead, we expect home activities to be launched when the system is ready
// (ActivityManagerService#systemReady).
if (mService.isBooted() || mService.isBooting()) {
- startSystemDecorations(display.mDisplayContent);
+ startSystemDecorations(display);
}
// Drop any cached DisplayInfos associated with this display id - the values are now
// out of date given this display added event.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bfb1a8e..5af1c8e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2152,10 +2152,7 @@
}
private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
- if (mWmService.mDisableTransitionAnimation
- || !isVisible()
- || getSurfaceControl() == null
- || !isLeafTask()) {
+ if (!isLeafTask() || !canStartChangeTransition()) {
return false;
}
// Only do an animation into and out-of freeform mode for now. Other mode
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7268610..796a90a 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -909,18 +909,24 @@
mBackgroundColor = colorInt;
Color color = Color.valueOf(colorInt);
mColorLayerCounter++;
- getPendingTransaction()
- .setColor(mSurfaceControl, new float[]{color.red(), color.green(), color.blue()});
- scheduleAnimation();
+ // Only apply the background color if the TDA is actually attached and has a valid surface
+ // to set the background color on. We still want to keep track of the background color state
+ // even if we are not showing it for when/if the TDA is reattached and gets a valid surface
+ if (mSurfaceControl != null) {
+ getPendingTransaction()
+ .setColor(mSurfaceControl,
+ new float[]{color.red(), color.green(), color.blue()});
+ scheduleAnimation();
+ }
}
void clearBackgroundColor() {
mColorLayerCounter--;
// Only clear the color layer if we have received the same amounts of clear as set
- // requests.
- if (mColorLayerCounter == 0) {
+ // requests and TDA has a non null surface control (i.e. is attached)
+ if (mColorLayerCounter == 0 && mSurfaceControl != null) {
getPendingTransaction().unsetColor(mSurfaceControl);
scheduleAnimation();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 929f221..e497b53 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2121,13 +2121,7 @@
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
private boolean shouldStartChangeTransition(Rect startBounds) {
- if (mWmService.mDisableTransitionAnimation
- || mDisplayContent == null
- || mTaskFragmentOrganizer == null
- || getSurfaceControl() == null
- // The change transition will be covered by display.
- || mDisplayContent.inTransition()
- || !isVisible()) {
+ if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9865506..7e84dbb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2543,6 +2543,13 @@
mSurfaceFreezer.unfreeze(getPendingTransaction());
}
+ /** Whether we can start change transition with this window and current display status. */
+ boolean canStartChangeTransition() {
+ return !mWmService.mDisableTransitionAnimation && mDisplayContent != null
+ && getSurfaceControl() != null && !mDisplayContent.inTransition()
+ && isVisible() && isVisibleRequested() && okToAnimate();
+ }
+
/**
* Initializes a change transition. See {@link SurfaceFreezer} for more information.
*
@@ -2891,12 +2898,7 @@
}
boolean okToAnimate() {
- return okToAnimate(false /* ignoreFrozen */);
- }
-
- boolean okToAnimate(boolean ignoreFrozen) {
- final DisplayContent dc = getDisplayContent();
- return dc != null && dc.okToAnimate(ignoreFrozen);
+ return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */);
}
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index bc53041..cc52713 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Display.isSuspendedState;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.window.WindowProviderService.isWindowProviderService;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
@@ -45,7 +47,7 @@
*
* <ul>
* <li>When a {@link WindowContext} is created, it registers the listener via
- * {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)}
+ * {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)}
* automatically.</li>
* <li>When the {@link WindowContext} adds the first window to the screen via
* {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)},
@@ -53,7 +55,7 @@
* to corresponding {@link WindowToken} via this controller.</li>
* <li>When the {@link WindowContext} is GCed, it unregisters the previously
* registered listener via
- * {@link WindowManagerService#unregisterWindowContextListener(IBinder)}.
+ * {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}.
* {@link WindowManagerService} is also responsible for removing the
* {@link WindowContext} created {@link WindowToken}.</li>
* </ul>
@@ -68,7 +70,7 @@
/**
* Registers the listener to a {@code container} which is associated with
- * a {@code clientToken}, which is a {@link android.app.WindowContext} representation. If the
+ * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the
* listener associated with {@code clientToken} hasn't been initialized yet, create one
* {@link WindowContextListenerImpl}. Otherwise, the listener associated with
* {@code clientToken} switches to listen to the {@code container}.
@@ -80,7 +82,7 @@
* @param options a bundle used to pass window-related options.
*/
void registerWindowContainerListener(@NonNull IBinder clientToken,
- @NonNull WindowContainer container, int ownerUid, @WindowType int type,
+ @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type,
@Nullable Bundle options) {
WindowContextListenerImpl listener = mListeners.get(clientToken);
if (listener == null) {
@@ -103,6 +105,16 @@
listener.unregister();
}
+ void dispatchPendingConfigurationIfNeeded(int displayId) {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final WindowContextListenerImpl listener = mListeners.valueAt(i);
+ if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId
+ && listener.mHasPendingConfiguration) {
+ listener.reportConfigToWindowTokenClient();
+ }
+ }
+ }
+
/**
* Verifies if the caller is allowed to do the operation to the listener specified by
* {@code clientToken}.
@@ -138,7 +150,7 @@
return listener != null ? listener.mOptions : null;
}
- @Nullable WindowContainer getContainer(IBinder clientToken) {
+ @Nullable WindowContainer<?> getContainer(IBinder clientToken) {
final WindowContextListenerImpl listener = mListeners.get(clientToken);
return listener != null ? listener.mContainer : null;
}
@@ -163,7 +175,7 @@
class WindowContextListenerImpl implements WindowContainerListener {
@NonNull private final IBinder mClientToken;
private final int mOwnerUid;
- @NonNull private WindowContainer mContainer;
+ @NonNull private WindowContainer<?> mContainer;
/**
* The options from {@link Context#createWindowContext(int, Bundle)}.
* <p>It can be used for choosing the {@link DisplayArea} where the window context
@@ -177,7 +189,9 @@
private int mLastReportedDisplay = INVALID_DISPLAY;
private Configuration mLastReportedConfig;
- private WindowContextListenerImpl(IBinder clientToken, WindowContainer container,
+ private boolean mHasPendingConfiguration;
+
+ private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container,
int ownerUid, @WindowType int type, @Nullable Bundle options) {
mClientToken = clientToken;
mContainer = Objects.requireNonNull(container);
@@ -197,11 +211,11 @@
/** TEST ONLY: returns the {@link WindowContainer} of the listener */
@VisibleForTesting
- WindowContainer getWindowContainer() {
+ WindowContainer<?> getWindowContainer() {
return mContainer;
}
- private void updateContainer(@NonNull WindowContainer newContainer) {
+ private void updateContainer(@NonNull WindowContainer<?> newContainer) {
Objects.requireNonNull(newContainer);
if (mContainer.equals(newContainer)) {
@@ -246,12 +260,20 @@
if (mDeathRecipient == null) {
throw new IllegalStateException("Invalid client token: " + mClientToken);
}
-
- if (mLastReportedConfig == null) {
- mLastReportedConfig = new Configuration();
+ // If the display of window context associated window container is suspended, don't
+ // report the configuration update. Note that we still dispatch the configuration update
+ // to WindowProviderService to make it compatible with Service#onConfigurationChanged.
+ // Service always receives #onConfigurationChanged callback regardless of display state.
+ if (!isWindowProviderService(mOptions)
+ && isSuspendedState(mContainer.getDisplayContent().getDisplayInfo().state)) {
+ mHasPendingConfiguration = true;
+ return;
}
final Configuration config = mContainer.getConfiguration();
final int displayId = mContainer.getDisplayContent().getDisplayId();
+ if (mLastReportedConfig == null) {
+ mLastReportedConfig = new Configuration();
+ }
if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) {
// No changes since last reported time.
return;
@@ -266,6 +288,7 @@
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client.");
}
+ mHasPendingConfiguration = false;
}
@Override
@@ -283,7 +306,7 @@
// If we cannot obtain the DisplayContent, the DisplayContent may also be removed.
// We should proceed the removal process.
if (dc != null) {
- final DisplayArea da = dc.findAreaForToken(windowToken);
+ final DisplayArea<?> da = dc.findAreaForToken(windowToken);
updateContainer(da);
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4720d7c..23f9c58 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2700,6 +2700,9 @@
@Override
public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
type, int displayId, Bundle options) {
+ if (clientToken == null) {
+ throw new IllegalArgumentException("clientToken must not be null!");
+ }
final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
"attachWindowContextToDisplayArea", false /* printLog */);
final int callingUid = Binder.getCallingUid();
@@ -2790,6 +2793,39 @@
}
}
+ @Override
+ public Configuration attachToDisplayContent(IBinder clientToken, int displayId) {
+ if (clientToken == null) {
+ throw new IllegalArgumentException("clientToken must not be null!");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because
+ // this method may be called in DisplayPolicy's constructor and may cause
+ // infinite loop. In this scenario, we early return here and switch to do the
+ // registration in DisplayContent#onParentChanged at DisplayContent initialization.
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ if (Binder.getCallingPid() != myPid()) {
+ throw new WindowManager.InvalidDisplayException("attachToDisplayContent: "
+ + "trying to attach to a non-existing display:" + displayId);
+ }
+ // Early return if this method is invoked from system process.
+ // See above comments for more detail.
+ return null;
+ }
+
+ mWindowContextListenerController.registerWindowContainerListener(clientToken, dc,
+ callingUid, INVALID_WINDOW_TYPE, null /* options */);
+ return dc.getConfiguration();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
/** Returns {@code true} if this binder is a registered window token. */
@Override
public boolean isWindowToken(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 44edaa2..bae5465 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3409,7 +3409,10 @@
}
// Exclude toast because legacy apps may show toast window by themselves, so the misused
// apps won't always be considered as foreground state.
- if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) {
+ // Exclude private presentations as they can only be shown on private virtual displays and
+ // shouldn't be the cause of an app be considered foreground.
+ if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST
+ && mAttrs.type != TYPE_PRIVATE_PRESENTATION) {
mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 6204824..ca834c5 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -48,12 +48,12 @@
class WindowTracing {
/**
- * Maximum buffer size, currently defined as 512 KB
+ * Maximum buffer size, currently defined as 5 MB
* Size was experimentally defined to fit between 100 to 150 elements.
*/
- private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024;
- private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024;
- private static final int BUFFER_CAPACITY_ALL = 4096 * 1024;
+ private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
+ private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
+ private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
static final String WINSCOPE_EXT = ".winscope";
private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
private static final String TAG = "WindowTracing";
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index e4c8871..2ccef9a 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -56,13 +56,13 @@
#include "gnss/GnssBatching.h"
#include "gnss/GnssConfiguration.h"
#include "gnss/GnssMeasurement.h"
+#include "gnss/GnssNavigationMessage.h"
#include "gnss/Utils.h"
#include "hardware_legacy/power.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
-static jclass class_gnssNavigationMessage;
static jclass class_gnssPowerStats;
static jmethodID method_reportLocation;
@@ -85,7 +85,6 @@
static jmethodID method_reportGeofenceRemoveStatus;
static jmethodID method_reportGeofencePauseStatus;
static jmethodID method_reportGeofenceResumeStatus;
-static jmethodID method_reportNavigationMessages;
static jmethodID method_reportGnssServiceDied;
static jmethodID method_reportGnssPowerStats;
static jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
@@ -115,7 +114,6 @@
static jmethodID method_correctionPlaneAzimDeg;
static jmethodID method_reportNfwNotification;
static jmethodID method_isInEmergencySession;
-static jmethodID method_gnssNavigationMessageCtor;
static jmethodID method_gnssPowerStatsCtor;
static jmethodID method_setSubHalPowerIndicationCapabilities;
@@ -233,7 +231,6 @@
sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
-sp<IGnssNavigationMessage> gnssNavigationMessageIface = nullptr;
sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr;
sp<IMeasurementCorrections_V1_0> gnssCorrectionsIface_V1_0 = nullptr;
sp<IMeasurementCorrections_V1_1> gnssCorrectionsIface_V1_1 = nullptr;
@@ -242,6 +239,7 @@
std::unique_ptr<GnssConfigurationInterface> gnssConfigurationIface = nullptr;
std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr;
+std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
#define WAKE_LOCK_NAME "GPS"
@@ -910,50 +908,6 @@
}
/*
- * GnssNavigationMessageCallback interface implements the callback methods
- * required by the IGnssNavigationMessage interface.
- */
-struct GnssNavigationMessageCallback : public IGnssNavigationMessageCallback {
- /*
- * Methods from ::android::hardware::gps::V1_0::IGnssNavigationMessageCallback
- * follow.
- */
- Return<void> gnssNavigationMessageCb(
- const IGnssNavigationMessageCallback::GnssNavigationMessage& message) override;
-};
-
-Return<void> GnssNavigationMessageCallback::gnssNavigationMessageCb(
- const IGnssNavigationMessageCallback::GnssNavigationMessage& message) {
- JNIEnv* env = getJniEnv();
-
- size_t dataLength = message.data.size();
-
- std::vector<uint8_t> navigationData = message.data;
- uint8_t* data = &(navigationData[0]);
- if (dataLength == 0 || data == nullptr) {
- ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data,
- dataLength);
- return Void();
- }
-
- JavaObject object(env, class_gnssNavigationMessage, method_gnssNavigationMessageCtor);
- SET(Type, static_cast<int32_t>(message.type));
- SET(Svid, static_cast<int32_t>(message.svid));
- SET(MessageId, static_cast<int32_t>(message.messageId));
- SET(SubmessageId, static_cast<int32_t>(message.submessageId));
- object.callSetter("setData", data, dataLength);
- SET(Status, static_cast<int32_t>(message.status));
-
- jobject navigationMessage = object.get();
- env->CallVoidMethod(mCallbacksObj,
- method_reportNavigationMessages,
- navigationMessage);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- env->DeleteLocalRef(navigationMessage);
- return Void();
-}
-
-/*
* MeasurementCorrectionsCallback implements callback methods of interface
* IMeasurementCorrectionsCallback.hal.
*/
@@ -1264,10 +1218,6 @@
"(II)V");
method_reportGeofencePauseStatus = env->GetMethodID(clazz, "reportGeofencePauseStatus",
"(II)V");
- method_reportNavigationMessages = env->GetMethodID(
- clazz,
- "reportNavigationMessage",
- "(Landroid/location/GnssNavigationMessage;)V");
method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
"(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
@@ -1337,14 +1287,11 @@
class_gnssPowerStats = (jclass)env->NewGlobalRef(gnssPowerStatsClass);
method_gnssPowerStatsCtor = env->GetMethodID(class_gnssPowerStats, "<init>", "(IJDDDDDD[D)V");
- jclass gnssNavigationMessageClass = env->FindClass("android/location/GnssNavigationMessage");
- class_gnssNavigationMessage = (jclass) env->NewGlobalRef(gnssNavigationMessageClass);
- method_gnssNavigationMessageCtor = env->GetMethodID(class_gnssNavigationMessage, "<init>", "()V");
-
gnss::GnssAntennaInfo_class_init_once(env, clazz);
gnss::GnssBatching_class_init_once(env, clazz);
gnss::GnssConfiguration_class_init_once(env);
gnss::GnssMeasurement_class_init_once(env, clazz);
+ gnss::GnssNavigationMessage_class_init_once(env, clazz);
gnss::Utils_class_init_once(env);
}
@@ -1431,12 +1378,20 @@
}
}
- if (gnssHal != nullptr) {
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ sp<hardware::gnss::IGnssNavigationMessageInterface> gnssNavigationMessage;
+ auto status = gnssHalAidl->getExtensionGnssNavigationMessage(&gnssNavigationMessage);
+ if (checkAidlStatus(status,
+ "Unable to get a handle to GnssNavigationMessage AIDL interface.")) {
+ gnssNavigationMessageIface =
+ std::make_unique<gnss::GnssNavigationMessageAidl>(gnssNavigationMessage);
+ }
+ } else if (gnssHal != nullptr) {
auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
- if (!gnssNavigationMessage.isOk()) {
- ALOGD("Unable to get a handle to GnssNavigationMessage");
- } else {
- gnssNavigationMessageIface = gnssNavigationMessage;
+ if (checkHidlReturn(gnssNavigationMessage,
+ "Unable to get a handle to GnssNavigationMessage interface.")) {
+ gnssNavigationMessageIface =
+ std::make_unique<gnss::GnssNavigationMessageHidl>(gnssNavigationMessage);
}
}
@@ -2624,20 +2579,8 @@
return JNI_FALSE;
}
- sp<IGnssNavigationMessageCallback> gnssNavigationMessageCbIface =
- new GnssNavigationMessageCallback();
- auto result = gnssNavigationMessageIface->setCallback(gnssNavigationMessageCbIface);
- if (!checkHidlReturn(result, "IGnssNavigationMessage setCallback() failed.")) {
- return JNI_FALSE;
- }
-
- IGnssNavigationMessage::GnssNavigationMessageStatus initRet = result;
- if (initRet != IGnssNavigationMessage::GnssNavigationMessageStatus::SUCCESS) {
- ALOGE("An error has been found in %s: %d", __FUNCTION__, static_cast<int32_t>(initRet));
- return JNI_FALSE;
- }
-
- return JNI_TRUE;
+ return gnssNavigationMessageIface->setCallback(
+ std::make_unique<gnss::GnssNavigationMessageCallback>());
}
static jboolean android_location_gnss_hal_GnssNative_stop_navigation_message_collection(JNIEnv* env,
@@ -2646,9 +2589,7 @@
ALOGE("%s: IGnssNavigationMessage interface not available.", __func__);
return JNI_FALSE;
}
-
- auto result = gnssNavigationMessageIface->close();
- return checkHidlReturn(result, "IGnssNavigationMessage close() failed.");
+ return gnssNavigationMessageIface->close();
}
static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*,
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index 090166a..6c6b304 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -29,6 +29,8 @@
"GnssConfiguration.cpp",
"GnssMeasurement.cpp",
"GnssMeasurementCallback.cpp",
+ "GnssNavigationMessage.cpp",
+ "GnssNavigationMessageCallback.cpp",
"Utils.cpp",
],
}
diff --git a/services/core/jni/gnss/GnssNavigationMessage.cpp b/services/core/jni/gnss/GnssNavigationMessage.cpp
new file mode 100644
index 0000000..75aee74
--- /dev/null
+++ b/services/core/jni/gnss/GnssNavigationMessage.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssNavigationMessageJni"
+
+#include "GnssNavigationMessage.h"
+
+#include "Utils.h"
+
+namespace android::gnss {
+
+using hardware::gnss::IGnssNavigationMessageInterface;
+using IGnssNavigationMessageHidl = hardware::gnss::V1_0::IGnssNavigationMessage;
+
+// Implementation of GnssNavigationMessage (AIDL HAL)
+
+GnssNavigationMessageAidl::GnssNavigationMessageAidl(
+ const sp<IGnssNavigationMessageInterface>& iGnssNavigationMessage)
+ : mIGnssNavigationMessage(iGnssNavigationMessage) {
+ assert(mIGnssNavigationMessage != nullptr);
+}
+
+jboolean GnssNavigationMessageAidl::setCallback(
+ const std::unique_ptr<GnssNavigationMessageCallback>& callback) {
+ auto status = mIGnssNavigationMessage->setCallback(callback->getAidl());
+ return checkAidlStatus(status, "IGnssNavigationMessageAidl setCallback() failed.");
+}
+
+jboolean GnssNavigationMessageAidl::close() {
+ auto status = mIGnssNavigationMessage->close();
+ return checkAidlStatus(status, "IGnssNavigationMessageAidl close() failed");
+}
+
+// Implementation of GnssNavigationMessageHidl
+
+GnssNavigationMessageHidl::GnssNavigationMessageHidl(
+ const sp<IGnssNavigationMessageHidl>& iGnssNavigationMessage)
+ : mIGnssNavigationMessageHidl(iGnssNavigationMessage) {
+ assert(mIGnssNavigationMessageHidl != nullptr);
+}
+
+jboolean GnssNavigationMessageHidl::setCallback(
+ const std::unique_ptr<GnssNavigationMessageCallback>& callback) {
+ auto result = mIGnssNavigationMessageHidl->setCallback(callback->getHidl());
+
+ IGnssNavigationMessageHidl::GnssNavigationMessageStatus initRet = result;
+ if (initRet != IGnssNavigationMessageHidl::GnssNavigationMessageStatus::SUCCESS) {
+ ALOGE("An error has been found in %s: %d", __FUNCTION__, static_cast<int32_t>(initRet));
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+jboolean GnssNavigationMessageHidl::close() {
+ auto result = mIGnssNavigationMessageHidl->close();
+ return checkHidlReturn(result, "IGnssNavigationMessage close() failed.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssNavigationMessage.h b/services/core/jni/gnss/GnssNavigationMessage.h
new file mode 100644
index 0000000..e3a1e4a
--- /dev/null
+++ b/services/core/jni/gnss/GnssNavigationMessage.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H
+#define _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssNavigationMessage.h>
+#include <android/hardware/gnss/BnGnssNavigationMessageInterface.h>
+#include <log/log.h>
+
+#include "GnssNavigationMessageCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class GnssNavigationMessageInterface {
+public:
+ virtual ~GnssNavigationMessageInterface() {}
+ virtual jboolean setCallback(
+ const std::unique_ptr<GnssNavigationMessageCallback>& callback) = 0;
+ virtual jboolean close() = 0;
+};
+
+class GnssNavigationMessageAidl : public GnssNavigationMessageInterface {
+public:
+ GnssNavigationMessageAidl(const sp<android::hardware::gnss::IGnssNavigationMessageInterface>&
+ iGnssNavigationMessage);
+ jboolean setCallback(const std::unique_ptr<GnssNavigationMessageCallback>& callback) override;
+ jboolean close() override;
+
+private:
+ const sp<android::hardware::gnss::IGnssNavigationMessageInterface> mIGnssNavigationMessage;
+};
+
+class GnssNavigationMessageHidl : public GnssNavigationMessageInterface {
+public:
+ GnssNavigationMessageHidl(const sp<android::hardware::gnss::V1_0::IGnssNavigationMessage>&
+ iGnssNavigationMessage);
+ jboolean setCallback(const std::unique_ptr<GnssNavigationMessageCallback>& callback) override;
+ jboolean close() override;
+
+private:
+ const sp<android::hardware::gnss::V1_0::IGnssNavigationMessage> mIGnssNavigationMessageHidl;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H
diff --git a/services/core/jni/gnss/GnssNavigationMessageCallback.cpp b/services/core/jni/gnss/GnssNavigationMessageCallback.cpp
new file mode 100644
index 0000000..1779c95
--- /dev/null
+++ b/services/core/jni/gnss/GnssNavigationMessageCallback.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GnssNavMsgCbJni"
+
+#include "GnssNavigationMessageCallback.h"
+
+namespace android::gnss {
+
+namespace {
+
+jclass class_gnssNavigationMessage;
+jmethodID method_reportNavigationMessages;
+jmethodID method_gnssNavigationMessageCtor;
+
+} // anonymous namespace
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+
+using GnssNavigationMessageAidl =
+ android::hardware::gnss::IGnssNavigationMessageCallback::GnssNavigationMessage;
+using GnssNavigationMessageHidl =
+ android::hardware::gnss::V1_0::IGnssNavigationMessageCallback::GnssNavigationMessage;
+
+void GnssNavigationMessage_class_init_once(JNIEnv* env, jclass clazz) {
+ method_reportNavigationMessages =
+ env->GetMethodID(clazz, "reportNavigationMessage",
+ "(Landroid/location/GnssNavigationMessage;)V");
+
+ jclass gnssNavigationMessageClass = env->FindClass("android/location/GnssNavigationMessage");
+ class_gnssNavigationMessage = (jclass)env->NewGlobalRef(gnssNavigationMessageClass);
+ method_gnssNavigationMessageCtor =
+ env->GetMethodID(class_gnssNavigationMessage, "<init>", "()V");
+}
+
+Status GnssNavigationMessageCallbackAidl::gnssNavigationMessageCb(
+ const GnssNavigationMessageAidl& message) {
+ GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(message);
+ return Status::ok();
+}
+
+Return<void> GnssNavigationMessageCallbackHidl::gnssNavigationMessageCb(
+ const GnssNavigationMessageHidl& message) {
+ GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(message);
+ return Void();
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssNavigationMessageCallback.h b/services/core/jni/gnss/GnssNavigationMessageCallback.h
new file mode 100644
index 0000000..fe76fc7
--- /dev/null
+++ b/services/core/jni/gnss/GnssNavigationMessageCallback.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssNavigationMessage.h>
+#include <android/hardware/gnss/BnGnssNavigationMessageCallback.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+
+extern jclass class_gnssNavigationMessage;
+extern jmethodID method_reportNavigationMessages;
+extern jmethodID method_gnssNavigationMessageCtor;
+
+} // anonymous namespace
+
+void GnssNavigationMessage_class_init_once(JNIEnv* env, jclass clazz);
+
+class GnssNavigationMessageCallbackAidl : public hardware::gnss::BnGnssNavigationMessageCallback {
+public:
+ GnssNavigationMessageCallbackAidl() {}
+ android::binder::Status gnssNavigationMessageCb(
+ const hardware::gnss::IGnssNavigationMessageCallback::GnssNavigationMessage& message)
+ override;
+};
+
+class GnssNavigationMessageCallbackHidl
+ : public hardware::gnss::V1_0::IGnssNavigationMessageCallback {
+public:
+ GnssNavigationMessageCallbackHidl() {}
+
+ hardware::Return<void> gnssNavigationMessageCb(
+ const hardware::gnss::V1_0::IGnssNavigationMessageCallback::GnssNavigationMessage&
+ message) override;
+};
+
+class GnssNavigationMessageCallback {
+public:
+ GnssNavigationMessageCallback() {}
+ sp<GnssNavigationMessageCallbackAidl> getAidl() {
+ if (callbackAidl == nullptr) {
+ callbackAidl = sp<GnssNavigationMessageCallbackAidl>::make();
+ }
+ return callbackAidl;
+ }
+
+ sp<GnssNavigationMessageCallbackHidl> getHidl() {
+ if (callbackHidl == nullptr) {
+ callbackHidl = sp<GnssNavigationMessageCallbackHidl>::make();
+ }
+ return callbackHidl;
+ }
+
+private:
+ sp<GnssNavigationMessageCallbackAidl> callbackAidl;
+ sp<GnssNavigationMessageCallbackHidl> callbackHidl;
+};
+
+struct GnssNavigationMessageCallbackUtil {
+ template <class T>
+ static void gnssNavigationMessageCbImpl(const T& message);
+
+private:
+ GnssNavigationMessageCallbackUtil() = delete;
+};
+
+template <class T>
+void GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(const T& message) {
+ JNIEnv* env = getJniEnv();
+
+ size_t dataLength = message.data.size();
+
+ std::vector<uint8_t> navigationData = message.data;
+ uint8_t* data = &(navigationData[0]);
+ if (dataLength == 0 || data == nullptr) {
+ ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength);
+ return;
+ }
+
+ JavaObject object(env, class_gnssNavigationMessage, method_gnssNavigationMessageCtor);
+ SET(Type, static_cast<int32_t>(message.type));
+ SET(Svid, static_cast<int32_t>(message.svid));
+ SET(MessageId, static_cast<int32_t>(message.messageId));
+ SET(SubmessageId, static_cast<int32_t>(message.submessageId));
+ object.callSetter("setData", data, dataLength);
+ SET(Status, static_cast<int32_t>(message.status));
+
+ jobject navigationMessage = object.get();
+ env->CallVoidMethod(mCallbacksObj, method_reportNavigationMessages, navigationMessage);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ env->DeleteLocalRef(navigationMessage);
+ return;
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index efac4d5..be74ed8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10399,14 +10399,23 @@
}
@Override
- public List<String> getPermittedInputMethodsForCurrentUser() {
+ public @Nullable List<String> getPermittedInputMethodsAsUser(@UserIdInt int userId) {
final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
Preconditions.checkCallAuthorization(canManageUsers(caller));
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return getPermittedInputMethodsUnchecked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+ private @Nullable List<String> getPermittedInputMethodsUnchecked(@UserIdInt int userId) {
synchronized (getLockObject()) {
List<String> result = null;
// Only device or profile owners can have permitted lists set.
- List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(caller.getUserId());
+ List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(userId);
for (ActiveAdmin admin: admins) {
List<String> fromAdmin = admin.permittedInputMethods;
if (fromAdmin != null) {
@@ -10421,7 +10430,7 @@
// If we have a permitted list add all system input methods.
if (result != null) {
List<InputMethodInfo> imes = InputMethodManagerInternal
- .get().getInputMethodListAsUser(caller.getUserId());
+ .get().getInputMethodListAsUser(userId);
if (imes != null) {
for (InputMethodInfo ime : imes) {
ServiceInfo serviceInfo = ime.getServiceInfo();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index b17ff53b..95912b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -43,6 +43,7 @@
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
+import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
@@ -168,12 +169,15 @@
}
}
- private JobStatus createJobStatus(String testTag, int jobId) {
- JobInfo jobInfo = new JobInfo.Builder(jobId,
+ private JobInfo createJobInfo(int jobId) {
+ return new JobInfo.Builder(jobId,
new ComponentName(mContext, "TestPrefetchJobService"))
.setPrefetch(true)
.build();
- return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
+ }
+
+ private JobStatus createJobStatus(String testTag, int jobId) {
+ return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, createJobInfo(jobId));
}
private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
@@ -331,6 +335,32 @@
}
@Test
+ public void testConstraintSatisfiedWhenWidget() {
+ final JobStatus jobNonWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 1);
+ final JobStatus jobWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 2);
+
+ when(mUsageStatsManagerInternal
+ .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
+ .thenReturn(sSystemClock.millis() + 100 * HOUR_IN_MILLIS);
+
+ final AppWidgetManager appWidgetManager = mock(AppWidgetManager.class);
+ when(mContext.getSystemService(AppWidgetManager.class)).thenReturn(appWidgetManager);
+ mPrefetchController.onSystemServicesReady();
+
+ when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
+ .thenReturn(false);
+ trackJobs(jobNonWidget);
+ verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
+ .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
+ assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+
+ when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
+ .thenReturn(true);
+ trackJobs(jobWidget);
+ assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ }
+
+ @Test
public void testEstimatedLaunchTimeChangedToLate() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
when(mUsageStatsManagerInternal
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 2fd2816..205c3da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -316,6 +317,34 @@
assertConfigEquals(config, result);
}
+ @Test
+ public void setMagnificationConfig_controllingModeChangeAndAnimating_transitionConfigMode() {
+ final int currentActivatedMode = WINDOW_MODE;
+ final int targetMode = FULLSCREEN_MODE;
+ final MagnificationConfig oldConfig = new MagnificationConfig.Builder()
+ .setMode(currentActivatedMode)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, oldConfig);
+ final MagnificationConfig newConfig = new MagnificationConfig.Builder()
+ .setMode(targetMode)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X + 10)
+ .setCenterY(TEST_CENTER_Y + 10).build();
+
+ // Has magnification animation running
+ when(mMockMagnificationController.hasDisableMagnificationCallback(TEST_DISPLAY)).thenReturn(
+ true);
+ setMagnificationActivated(TEST_DISPLAY, newConfig);
+
+ final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
+ TEST_DISPLAY);
+ verify(mMockMagnificationController).transitionMagnificationConfigMode(eq(TEST_DISPLAY),
+ eq(newConfig), anyBoolean());
+ assertConfigEquals(newConfig, result);
+ }
+
private void setMagnificationActivated(int displayId, int configMode) {
setMagnificationActivated(displayId,
new MagnificationConfig.Builder().setMode(configMode).build());
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 8a521d8..0863f9e 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
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.accessibilityservice.MagnificationConfig;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -229,7 +230,7 @@
}
@Test
- public void transitionToFullScreen_centerNotInTheBounds_magnifyTheCenterOfMagnificationBounds()
+ public void transitionToFullScreen_centerNotInTheBounds_magnifyBoundsCenter()
throws RemoteException {
final Rect magnificationBounds = MAGNIFICATION_REGION.getBounds();
final PointF magnifiedCenter = new PointF(magnificationBounds.right + 100,
@@ -289,6 +290,73 @@
}
@Test
+ public void configTransitionToWindowMode_fullScreenMagnifying_disableFullScreenAndEnableWindow()
+ throws RemoteException {
+ activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ obtainMagnificationConfig(MODE_WINDOW),
+ false);
+
+ verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false));
+ mMockConnection.invokeCallbacks();
+ assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0);
+ assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0);
+ }
+
+ @Test
+ public void configTransitionToFullScreen_windowMagnifying_disableWindowAndEnableFullScreen()
+ throws RemoteException {
+ final boolean animate = true;
+ activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ obtainMagnificationConfig(MODE_FULLSCREEN),
+ animate);
+ mMockConnection.invokeCallbacks();
+
+ assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+ verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY,
+ DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
+ animate, MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+
+ @Test
+ public void configTransitionToFullScreen_userSettingsDisablingFullScreen_enableFullScreen()
+ throws RemoteException {
+ activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ // User-setting mode
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_WINDOW, mTransitionCallBack);
+
+ // Config-setting mode
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ obtainMagnificationConfig(MODE_FULLSCREEN),
+ true);
+
+ assertEquals(DEFAULT_SCALE, mScreenMagnificationController.getScale(TEST_DISPLAY), 0);
+ assertEquals(MAGNIFIED_CENTER_X, mScreenMagnificationController.getCenterX(TEST_DISPLAY),
+ 0);
+ assertEquals(MAGNIFIED_CENTER_Y, mScreenMagnificationController.getCenterY(TEST_DISPLAY),
+ 0);
+ }
+
+ @Test
+ public void interruptDuringTransitionToWindow_disablingFullScreen_discardPreviousTransition()
+ throws RemoteException {
+ activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ // User-setting mode
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_WINDOW, mTransitionCallBack);
+
+ // Config-setting mode
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ obtainMagnificationConfig(MODE_FULLSCREEN),
+ true);
+
+ verify(mTransitionCallBack, never()).onResult(TEST_DISPLAY, true);
+ }
+
+ @Test
public void onDisplayRemoved_notifyAllModules() {
mMagnificationController.onDisplayRemoved(TEST_DISPLAY);
@@ -402,6 +470,16 @@
}
@Test
+ public void onFullScreenMagnificationActivationState_windowActivated_disableMagnification()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+
+ mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+
+ verify(mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), eq(false));
+ }
+
+ @Test
public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton()
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
@@ -609,6 +687,10 @@
private void setMagnificationEnabled(int mode, float centerX, float centerY)
throws RemoteException {
setMagnificationModeSettings(mode);
+ activateMagnifier(mode, centerX, centerY);
+ }
+
+ private void activateMagnifier(int mode, float centerX, float centerY) throws RemoteException {
mScreenMagnificationControllerStubber.resetAndStubMethods();
final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled(
TEST_DISPLAY);
@@ -631,6 +713,11 @@
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, mode, CURRENT_USER_ID);
}
+ private MagnificationConfig obtainMagnificationConfig(int mode) {
+ return new MagnificationConfig.Builder().setMode(mode).setScale(DEFAULT_SCALE).setCenterX(
+ MAGNIFIED_CENTER_X).setCenterY(MAGNIFIED_CENTER_Y).build();
+ }
+
/**
* Stubs public methods to simulate the real beahviours.
*/
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java
deleted file mode 100644
index bae11eb..0000000
--- a/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.transport;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.backup.IBackupTransport;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class DelegatingTransportTest {
- @Mock private IBackupTransport mBackupTransport;
- @Mock private PackageInfo mPackageInfo;
- @Mock private ParcelFileDescriptor mFd;
-
- private final String mPackageName = "testpackage";
- private final RestoreSet mRestoreSet = new RestoreSet();
- private final int mFlags = 1;
- private final long mRestoreToken = 10;
- private final long mSize = 100;
- private final int mNumBytes = 1000;
- private DelegatingTransport mDelegatingTransport;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mDelegatingTransport = new DelegatingTransport() {
- @Override
- protected IBackupTransport getDelegate() {
- return mBackupTransport;
- }
- };
- }
-
- @Test
- public void testName() throws RemoteException {
- String exp = "dummy";
- when(mBackupTransport.name()).thenReturn(exp);
-
- String ret = mDelegatingTransport.name();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).name();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testConfigurationIntent() throws RemoteException {
- Intent exp = new Intent("dummy");
- when(mBackupTransport.configurationIntent()).thenReturn(exp);
-
- Intent ret = mDelegatingTransport.configurationIntent();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).configurationIntent();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testCurrentDestinationString() throws RemoteException {
- String exp = "dummy";
- when(mBackupTransport.currentDestinationString()).thenReturn(exp);
-
- String ret = mDelegatingTransport.currentDestinationString();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).currentDestinationString();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testDataManagementIntent() throws RemoteException {
- Intent exp = new Intent("dummy");
- when(mBackupTransport.dataManagementIntent()).thenReturn(exp);
-
- Intent ret = mDelegatingTransport.dataManagementIntent();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).dataManagementIntent();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testDataManagementIntentLabel() throws RemoteException {
- String exp = "dummy";
- when(mBackupTransport.dataManagementIntentLabel()).thenReturn(exp);
-
- CharSequence ret = mDelegatingTransport.dataManagementIntentLabel();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).dataManagementIntentLabel();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testTransportDirName() throws RemoteException {
- String exp = "dummy";
- when(mBackupTransport.transportDirName()).thenReturn(exp);
-
- String ret = mDelegatingTransport.transportDirName();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).transportDirName();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testRequestBackupTime() throws RemoteException {
- long exp = 1000L;
- when(mBackupTransport.requestBackupTime()).thenReturn(exp);
-
- long ret = mDelegatingTransport.requestBackupTime();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).requestBackupTime();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testInitializeDevice() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.initializeDevice()).thenReturn(exp);
-
- long ret = mDelegatingTransport.initializeDevice();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).initializeDevice();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testPerformBackup() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.performBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
-
- int ret = mDelegatingTransport.performBackup(mPackageInfo, mFd, mFlags);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).performBackup(mPackageInfo, mFd, mFlags);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testClearBackupData() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.clearBackupData(mPackageInfo)).thenReturn(exp);
-
- int ret = mDelegatingTransport.clearBackupData(mPackageInfo);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).clearBackupData(mPackageInfo);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testFinishBackup() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.finishBackup()).thenReturn(exp);
-
- int ret = mDelegatingTransport.finishBackup();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).finishBackup();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetAvailableRestoreSets() throws RemoteException {
- RestoreSet[] exp = new RestoreSet[] {mRestoreSet};
- when(mBackupTransport.getAvailableRestoreSets()).thenReturn(exp);
-
- RestoreSet[] ret = mDelegatingTransport.getAvailableRestoreSets();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getAvailableRestoreSets();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetCurrentRestoreSet() throws RemoteException {
- long exp = 1000;
- when(mBackupTransport.getCurrentRestoreSet()).thenReturn(exp);
-
- long ret = mDelegatingTransport.getCurrentRestoreSet();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getCurrentRestoreSet();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testStartRestore() throws RemoteException {
- int exp = 1000;
- PackageInfo[] packageInfos = {mPackageInfo};
- when(mBackupTransport.startRestore(mRestoreToken, packageInfos)).thenReturn(exp);
-
- int ret = mDelegatingTransport.startRestore(mRestoreToken, packageInfos);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).startRestore(mRestoreToken, packageInfos);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testNextRestorePackage() throws RemoteException {
- RestoreDescription exp = new RestoreDescription(mPackageName, 1);
- when(mBackupTransport.nextRestorePackage()).thenReturn(exp);
-
- RestoreDescription ret = mDelegatingTransport.nextRestorePackage();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).nextRestorePackage();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetRestoreData() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.getRestoreData(mFd)).thenReturn(exp);
-
- int ret = mDelegatingTransport.getRestoreData(mFd);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getRestoreData(mFd);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void tesFinishRestore() throws RemoteException {
- mDelegatingTransport.finishRestore();
-
- verify(mBackupTransport, times(1)).finishRestore();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testRequestFullBackupTime() throws RemoteException {
- long exp = 1000L;
- when(mBackupTransport.requestFullBackupTime()).thenReturn(exp);
-
- long ret = mDelegatingTransport.requestFullBackupTime();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).requestFullBackupTime();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testPerformFullBackup() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.performFullBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
-
- int ret = mDelegatingTransport.performFullBackup(mPackageInfo, mFd, mFlags);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).performFullBackup(mPackageInfo, mFd, mFlags);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testCheckFullBackupSize() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.checkFullBackupSize(mSize)).thenReturn(exp);
-
- int ret = mDelegatingTransport.checkFullBackupSize(mSize);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).checkFullBackupSize(mSize);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testSendBackupData() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.sendBackupData(mNumBytes)).thenReturn(exp);
-
- int ret = mDelegatingTransport.sendBackupData(mNumBytes);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).sendBackupData(mNumBytes);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testCancelFullBackup() throws RemoteException {
- mDelegatingTransport.cancelFullBackup();
-
- verify(mBackupTransport, times(1)).cancelFullBackup();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testIsAppEligibleForBackup() throws RemoteException {
- boolean exp = true;
- when(mBackupTransport.isAppEligibleForBackup(mPackageInfo, true)).thenReturn(exp);
-
- boolean ret = mDelegatingTransport.isAppEligibleForBackup(mPackageInfo, true);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).isAppEligibleForBackup(mPackageInfo, true);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetBackupQuota() throws RemoteException {
- long exp = 1000;
- when(mBackupTransport.getBackupQuota(mPackageName, true)).thenReturn(exp);
-
- long ret = mDelegatingTransport.getBackupQuota(mPackageName, true);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getBackupQuota(mPackageName, true);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetNextFullRestoreDataChunk() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.getNextFullRestoreDataChunk(mFd)).thenReturn(exp);
-
- int ret = mDelegatingTransport.getNextFullRestoreDataChunk(mFd);
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getNextFullRestoreDataChunk(mFd);
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testAbortFullRestore() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.abortFullRestore()).thenReturn(exp);
-
- int ret = mDelegatingTransport.abortFullRestore();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).abortFullRestore();
- verifyNoMoreInteractions(mBackupTransport);
- }
-
- @Test
- public void testGetTransportFlags() throws RemoteException {
- int exp = 1000;
- when(mBackupTransport.getTransportFlags()).thenReturn(exp);
-
- int ret = mDelegatingTransport.getTransportFlags();
-
- assertEquals(exp, ret);
- verify(mBackupTransport, times(1)).getTransportFlags();
- verifyNoMoreInteractions(mBackupTransport);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 285806b..c675726 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -59,6 +59,20 @@
5000
};
+ private static final int[] LUX_LEVELS_IDLE = {
+ 0,
+ 10,
+ 40,
+ 80,
+ 200,
+ 655,
+ 1200,
+ 2500,
+ 4400,
+ 8000,
+ 10000
+ };
+
private static final float[] DISPLAY_LEVELS_NITS = {
13.25f,
54.0f,
@@ -73,6 +87,20 @@
478.5f,
};
+ private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
+ 23.25f,
+ 64.0f,
+ 88.85f,
+ 115.02f,
+ 142.7f,
+ 180.12f,
+ 222.1f,
+ 275.2f,
+ 345.8f,
+ 425.2f,
+ 468.5f,
+ };
+
private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
9,
30,
@@ -88,7 +116,6 @@
};
private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
- private static final float[] DISPLAY_LEVELS_RANGE_NITS = { 13.25f, 478.5f };
private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
@@ -118,6 +145,8 @@
new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
+ private static final float TOLERANCE = 0.0001f;
+
@Test
public void testSimpleStrategyMappingAtControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
@@ -357,6 +386,27 @@
assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc));
}
+ @Test
+ public void testIdleModeConfigLoadsCorrectly() {
+ Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+
+ // Create an idle mode bms
+ // This will fail if it tries to fetch the wrong configuration.
+ BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc);
+ assertNotNull("BrightnessMappingStrategy should not be null", bms);
+
+ // Ensure that the config is the one we set
+ // Ensure that the lux -> brightness -> nits path works. ()
+ for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
+ assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
+ assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
+ TOLERANCE);
+ assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
+ DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
+ }
+ }
+
private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
// Save out all of the initial brightness data for comparison after reset.
float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
@@ -421,14 +471,39 @@
brightnessLevelsNits);
}
+ private Resources createResourcesIdle(int[] luxLevels, float[] brightnessLevelsNits) {
+ return createResources(EMPTY_INT_ARRAY, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY,
+ luxLevels, brightnessLevelsNits);
+ }
+
private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
float[] brightnessLevelsNits) {
+ return createResources(luxLevels, brightnessLevelsBacklight, brightnessLevelsNits,
+ EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+
+ }
+
+ private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
+ float[] brightnessLevelsNits, int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
+
Resources mockResources = mock(Resources.class);
+
// For historical reasons, the lux levels resource implicitly defines the first point as 0,
// so we need to chop it off of the array the mock resource object returns.
- int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
- when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels))
- .thenReturn(luxLevelsResource);
+ // Don't mock if these values are not set. If we try to use them, we will fail.
+ if (luxLevels.length > 0) {
+ int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
+ when(mockResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(luxLevelsResource);
+ }
+ if (luxLevelsIdle.length > 0) {
+ int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
+ luxLevelsIdle.length);
+ when(mockResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevelsIdle))
+ .thenReturn(luxLevelsIdleResource);
+ }
when(mockResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
@@ -438,6 +513,10 @@
when(mockResources.obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
.thenReturn(mockBrightnessLevelNits);
+ TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
+ when(mockResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
+ .thenReturn(mockBrightnessLevelNitsIdle);
when(mockResources.getInteger(
com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index cf4bdf6..b588db6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -153,6 +154,7 @@
mHdmiControlService);
audioDevice.init();
mLocalDevices.add(audioDevice);
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index 4ff7c669..ff01cb1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -115,6 +116,7 @@
mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+ hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index c6bb914..a44a5cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -114,6 +115,7 @@
mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
+ hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 17f827d..a411392 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -206,6 +207,7 @@
4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false);
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
// No TV device interacts with AVR so system audio control won't be turned on here
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 809b2f9..6ee6020c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -48,6 +48,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -110,6 +111,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -174,6 +176,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -236,6 +239,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -288,4 +292,18 @@
assertTrue(configuration.isGeoDetectionEnabled());
}
}
+
+ @Test
+ public void test_telephonyFallbackSupported() {
+ ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(true)
+ .setTelephonyDetectionFeatureSupported(true)
+ .setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ assertTrue(config.isTelephonyFallbackSupported());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index ee3195e..2d0dca2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -51,6 +51,11 @@
}
@Override
+ public void enableTelephonyTimeZoneFallback() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public MetricsTimeZoneDetectorState generateMetricsState() {
throw new UnsupportedOperationException();
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 19269e0..193b2e3 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -378,6 +378,7 @@
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.setLocationEnabledSetting(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 91e8d16..ef1b4f5 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -60,6 +60,7 @@
public class TimeZoneDetectorStrategyImplTest {
private static final @UserIdInt int USER_ID = 9876;
+ private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234;
/** A time zone used for initialization that does not occur elsewhere in tests. */
private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
private static final int SLOT_INDEX1 = 10000;
@@ -89,6 +90,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -99,6 +101,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -109,6 +112,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -119,6 +123,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -128,6 +133,7 @@
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -138,6 +144,7 @@
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -147,9 +154,6 @@
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
private FakeEnvironment mFakeEnvironment;
- // A fake source of time for suggestions. This will typically be incremented after every use.
- @ElapsedRealtimeLong private long mElapsedRealtimeMillis;
-
@Before
public void setUp() {
mFakeEnvironment = new FakeEnvironment();
@@ -753,6 +757,204 @@
}
@Test
+ public void testTelephonyFallback() {
+ ConfigurationInternal config = new ConfigurationInternal.Builder(
+ CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .setTelephonyFallbackSupported(true)
+ .build();
+
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .simulateConfigurationInternalChange(config)
+ .resetConfigurationTracking();
+
+ // Confirm initial state is as expected.
+ script.verifyTelephonyFallbackIsEnabled(true)
+ .verifyTimeZoneNotChanged();
+
+ // Although geolocation detection is enabled, telephony fallback should be used initially
+ // and until a suitable "certain" geolocation suggestion is received.
+ {
+ TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+ SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+ "Europe/Paris");
+ script.simulateIncrementClock()
+ .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+ .verifyTimeZoneChangedAndReset(telephonySuggestion)
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Receiving an "uncertain" geolocation suggestion should have no effect.
+ {
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
+ {
+ GeolocationTimeZoneSuggestion geolocationSuggestion =
+ createCertainGeolocationSuggestion("Europe/London");
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .verifyTelephonyFallbackIsEnabled(false);
+ }
+
+ // Used to record the last telephony suggestion received, which will be used when fallback
+ // takes place.
+ TelephonyTimeZoneSuggestion lastTelephonySuggestion;
+
+ // Telephony suggestions should now be ignored and geolocation detection is "in control".
+ {
+ TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
+ SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
+ "Europe/Berlin");
+ script.simulateIncrementClock()
+ .simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+ lastTelephonySuggestion = telephonySuggestion;
+ }
+
+ // Geolocation suggestions should continue to be used as normal (previous telephony
+ // suggestions are not used, even when the geolocation suggestion is uncertain).
+ {
+ GeolocationTimeZoneSuggestion geolocationSuggestion =
+ createCertainGeolocationSuggestion("Europe/Rome");
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ // No change needed, device will already be set to Europe/Rome.
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+ }
+
+ // Enable telephony fallback. Nothing will change, because the geolocation is still certain,
+ // but fallback will remain enabled.
+ {
+ script.simulateIncrementClock()
+ .simulateEnableTelephonyFallback()
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Make the geolocation algorithm uncertain.
+ {
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Make the geolocation algorithm certain, disabling telephony fallback.
+ {
+ GeolocationTimeZoneSuggestion geolocationSuggestion =
+ createCertainGeolocationSuggestion("Europe/Lisbon");
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ }
+
+ // Demonstrate what happens when geolocation is uncertain when telephony fallback is
+ // enabled.
+ {
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false)
+ .simulateEnableTelephonyFallback()
+ .verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+ }
+
+ @Test
+ public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() {
+ ConfigurationInternal config = new ConfigurationInternal.Builder(
+ CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .setTelephonyFallbackSupported(true)
+ .build();
+
+ Script script = new Script()
+ .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
+ .simulateConfigurationInternalChange(config)
+ .resetConfigurationTracking();
+
+ // Confirm initial state is as expected.
+ script.verifyTelephonyFallbackIsEnabled(true)
+ .verifyTimeZoneNotChanged();
+
+ // Receiving an "uncertain" geolocation suggestion should have no effect.
+ {
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
+ // to
+ {
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+
+ // Similar to the case above, but force a fallback attempt after making a "certain"
+ // geolocation suggestion.
+ // Geolocation suggestions should continue to be used as normal (previous telephony
+ // suggestions are not used, even when the geolocation suggestion is uncertain).
+ {
+ GeolocationTimeZoneSuggestion geolocationSuggestion =
+ createCertainGeolocationSuggestion("Europe/Rome");
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
+ createUncertainGeolocationSuggestion();
+ script.simulateIncrementClock()
+ .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(false);
+
+ script.simulateIncrementClock()
+ .simulateEnableTelephonyFallback()
+ .verifyTimeZoneNotChanged()
+ .verifyTelephonyFallbackIsEnabled(true);
+ }
+ }
+
+ @Test
public void testGenerateMetricsState() {
ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
String expectedDeviceTimeZoneId = "InitialZoneId";
@@ -835,6 +1037,8 @@
assertEquals(config.isTelephonyDetectionSupported(),
actualState.isTelephonyDetectionSupported());
assertEquals(config.isGeoDetectionSupported(), actualState.isGeoDetectionSupported());
+ assertEquals(config.isTelephonyFallbackSupported(),
+ actualState.isTelephonyTimeZoneFallbackSupported());
assertEquals(config.getAutoDetectionEnabledSetting(),
actualState.getAutoDetectionEnabledSetting());
assertEquals(config.getGeoDetectionEnabledSetting(),
@@ -865,7 +1069,7 @@
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mElapsedRealtimeMillis++, null);
+ mFakeEnvironment.elapsedRealtimeMillis(), null);
}
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@@ -874,7 +1078,7 @@
GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mElapsedRealtimeMillis++, Arrays.asList(zoneIds));
+ mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
suggestion.addDebugInfo("Test suggestion");
return suggestion;
}
@@ -883,16 +1087,25 @@
private final TestState<String> mTimeZoneId = new TestState<>();
private ConfigurationInternal mConfigurationInternal;
+ private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
private ConfigurationChangeListener mConfigurationInternalChangeListener;
void initializeConfig(ConfigurationInternal configurationInternal) {
mConfigurationInternal = configurationInternal;
}
+ void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ }
+
void initializeTimeZoneSetting(String zoneId) {
mTimeZoneId.init(zoneId);
}
+ void incrementClock() {
+ mElapsedRealtimeMillis++;
+ }
+
@Override
public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
mConfigurationInternalChangeListener = listener;
@@ -936,6 +1149,12 @@
void commitAllChanges() {
mTimeZoneId.commitLatest();
}
+
+ @Override
+ @ElapsedRealtimeLong
+ public long elapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
}
/**
@@ -949,6 +1168,16 @@
return this;
}
+ Script initializeClock(long elapsedRealtimeMillis) {
+ mFakeEnvironment.initializeClock(elapsedRealtimeMillis);
+ return this;
+ }
+
+ Script simulateIncrementClock() {
+ mFakeEnvironment.incrementClock();
+ return this;
+ }
+
/**
* Simulates the user / user's configuration changing.
*/
@@ -1009,6 +1238,15 @@
}
/**
+ * Simulates the time zone detection strategty receiving a signal that allows it to do
+ * telephony fallback.
+ */
+ Script simulateEnableTelephonyFallback() {
+ mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback();
+ return this;
+ }
+
+ /**
* Confirms that the device's time zone has not been set by previous actions since the test
* state was last reset.
*/
@@ -1044,6 +1282,13 @@
return this;
}
+ /** Verifies the state for telephony fallback. */
+ Script verifyTelephonyFallbackIsEnabled(boolean expectedEnabled) {
+ assertEquals(expectedEnabled,
+ mTimeZoneDetectorStrategy.isTelephonyFallbackEnabledForTests());
+ return this;
+ }
+
Script resetConfigurationTracking() {
mFakeEnvironment.commitAllChanges();
return this;
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
similarity index 81%
rename from services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index 463ac52..20c25a0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -57,9 +57,9 @@
import java.util.List;
import java.util.Objects;
-/** Tests for {@link ControllerImpl}. */
+/** Tests for {@link LocationTimeZoneProviderController}. */
@Presubmit
-public class ControllerImplTest {
+public class LocationTimeZoneProviderControllerTest {
private static final long ARBITRARY_TIME_MILLIS = 12345L;
@@ -95,10 +95,11 @@
@Test
public void initializationFailure_primary() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
.plus(testEnvironment.getProviderInitializationTimeoutFuzz());
@@ -106,7 +107,7 @@
// Initialize. After initialization the providers must be initialized and one should be
// started.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
mTestSecondaryLocationTimeZoneProvider.assertInitialized();
@@ -116,15 +117,16 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void initializationFailure_secondary() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
.plus(testEnvironment.getProviderInitializationTimeoutFuzz());
@@ -132,7 +134,7 @@
// Initialize. After initialization the providers must be initialized and one should be
// started.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
mTestSecondaryLocationTimeZoneProvider.assertInitialized();
@@ -142,22 +144,23 @@
mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void initializationFailure_both() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true);
mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true);
// Initialize. After initialization the providers must be initialized and one should be
// started.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
mTestSecondaryLocationTimeZoneProvider.assertInitialized();
@@ -165,21 +168,22 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void initialState_started() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
.plus(testEnvironment.getProviderInitializationTimeoutFuzz());
// Initialize. After initialization the providers must be initialized and one should be
// started.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
mTestSecondaryLocationTimeZoneProvider.assertInitialized();
@@ -189,19 +193,20 @@
mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void initialState_disabled() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED);
// Initialize. After initialization the providers must be initialized but neither should be
// started.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
mTestSecondaryLocationTimeZoneProvider.assertInitialized();
@@ -209,24 +214,25 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
@@ -238,7 +244,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing with no provider event being received from either the primary or
// secondary.
@@ -251,7 +257,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Finally, the uncertainty timeout should cause the controller to make an uncertain
// suggestion.
@@ -262,24 +268,25 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_eventReceivedBeforeInitializationTimeout() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
// suggestion to be made.
@@ -291,24 +298,25 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
@@ -318,7 +326,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the primary provider. This should cause a
// suggestion to be made and the secondary to be shut down.
@@ -330,24 +338,25 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_eventReceivedFromSecondaryAfterInitializationTimeout() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
@@ -357,7 +366,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
// suggestion to be made.
@@ -370,24 +379,25 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_repeatedPrimaryCertainty() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
// suggestion to be made.
@@ -399,7 +409,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -409,7 +419,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -420,24 +430,25 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_repeatedSecondaryCertainty() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
@@ -447,7 +458,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
// suggestion to be made.
@@ -460,7 +471,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -471,7 +482,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -483,24 +494,25 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
// suggestion to be made and ensure the primary is considered initialized.
@@ -512,7 +524,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the primary provider. This should not
// cause a suggestion to be made straight away, but the uncertainty timeout should be
@@ -525,7 +537,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
// suggestion to be made, cancel the uncertainty timeout and ensure the secondary is
@@ -539,7 +551,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event being received from the secondary provider. This should not
// cause a suggestion to be made straight away, but the uncertainty timeout should be
@@ -552,7 +564,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing. This means the uncertainty timeout should fire and the uncertain
// suggestion should be made.
@@ -564,24 +576,25 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void enabled_briefUncertaintyTriggersNoSuggestion() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
// suggestion to be made.
@@ -593,7 +606,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
// timeout should be started and the secondary should be started.
@@ -605,7 +618,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make another
// suggestion, the uncertainty timeout should be cancelled and the secondary should be
@@ -618,23 +631,24 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
@@ -643,7 +657,7 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
@@ -651,23 +665,24 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void configChanges_enableAndDisableWithPreviousSuggestion() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
@@ -676,7 +691,7 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a success event being received from the primary provider.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -687,7 +702,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
// Because there had been a previous suggestion, the controller should withdraw it
@@ -698,24 +713,25 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void configChanges_userSwitch_enabledToEnabled() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -728,7 +744,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the user change (but geo detection still enabled).
testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED);
@@ -744,24 +760,25 @@
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void primaryPermFailure_secondaryEventsReceived() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
// cause the secondary to be started.
@@ -772,7 +789,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -782,7 +799,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the secondary provider should cause the controller to make
// another suggestion, the uncertainty timeout should be cancelled.
@@ -794,7 +811,7 @@
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -804,24 +821,25 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@Test
public void primaryPermFailure_disableAndEnable() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
// cause the secondary to be started.
@@ -832,7 +850,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
@@ -840,7 +858,7 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
@@ -849,24 +867,25 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void secondaryPermFailure_primaryEventsReceived() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
// give this test the opportunity to simulate its failure. Then it will be possible to
@@ -879,7 +898,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -889,7 +908,7 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make
// a suggestion, the uncertainty timeout should be cancelled.
@@ -901,7 +920,7 @@
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the primary. The secondary cannot be started.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -911,24 +930,25 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@Test
public void secondaryPermFailure_disableAndEnable() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
// give this test the opportunity to simulate its failure. Then it will be possible to
@@ -941,7 +961,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -951,7 +971,7 @@
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ assertUncertaintyTimeoutSet(testEnvironment, controller);
// Now signal a config change so that geo detection is disabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
@@ -959,7 +979,7 @@
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled. Only the primary can be
// started.
@@ -969,24 +989,25 @@
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void bothPermFailure_disableAndEnable() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure event from the primary. This will start the secondary.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -996,7 +1017,7 @@
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate failure event from the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -1005,28 +1026,29 @@
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
@Test
public void stateRecording() {
// The test provider enables state recording by default.
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial states.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
{
- LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertNull(state.getLastSuggestion());
assertProviderStates(state.getPrimaryProviderStates(),
PROVIDER_STATE_STOPPED, PROVIDER_STATE_STARTED_INITIALIZING);
assertProviderStates(state.getSecondaryProviderStates(), PROVIDER_STATE_STOPPED);
}
- controllerImpl.clearRecordedProviderStates();
+ controller.clearRecordedProviderStates();
// Simulate some provider behavior that will show up in the state recording.
@@ -1035,21 +1057,21 @@
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
{
- LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertNull(state.getLastSuggestion());
assertProviderStates(
state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
assertProviderStates(
state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_INITIALIZING);
}
- controllerImpl.clearRecordedProviderStates();
+ controller.clearRecordedProviderStates();
// Simulate a certain event from the secondary.
mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
{
- LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
assertProviderStates(state.getPrimaryProviderStates());
@@ -1057,9 +1079,9 @@
state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_CERTAIN);
}
- controllerImpl.clearRecordedProviderStates();
+ controller.clearRecordedProviderStates();
{
- LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests();
+ LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
state.getLastSuggestion().getZoneIds());
assertProviderStates(state.getPrimaryProviderStates());
@@ -1078,19 +1100,20 @@
@Test
public void destroy() {
- ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
- mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+ mTestThreadingDomain, mTestPrimaryLocationTimeZoneProvider,
+ mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED);
// Initialize and check initial state.
- controllerImpl.initialize(testEnvironment, mTestCallback);
+ controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertNoSuggestionMade();
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent(
@@ -1103,10 +1126,10 @@
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
// Trigger destroy().
- controllerImpl.destroy();
+ controller.destroy();
// Confirm that the previous suggestion was overridden.
mTestCallback.assertUncertainSuggestionMadeAndCommit();
@@ -1115,7 +1138,7 @@
PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED);
mTestSecondaryLocationTimeZoneProvider.assertStateChangesAndCommit(
PROVIDER_STATE_DESTROYED);
- assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ assertFalse(controller.isUncertaintyTimeoutSet());
}
private static void assertUncertaintyTimeoutSet(
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index e3da90e..a2df3130 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -46,6 +46,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index a9cbad2..beee2a7 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -126,7 +126,7 @@
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
}
@@ -141,7 +141,7 @@
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
@@ -220,7 +220,7 @@
@Test
public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
setRingerMode(AudioManager.RINGER_MODE_SILENT);
assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
@@ -238,7 +238,7 @@
@Test
public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index be83efb..4eb9c06 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -536,7 +536,7 @@
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
// Wait before checking it never played.
@@ -544,14 +544,14 @@
service, /* timeout= */ 50));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
@@ -635,7 +635,7 @@
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+ eq(AudioAttributes.USAGE_VOICE_COMMUNICATION),
anyInt(), anyString());
inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
@@ -1109,19 +1109,19 @@
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
createSystemReadyService();
int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, scale);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
createSystemReadyService();
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, scale);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ea3a4cd..622669a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -99,6 +99,9 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -119,6 +122,7 @@
import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
+import android.companion.AssociationInfo;
import android.companion.ICompanionDeviceManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -2335,10 +2339,8 @@
@Test
public void testCreateChannelNotifyListener() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -2364,10 +2366,8 @@
@Test
public void testCreateChannelGroupNotifyListener() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
@@ -2385,10 +2385,8 @@
@Test
public void testUpdateChannelNotifyListener() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
mTestNotificationChannel.setLightColor(Color.CYAN);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -2404,10 +2402,8 @@
@Test
public void testDeleteChannelNotifyListener() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -2423,10 +2419,8 @@
@Test
public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -2439,10 +2433,8 @@
@Test
public void testDeleteChannelGroupNotifyListener() throws Exception {
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
@@ -2457,10 +2449,8 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
@@ -2479,9 +2469,8 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(emptyList());
try {
mBinderService.updateNotificationChannelFromPrivilegedListener(
@@ -2502,10 +2491,8 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
mListener.component = new ComponentName(PKG, PKG);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -2530,10 +2517,8 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
@@ -2545,9 +2530,8 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(emptyList());
try {
mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -2566,7 +2550,7 @@
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(new ArrayList<>());
+ .thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -2581,7 +2565,7 @@
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(new ArrayList<>());
+ .thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
try {
@@ -2599,10 +2583,8 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
@@ -2622,10 +2604,8 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
- associations.add("a");
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
@@ -2636,9 +2616,8 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(emptyList());
try {
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
@@ -2654,9 +2633,8 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
- .thenReturn(associations);
+ .thenReturn(emptyList());
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
@@ -7326,12 +7304,12 @@
// Make sure the shortcut is cached.
verify(mShortcutServiceInternal).cacheShortcuts(
- anyInt(), any(), eq(PKG), eq(Collections.singletonList(VALID_CONVO_SHORTCUT_ID)),
+ anyInt(), any(), eq(PKG), eq(singletonList(VALID_CONVO_SHORTCUT_ID)),
eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
// Test: Remove the shortcut
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
- launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(),
+ launcherAppsCallback.getValue().onShortcutsChanged(PKG, emptyList(),
UserHandle.getUserHandleForUid(mUid));
waitForIdle();
@@ -7399,7 +7377,7 @@
// Make sure the shortcut is cached.
verify(mShortcutServiceInternal).cacheShortcuts(
- anyInt(), any(), eq(PKG), eq(Collections.singletonList(shortcutId)),
+ anyInt(), any(), eq(PKG), eq(singletonList(shortcutId)),
eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
// Test: Remove the notification
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ae24785..7a133ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1778,11 +1778,6 @@
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Set to visible so the activity can freeze the screen.
activity.setVisibility(true);
- // Update the display policy to make the screen fully turned on so the freeze is allowed
- display.getDisplayPolicy().screenTurnedOn(null);
- display.getDisplayPolicy().finishKeyguardDrawn();
- display.getDisplayPolicy().finishWindowsDrawn();
- display.getDisplayPolicy().finishScreenTurningOn();
display.rotateInDifferentOrientationIfNeeded(activity);
display.setFixedRotationLaunchingAppUnchecked(activity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index a8ede13..407f9cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_ON;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -29,7 +30,9 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -44,6 +47,7 @@
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodMenuController;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,18 +66,24 @@
private InputMethodMenuController mController;
private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
+ private IWindowManager mIWindowManager;
+ private DisplayManagerGlobal mDisplayManagerGlobal;
+
@Before
public void setUp() throws Exception {
- // Let the Display to be created with the DualDisplay policy.
+ // Let the Display be created with the DualDisplay policy.
final DisplayAreaPolicy.Provider policyProvider =
new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
+ mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
+ .Builder(mAtm, 1000, 1000).build();
+ mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
// Mock addWindowTokenWithOptions to create a test window token.
- IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
- spyOn(wms);
+ mIWindowManager = WindowManagerGlobal.getWindowManagerService();
+ spyOn(mIWindowManager);
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
IBinder clientToken = (IBinder) args[0];
@@ -83,19 +93,24 @@
dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
null /* options */);
return dc.getImeContainer().getConfiguration();
- }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
- anyInt(), any());
-
- mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
- .Builder(mAtm, 1000, 1000).build();
-
- // Mock DisplayManagerGlobal to return test display when obtaining Display instance.
+ }).when(mIWindowManager).attachWindowContextToDisplayArea(any(),
+ eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());
+ mDisplayManagerGlobal = DisplayManagerGlobal.getInstance();
+ spyOn(mDisplayManagerGlobal);
final int displayId = mSecondaryDisplay.getDisplayId();
final Display display = mSecondaryDisplay.getDisplay();
- DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
- spyOn(displayManagerGlobal);
- doReturn(display).when(displayManagerGlobal).getCompatibleDisplay(eq(displayId),
+ doReturn(display).when(mDisplayManagerGlobal).getCompatibleDisplay(eq(displayId),
(Resources) any());
+ Context systemUiContext = ActivityThread.currentActivityThread()
+ .getSystemUiContext(displayId);
+ spyOn(systemUiContext);
+ doReturn(display).when(systemUiContext).getDisplay();
+ }
+
+ @After
+ public void tearDown() {
+ reset(mIWindowManager);
+ reset(mDisplayManagerGlobal);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 6737b1a..730275c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -16,11 +16,14 @@
package com.android.server.wm;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.clearInvocations;
import android.graphics.Rect;
@@ -91,6 +94,7 @@
final Rect endBounds = new Rect(500, 500, 1000, 1000);
mTaskFragment.setBounds(startBounds);
doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
mTaskFragment.setBounds(endBounds);
@@ -108,6 +112,25 @@
verify(mTransaction).setWindowCrop(mLeash, 500, 500);
}
+ @Test
+ public void testNotOkToAnimate_doNotStartChangeTransition() {
+ mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(500, 500, 1000, 1000);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ displayPolicy.screenTurnedOff();
+
+ assertFalse(mTaskFragment.okToAnimate());
+
+ mTaskFragment.setBounds(endBounds);
+
+ verify(mTaskFragment, never()).initializeChangeTransition(any());
+ }
+
/**
* Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an
* activity that has not yet been attached to a process because it is being initialized but
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index c0ae8a5..3065e7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -168,6 +168,11 @@
doReturn(false).when(displayPolicy).hasStatusBar();
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
+ // Update the display policy to make the screen fully turned on so animation is allowed
+ displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.finishKeyguardDrawn();
+ displayPolicy.finishWindowsDrawn();
+ displayPolicy.finishScreenTurningOn();
if (mStatusBarHeight > 0) {
doReturn(true).when(displayPolicy).hasStatusBar();
doAnswer(invocation -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index e5eba57..646647f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -17,22 +17,35 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.window.WindowProvider.KEY_IS_WINDOW_PROVIDER_SERVICE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import android.app.IWindowToken;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -55,12 +68,15 @@
private static final int ANOTHER_UID = 1000;
private final IBinder mClientToken = new Binder();
- private WindowContainer mContainer;
+ private WindowContainer<?> mContainer;
@Before
public void setUp() {
mController = new WindowContextListenerController();
mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+ // Make display on to verify configuration propagation.
+ mDefaultDisplay.getDisplayInfo().state = STATE_ON;
+ mDisplayContent.getDisplayInfo().state = STATE_ON;
}
@Test
@@ -76,7 +92,7 @@
assertEquals(2, mController.mListeners.size());
- final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
+ final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
mDefaultDisplay);
mController.registerWindowContainerListener(mClientToken, container, -1,
TYPE_APPLICATION_OVERLAY, null /* options */);
@@ -89,6 +105,7 @@
assertEquals(container, listener.getWindowContainer());
}
+ @UseTestDisplay
@Test
public void testRegisterWindowContextListenerClientConfigPropagation() {
final TestWindowTokenClient clientToken = new TestWindowTokenClient();
@@ -107,7 +124,7 @@
assertEquals(mDisplayContent.mDisplayId, clientToken.mDisplayId);
// Update the WindowContainer.
- final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
+ final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
mDefaultDisplay);
final Configuration config2 = container.getConfiguration();
final Rect bounds2 = new Rect(0, 0, 20, 20);
@@ -174,7 +191,7 @@
.setDisplayContent(mDefaultDisplay)
.setFromClientToken(true)
.build();
- final DisplayArea da = windowContextCreatedToken.getDisplayArea();
+ final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea();
mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken,
TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
@@ -192,11 +209,12 @@
// Let the Display to be created with the DualDisplay policy.
final DisplayAreaPolicy.Provider policyProvider =
new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
- Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
+ doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
// Create a DisplayContent with dual RootDisplayArea
DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent =
new DualDisplayAreaGroupPolicyTest.DualDisplayContent
.Builder(mAtm, 1000, 1000).build();
+ dualDisplayContent.getDisplayInfo().state = STATE_ON;
final DisplayArea.Tokens imeContainer = dualDisplayContent.getImeContainer();
// Put the ImeContainer to the first sub-RootDisplayArea
dualDisplayContent.mFirstRoot.placeImeContainer(imeContainer);
@@ -222,7 +240,62 @@
assertThat(mController.getContainer(mClientToken)).isEqualTo(imeContainer);
}
- private class TestWindowTokenClient extends IWindowToken.Stub {
+ @Test
+ public void testConfigUpdateForSuspendedWindowContext() {
+ final TestWindowTokenClient mockToken = new TestWindowTokenClient();
+ spyOn(mockToken);
+
+ mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF;
+
+ final Configuration config1 = mContainer.getConfiguration();
+ final Rect bounds1 = new Rect(0, 0, 10, 10);
+ config1.windowConfiguration.setBounds(bounds1);
+ config1.densityDpi = 100;
+ mContainer.onRequestedOverrideConfigurationChanged(config1);
+
+ mController.registerWindowContainerListener(mockToken, mContainer, -1,
+ TYPE_APPLICATION_OVERLAY, null /* options */);
+
+ verify(mockToken, never()).onConfigurationChanged(any(), anyInt());
+
+ // Turn on the display and verify if the client receive the callback
+ Display display = mContainer.getDisplayContent().getDisplay();
+ spyOn(display);
+ Mockito.doAnswer(invocation -> {
+ final DisplayInfo info = mContainer.getDisplayContent().getDisplayInfo();
+ info.state = STATE_ON;
+ ((DisplayInfo) invocation.getArgument(0)).copyFrom(info);
+ return null;
+ }).when(display).getDisplayInfo(any(DisplayInfo.class));
+
+ mContainer.getDisplayContent().onDisplayChanged();
+
+ assertThat(mockToken.mConfiguration).isEqualTo(config1);
+ assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId());
+ }
+
+ @Test
+ public void testReportConfigUpdateForSuspendedWindowProviderService() {
+ final TestWindowTokenClient clientToken = new TestWindowTokenClient();
+ final Bundle options = new Bundle();
+ options.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true);
+
+ mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF;
+
+ final Configuration config1 = mContainer.getConfiguration();
+ final Rect bounds1 = new Rect(0, 0, 10, 10);
+ config1.windowConfiguration.setBounds(bounds1);
+ config1.densityDpi = 100;
+ mContainer.onRequestedOverrideConfigurationChanged(config1);
+
+ mController.registerWindowContainerListener(clientToken, mContainer, -1,
+ TYPE_APPLICATION_OVERLAY, options);
+
+ assertThat(clientToken.mConfiguration).isEqualTo(config1);
+ assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId);
+ }
+
+ private static class TestWindowTokenClient extends IWindowToken.Stub {
private Configuration mConfiguration;
private int mDisplayId;
private boolean mRemoved;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 996b4b2..92fd682 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -198,6 +198,13 @@
SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
+ // Update the display policy to make the screen fully turned on so animation is allowed
+ final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy();
+ displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.finishKeyguardDrawn();
+ displayPolicy.finishWindowsDrawn();
+ displayPolicy.finishScreenTurningOn();
+
mTransaction = mSystemServicesTestRule.mTransaction;
mMockSession = mock(Session.class);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 88b21e0..ae2facd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3465,15 +3465,39 @@
* @see #SIM_STATE_PRESENT
*
* @hide
+ * @deprecated instead use {@link #getSimCardState(int, int)}
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Deprecated
public @SimState int getSimCardState(int physicalSlotIndex) {
- int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
+ int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex, DEFAULT_PORT_INDEX));
return getSimCardStateFromSimState(simState);
}
/**
+ * Returns a constant indicating the state of the device SIM card in a physical slot and
+ * port index.
+ *
+ * @param physicalSlotIndex physical slot index
+ * @param portIndex The port index is an enumeration of the ports available on the UICC.
+ * Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_PRESENT
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimState int getSimCardState(int physicalSlotIndex, int portIndex) {
+ int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex, portIndex));
+ return getSimCardStateFromSimState(simState);
+ }
+ /**
* Converts SIM state to SIM card state.
* @param simState
* @return SIM card state
@@ -3493,13 +3517,19 @@
/**
* Converts a physical slot index to logical slot index.
* @param physicalSlotIndex physical slot index
+ * @param portIndex The port index is an enumeration of the ports available on the UICC.
+ * Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
* @return logical slot index
*/
- private int getLogicalSlotIndex(int physicalSlotIndex) {
+ private int getLogicalSlotIndex(int physicalSlotIndex, int portIndex) {
UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
&& slotInfos[physicalSlotIndex] != null) {
- return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
+ for (UiccPortInfo portInfo : slotInfos[physicalSlotIndex].getPorts()) {
+ if (portInfo.getPortIndex() == portIndex) {
+ return portInfo.getLogicalSlotIndex();
+ }
+ }
}
return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
@@ -3539,12 +3569,42 @@
* @see #SIM_STATE_LOADED
*
* @hide
+ * @deprecated instead use {@link #getSimApplicationState(int, int)}
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Deprecated
public @SimState int getSimApplicationState(int physicalSlotIndex) {
int simState =
- SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
+ SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex,
+ DEFAULT_PORT_INDEX));
+ return getSimApplicationStateFromSimState(simState);
+ }
+
+ /**
+ * Returns a constant indicating the state of the card applications on the device SIM card in
+ * a physical slot.
+ *
+ * @param physicalSlotIndex physical slot index
+ * @param portIndex The port index is an enumeration of the ports available on the UICC.
+ * Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+ *
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_PIN_REQUIRED
+ * @see #SIM_STATE_PUK_REQUIRED
+ * @see #SIM_STATE_NETWORK_LOCKED
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_PERM_DISABLED
+ * @see #SIM_STATE_LOADED
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @SimState int getSimApplicationState(int physicalSlotIndex, int portIndex) {
+ int simState =
+ SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex,
+ portIndex));
return getSimApplicationStateFromSimState(simState);
}
@@ -4143,18 +4203,21 @@
* should be {@link #getPhoneCount()} if success, otherwise return an empty map.
*
* @hide
+ * @deprecated use {@link #getSimSlotMapping()} instead.
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@NonNull
+ @Deprecated
public Map<Integer, Integer> getLogicalToPhysicalSlotMapping() {
Map<Integer, Integer> slotMapping = new HashMap<>();
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- int[] slotMappingArray = telephony.getSlotsMapping(mContext.getOpPackageName());
- for (int i = 0; i < slotMappingArray.length; i++) {
- slotMapping.put(i, slotMappingArray[i]);
+ List<UiccSlotMapping> simSlotsMapping = telephony.getSlotsMapping(
+ mContext.getOpPackageName());
+ for (UiccSlotMapping slotMap : simSlotsMapping) {
+ slotMapping.put(slotMap.getLogicalSlotIndex(), slotMap.getPhysicalSlotIndex());
}
}
} catch (RemoteException e) {
@@ -4163,6 +4226,33 @@
return slotMapping;
}
+ /**
+ * Get the mapping from logical slots to physical sim slots and port indexes. Initially the
+ * logical slot index was mapped to physical slot index, but with support for multi-enabled
+ * profile(MEP) logical slot is now mapped to port index.
+ *
+ * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
+ * slots to ports and physical slots.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @NonNull
+ public Collection<UiccSlotMapping> getSimSlotMapping() {
+ List<UiccSlotMapping> slotMap = new ArrayList<>();
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ slotMap = telephony.getSlotsMapping(mContext.getOpPackageName());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ return slotMap;
+ }
//
//
// Subscriber Info
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index dbc6cb6..6d094cb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2134,9 +2134,9 @@
String callingFeatureId);
/**
- * Get the mapping from logical slots to physical slots.
+ * Get the mapping from logical slots to port index.
*/
- int[] getSlotsMapping(String callingPackage);
+ List<UiccSlotMapping> getSlotsMapping(String callingPackage);
/**
* Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 0bb6198..22320fd 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -27,9 +27,8 @@
java_sdk_library {
name: "android.test.mock",
-
- srcs: [
- ":android-test-mock-sources",
+ srcs: [":android-test-mock-sources"],
+ api_srcs: [
// Note: Below are NOT APIs of this library. We only take APIs under
// the android.test.mock package. They however provide private APIs that
// android.test.mock APIs references to. We need to have the classes in
@@ -44,15 +43,9 @@
"app-compat-annotations",
"unsupportedappusage",
],
-
api_packages: [
"android.test.mock",
],
- // Only include android.test.mock.* classes. Jarjar rules below removes
- // classes in other packages like android.content. In order to keep the
- // list up-to-date, permitted_packages ensures that the library contains
- // clases under android.test.mock after the jarjar rules are applied.
- jarjar_rules: "jarjar-rules.txt",
permitted_packages: [
"android.test.mock",
],
diff --git a/test-mock/jarjar-rules.txt b/test-mock/jarjar-rules.txt
deleted file mode 100644
index 4420a44..0000000
--- a/test-mock/jarjar-rules.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-zap android.accounts.**
-zap android.app.**
-zap android.content.**
-zap android.database.**
-zap android.os.**
-zap android.util.**
-zap android.view.**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 3c9ba20..ca735031 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.close
import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -196,13 +195,13 @@
testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPresubmitAssertion() {
flickerRule.checkPresubmitAssertions()
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPostsubmitAssertion() {
flickerRule.checkPostsubmitAssertions()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index b1bdb31..39d2518 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -105,7 +105,7 @@
}
}
- @FlakyTest(bugId = 190189685)
+ @Postsubmit
@Test
fun imeAppWindowBecomesInvisible() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index ac90752..61fe02e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -106,7 +106,7 @@
@Test
fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
- @FlakyTest
+ @Postsubmit
@Test
fun imeAppWindowBecomesInvisible() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index cc5d9d2..1c14916 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -144,7 +144,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 429841b..61fe07a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -110,7 +110,7 @@
* Checks that the app layer doesn't exist at the start of the transition, that it is
* created (invisible) and becomes visible during the transition
*/
- @FlakyTest
+ @Postsubmit
@Test
fun appLayerBecomesVisible() {
testSpec.assertLayers {
@@ -169,7 +169,7 @@
}
/** {@inheritDoc} */
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
@@ -211,7 +211,7 @@
}
/** {@inheritDoc} */
- @FlakyTest
+ @Postsubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -227,7 +227,7 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @Postsubmit
+ @FlakyTest
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 5b0372d..0a64939 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -133,7 +134,7 @@
/**
* Checks that the transition starts with [testApp2] being the top window.
*/
- @Postsubmit
+ @Presubmit
@Test
fun startsWithApp2WindowBeingOnTop() {
testSpec.assertWmStart {
@@ -284,7 +285,7 @@
/**
* Checks that the navbar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
@@ -293,21 +294,21 @@
*
* NOTE: This doesn't check that the navbar is visible or not.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
/**
* Checks that the status bar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 99bc115..5b63376 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -298,7 +299,7 @@
/**
* Checks that the navbar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
@@ -307,21 +308,21 @@
*
* NOTE: This doesn't check that the navbar is visible or not.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
/**
* Checks that the status bar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index a97a48e..cac7978 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.rotation
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -97,13 +96,13 @@
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPresubmitAssertion() {
flickerRule.checkPresubmitAssertions()
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPostsubmitAssertion() {
flickerRule.checkPostsubmitAssertions()