diff options
105 files changed, 2471 insertions, 1138 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index fc2a409be2c2..040a1164fc73 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -887,8 +887,10 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserStarting(@NonNull TargetUser user) { + public void onUserUnlocked(@NonNull TargetUser user) { synchronized (mLock) { + // Note that the user has started after its unlocked instead of when the user + // actually starts because the storage won't be decrypted until unlock. mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier()); } // Let's kick any outstanding jobs for this user. @@ -896,12 +898,6 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserUnlocking(@NonNull TargetUser user) { - // Let's kick any outstanding jobs for this user. - mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); - } - - @Override public void onUserStopping(@NonNull TargetUser user) { synchronized (mLock) { mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier()); diff --git a/core/api/current.txt b/core/api/current.txt index 78c7f939d556..24f9aa03ff08 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9741,7 +9741,7 @@ package android.companion { public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { } - public class DeviceNotAssociatedException extends java.lang.Exception { + public class DeviceNotAssociatedException extends java.lang.RuntimeException { } public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> { @@ -19662,6 +19662,7 @@ package android.location { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent); method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties); + method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties, @NonNull java.util.Set<java.lang.String>); method @Deprecated public void clearTestProviderEnabled(@NonNull String); method @Deprecated public void clearTestProviderLocation(@NonNull String); method @Deprecated public void clearTestProviderStatus(@NonNull String); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 05360141d9dd..5734cbd8d84f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -176,6 +176,7 @@ package android { field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; field public static final String OBSERVE_NETWORK_POLICY = "android.permission.OBSERVE_NETWORK_POLICY"; field public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS"; + field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY"; field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; @@ -2550,10 +2551,12 @@ package android.content.pm { field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; + field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg"; @@ -2908,6 +2911,22 @@ package android.hardware { method public boolean injectSensorData(android.hardware.Sensor, float[], int, long); } + public final class SensorPrivacyManager { + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 + } + } package android.hardware.biometrics { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 835c49fccf7d..6231b958ae9a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -718,8 +718,10 @@ package android.content.pm { method public void holdLock(android.os.IBinder, int); method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; + field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000 field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; @@ -880,11 +882,21 @@ package android.graphics.fonts { package android.hardware { public final class SensorPrivacyManager { - method public boolean isIndividualSensorPrivacyEnabled(int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacy(int, boolean); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacyForProfileGroup(int, boolean); - field public static final int INDIVIDUAL_SENSOR_CAMERA = 2; // 0x2 - field public static final int INDIVIDUAL_SENSOR_MICROPHONE = 1; // 0x1 + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, boolean); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2f3b50b17d51..160844aacc46 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1141,23 +1141,20 @@ public class AppOpsManager { * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_MICROPHONE = 100; + public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE; /** * Phone call is using camera * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_CAMERA = 101; + public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA; /** * Audio is being recorded for hotword detection. * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_RECORD_AUDIO_HOTWORD = 102; + public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD; /** * Manage credentials in the system KeyChain. @@ -1184,10 +1181,29 @@ public class AppOpsManager { */ public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; + /** + * Fine location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 108; + public static final int _NUM_OP = 110; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1567,6 +1583,24 @@ public class AppOpsManager { */ public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm"; + /** + * Fine location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_FINE_LOCATION_SOURCE = "android:fine_location_source"; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1767,6 +1801,8 @@ public class AppOpsManager { OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM + OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE + OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE }; /** @@ -1881,6 +1917,8 @@ public class AppOpsManager { OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_RECORD_AUDIO_OUTPUT, OPSTR_SCHEDULE_EXACT_ALARM, + OPSTR_FINE_LOCATION_SOURCE, + OPSTR_COARSE_LOCATION_SOURCE, }; /** @@ -1996,6 +2034,8 @@ public class AppOpsManager { "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "RECORD_AUDIO_OUTPUT", "SCHEDULE_EXACT_ALARM", + "FINE_LOCATION_SOURCE", + "COARSE_LOCATION_SOURCE", }; /** @@ -2112,6 +2152,8 @@ public class AppOpsManager { Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, null, // no permission for OP_RECORD_AUDIO_OUTPUT Manifest.permission.SCHEDULE_EXACT_ALARM, + null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, + null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, }; /** @@ -2228,6 +2270,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2343,6 +2387,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2457,6 +2503,8 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM + AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE + AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2575,6 +2623,8 @@ public class AppOpsManager { true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER false, // RECORD_AUDIO_OUTPUT false, // SCHEDULE_EXACT_ALARM + false, // ACCESS_FINE_LOCATION_SOURCE + false, // ACCESS_COARSE_LOCATION_SOURCE }; /** diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java index 71a800f2085e..066aadae1476 100644 --- a/core/java/android/app/time/LocationTimeZoneManager.java +++ b/core/java/android/app/time/LocationTimeZoneManager.java @@ -37,7 +37,7 @@ public final class LocationTimeZoneManager { /** * The name of the service for shell commands */ - public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; + public static final String SERVICE_NAME = "location_time_zone_manager"; /** * A shell command that starts the service (after stop). diff --git a/core/java/android/companion/DeviceNotAssociatedException.java b/core/java/android/companion/DeviceNotAssociatedException.java index bebb6c9ff7eb..f8a7a7cdeffe 100644 --- a/core/java/android/companion/DeviceNotAssociatedException.java +++ b/core/java/android/companion/DeviceNotAssociatedException.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; * An exception for a case when a given device was not * {@link CompanionDeviceManager#associate associated} to the calling app. */ -public class DeviceNotAssociatedException extends Exception { +public class DeviceNotAssociatedException extends RuntimeException { /** @hide */ public DeviceNotAssociatedException(@Nullable String deviceName) { super("Device not associated with the current app: " + deviceName); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9a3fbdcea9ff..ca882417394e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3623,11 +3623,26 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for - * camera. When sensory privacy for the camera is enabled no camera data is send to clients, + * microphone. When sensory privacy for the microphone is enabled no microphone data is sent to + * clients, e.g. all audio data is silent. + * + * @hide + */ + @SystemApi + @TestApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for + * camera. When sensory privacy for the camera is enabled no camera data is sent to clients, * e.g. the view finder in a camera app would appear blank. * * @hide */ + @SystemApi + @TestApi @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index f4f9e1775d1a..e03c1f48773a 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -19,6 +19,7 @@ package android.hardware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; @@ -33,6 +34,7 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * This class provides access to the sensor privacy services; sensor privacy allows the @@ -42,9 +44,21 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi @TestApi @SystemService(Context.SENSOR_PRIVACY_SERVICE) public final class SensorPrivacyManager { + + /** + * @hide + */ + public static final boolean USE_MICROPHONE_TOGGLE = true; + + /** + * @hide + */ + public static final boolean USE_CAMERA_TOGGLE = true; + /** * Unique Id of this manager to identify to the service * @hide @@ -58,28 +72,39 @@ public final class SensorPrivacyManager { public static final String EXTRA_SENSOR = SensorPrivacyManager.class.getName() + ".extra.sensor"; - /** Microphone - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_MICROPHONE = - SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; - - /** Camera - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_CAMERA = - SensorPrivacyIndividualEnabledSensorProto.CAMERA; - /** - * Individual sensors not listed in {@link Sensor} + * Individual sensors not listed in {@link Sensors} * @hide */ - @IntDef(prefix = "INDIVIDUAL_SENSOR_", value = { - INDIVIDUAL_SENSOR_MICROPHONE, - INDIVIDUAL_SENSOR_CAMERA - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndividualSensor {} + @SystemApi + @TestApi + public static class Sensors { + + private Sensors() {} + + /** Microphone + * @hide */ + @SystemApi + @TestApi + public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; + /** Camera + * @hide */ + @SystemApi + @TestApi + public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA; + + /** + * Individual sensors not listed in {@link Sensors} + * + * @hide + */ + @IntDef(value = { + MICROPHONE, + CAMERA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Sensor {} + } /** * A class implementing this interface can register with the {@link @@ -88,6 +113,8 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi + @TestApi public interface OnSensorPrivacyChangedListener { /** * Callback invoked when the sensor privacy state changes. @@ -165,7 +192,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@NonNull final OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { @@ -196,15 +224,37 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(@IndividualSensor int sensor, - final OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, + @NonNull OnSensorPrivacyChangedListener listener) { + addSensorPrivacyListener(sensor, mContext.getMainExecutor(), listener); + } + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param sensor the sensor to listen to changes to + * @param executor the executor to dispatch the callback on + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + * + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor, + @NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { - listener.onSensorPrivacyChanged(enabled); + executor.execute(() -> listener.onSensorPrivacyChanged(enabled)); } }; mListeners.put(listener, iListener); @@ -228,7 +278,10 @@ public final class SensorPrivacyManager { * * @hide */ - public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void removeSensorPrivacyListener(@NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener != null) { @@ -249,6 +302,7 @@ public final class SensorPrivacyManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled() { try { return mService.isSensorPrivacyEnabled(); @@ -264,8 +318,10 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi @TestApi - public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) { try { return mService.isIndividualSensorPrivacyEnabled(mContext.getUserId(), sensor); } catch (RemoteException e) { @@ -283,8 +339,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacy(@IndividualSensor int sensor, - boolean enable) { + public void setSensorPrivacy(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacy(mContext.getUserId(), sensor, enable); } catch (RemoteException e) { @@ -303,7 +358,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacyForProfileGroup(@IndividualSensor int sensor, + public void setSensorPrivacyForProfileGroup(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacyForProfileGroup(mContext.getUserId(), sensor, @@ -321,7 +376,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void suppressIndividualSensorPrivacyReminders(@NonNull String packageName, + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void suppressSensorPrivacyReminders(@NonNull String packageName, boolean suppress) { try { mService.suppressIndividualSensorPrivacyReminders(mContext.getUserId(), packageName, diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index c6efaace76d7..2b6f336848c3 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -47,6 +47,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_SYSTEM_SERVICES, POWER_COMPONENT_SENSORS, POWER_COMPONENT_GNSS, + POWER_COMPONENT_WIFI, POWER_COMPONENT_WAKELOCK, POWER_COMPONENT_SCREEN, POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, @@ -66,6 +67,7 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_MOBILE_RADIO = 8; public static final int POWER_COMPONENT_SENSORS = 9; public static final int POWER_COMPONENT_GNSS = 10; + public static final int POWER_COMPONENT_WIFI = 11; public static final int POWER_COMPONENT_WAKELOCK = 12; public static final int POWER_COMPONENT_SCREEN = 13; // Power that is re-attributed to other battery consumers. For example, for System Server @@ -94,6 +96,7 @@ public abstract class BatteryConsumer { TIME_COMPONENT_MOBILE_RADIO, TIME_COMPONENT_SENSORS, TIME_COMPONENT_GNSS, + TIME_COMPONENT_WIFI, TIME_COMPONENT_WAKELOCK, TIME_COMPONENT_SCREEN, }) @@ -112,6 +115,7 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_MOBILE_RADIO = 8; public static final int TIME_COMPONENT_SENSORS = 9; public static final int TIME_COMPONENT_GNSS = 10; + public static final int TIME_COMPONENT_WIFI = 11; public static final int TIME_COMPONENT_WAKELOCK = 12; public static final int TIME_COMPONENT_SCREEN = 13; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 54095bd86887..62e1ee19b25e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -7965,16 +7965,14 @@ public class BatteryStatsImpl extends BatteryStats { /** Adds the given energy to the given standard energy bucket for this uid. */ private void addEnergyToStandardBucketLocked(long energyDeltaUJ, - @StandardEnergyBucket int energyBucket, boolean accumulate) { + @StandardEnergyBucket int energyBucket) { getOrCreateMeasuredEnergyStatsLocked() - .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate); + .updateStandardBucket(energyBucket, energyDeltaUJ); } /** Adds the given energy to the given custom energy bucket for this uid. */ - private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket, - boolean accumulate) { - getOrCreateMeasuredEnergyStatsLocked() - .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate); + private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket) { + getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(energyBucket, energyDeltaUJ); } /** @@ -12468,7 +12466,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ, true); + mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ); // Now we blame individual apps, but only if the display was ON. if (energyBucket != MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON) { @@ -12506,7 +12504,7 @@ public class BatteryStatsImpl extends BatteryStats { final long appDisplayEnergyMJ = (totalDisplayEnergyMJ * fgTimeMs + (totalFgTimeMs / 2)) / totalFgTimeMs; - uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true); + uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket); // To mitigate round-off errors, remove this app from numerator & denominator totals totalDisplayEnergyMJ -= appDisplayEnergyMJ; @@ -12533,7 +12531,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mGlobalMeasuredEnergyStats == null) return; if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return; - mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true); + mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ); if (uidEnergies == null) return; final int numUids = uidEnergies.size(); @@ -12543,7 +12541,7 @@ public class BatteryStatsImpl extends BatteryStats { if (uidEnergyUJ == 0) continue; final Uid uidObj = getAvailableUidStatsLocked(uidInt); if (uidObj != null) { - uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true); + uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket); } else { // Ignore any uid not already known to BatteryStats, rather than creating a new Uid. // Otherwise we could end up reviving dead Uids. Note that the CPU data is updated diff --git a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java index 5910b61f0f6f..fd5201431eab 100644 --- a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java +++ b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java @@ -32,6 +32,10 @@ public class UsageBasedPowerEstimator { mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR; } + public boolean isSupported() { + return mAveragePowerMahPerMs != 0; + } + /** * Given a {@link BatteryStats.Timer}, returns the accumulated duration. */ diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 63763f76c6ed..98f613fc1c40 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -15,8 +15,13 @@ */ package com.android.internal.os; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; @@ -30,25 +35,93 @@ import java.util.List; public class WifiPowerCalculator extends PowerCalculator { private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private static final String TAG = "WifiPowerCalculator"; - private final double mIdleCurrentMa; - private final double mTxCurrentMa; - private final double mRxCurrentMa; - private final PowerProfile mPowerProfile; + private final UsageBasedPowerEstimator mIdlePowerEstimator; + private final UsageBasedPowerEstimator mTxPowerEstimator; + private final UsageBasedPowerEstimator mRxPowerEstimator; + private final UsageBasedPowerEstimator mPowerOnPowerEstimator; + private final UsageBasedPowerEstimator mScanPowerEstimator; + private final UsageBasedPowerEstimator mBatchScanPowerEstimator; private final boolean mHasWifiPowerController; - private double mTotalAppPowerDrain = 0; - private long mTotalAppRunningTime = 0; - private WifiPowerEstimator mWifiPowerEstimator; - private boolean mHasWifiPowerReporting; + private final double mWifiPowerPerPacket; + + private static class PowerDurationAndTraffic { + public double powerMah; + public long durationMs; + + public long wifiRxPackets; + public long wifiTxPackets; + public long wifiRxBytes; + public long wifiTxBytes; + } public WifiPowerCalculator(PowerProfile profile) { - mPowerProfile = profile; + mPowerOnPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_ON)); + mScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)); + mBatchScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN)); + mIdlePowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE)); + mTxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX)); + mRxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX)); + + mWifiPowerPerPacket = getWifiPowerPerPacket(profile); + + mHasWifiPowerController = + mIdlePowerEstimator.isSupported() && mTxPowerEstimator.isSupported() + && mRxPowerEstimator.isSupported(); + } + + @Override + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + + // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, + // so always check this field. + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); + + final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = + builder.getOrCreateSystemBatteryConsumerBuilder( + SystemBatteryConsumer.DRAIN_TYPE_WIFI); - mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); - mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX); - mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting); - mHasWifiPowerController = mIdleCurrentMa != 0 && mTxCurrentMa != 0 && mRxCurrentMa != 0; - mWifiPowerEstimator = new WifiPowerEstimator(mPowerProfile); + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs); + app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); + + if (app.getUid() == Process.WIFI_UID) { + systemBatteryConsumerBuilder.addUidBatteryConsumer(app); + app.excludeFromBatteryUsageStats(); + } + } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + systemBatteryConsumerBuilder + .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); } /** @@ -64,100 +137,151 @@ public class WifiPowerCalculator extends PowerCalculator { // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, // so always check this field. - mHasWifiPowerReporting = mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); + final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + calculateApp(powerDurationAndTraffic, app.uidObj, rawRealtimeUs, statsType, + hasWifiPowerReporting); + + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.wifiPowerMah = powerDurationAndTraffic.powerMah; + app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs; + app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes; + app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets; + app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes; + app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets; + if (app.getUid() == Process.WIFI_UID) { + if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); + app.isAggregated = true; + bs.add(app); + } } } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, statsType, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs; + bs.wifiPowerMah += powerDurationAndTraffic.powerMah; + if (bs.sumPower() > 0) { sippers.add(bs); } } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateApp(app, u, rawRealtimeUs, rawUptimeUs, statsType); - return; - } - - final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); - if (counter == null) { - return; - } - - final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = idleTime + rxTime + txTime; - mTotalAppRunningTime += app.wifiRunningTimeMs; - - app.wifiPowerMah = - ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) - / (1000 * 60 * 60); - mTotalAppPowerDrain += app.wifiPowerMah; - - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, + private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u, + long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting) { + powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, + powerDurationAndTraffic.wifiRxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" + - txTime + "ms power=" + formatCharge(app.wifiPowerMah)); - } - } + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); + if (counter != null) { + final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); + + powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; + powerDurationAndTraffic.powerMah = mIdlePowerEstimator.calculatePower(idleTime) + + mTxPowerEstimator.calculatePower(txTime) + + mRxPowerEstimator.calculatePower(rxTime); - private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateRemaining(app, stats, rawRealtimeUs, rawUptimeUs, - statsType); - return; + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + + "ms tx=" + txTime + "ms power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } + } + } else { + final double wifiPacketPower = ( + powerDurationAndTraffic.wifiRxPackets + powerDurationAndTraffic.wifiTxPackets) + * mWifiPowerPerPacket; + final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; + long batchScanTimeMs = 0; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + batchScanTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; + } + + powerDurationAndTraffic.durationMs = wifiRunningTime; + powerDurationAndTraffic.powerMah = wifiPacketPower + + mPowerOnPowerEstimator.calculatePower(wifiRunningTime) + + mScanPowerEstimator.calculatePower(wifiScanTimeMs) + + mBatchScanPowerEstimator.calculatePower(batchScanTimeMs); + + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } } + } - final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity(); + private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic, + BatteryStats stats, long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting, long totalAppDurationMs, + double totalAppPowerMah) { + long totalDurationMs; + double totalPowerMah; + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = + stats.getWifiControllerActivity(); - final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); + final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = Math.max(0, - (idleTimeMs + rxTimeMs + txTimeMs) - mTotalAppRunningTime); + totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs; - double powerDrainMah = counter.getPowerCounter().getCountLocked(statsType) - / (double) (1000 * 60 * 60); - if (powerDrainMah == 0) { - // Some controllers do not report power drain, so we can calculate it here. - powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) - + (rxTimeMs * mRxCurrentMa)) / (1000 * 60 * 60); + totalPowerMah = + counter.getPowerCounter().getCountLocked(statsType) / (double) (1000 * 60 * 60); + if (totalPowerMah == 0) { + // Some controllers do not report power drain, so we can calculate it here. + totalPowerMah = mIdlePowerEstimator.calculatePower(idleTimeMs) + + mTxPowerEstimator.calculatePower(txTimeMs) + + mRxPowerEstimator.calculatePower(rxTimeMs); + } + } else { + totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000; + totalPowerMah = mPowerOnPowerEstimator.calculatePower(totalDurationMs); } - app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain); + + powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs); + powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah); if (DEBUG) { - Log.d(TAG, "left over WiFi power: " + formatCharge(app.wifiPowerMah)); + Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah)); } } - @Override - public void reset() { - mTotalAppPowerDrain = 0; - mTotalAppRunningTime = 0; - mWifiPowerEstimator.reset(); + /** + * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. + */ + private static double getWifiPowerPerPacket(PowerProfile profile) { + // TODO(b/179392913): Extract average bit rates from system + final long wifiBps = 1000000; + final double averageWifiActivePower = + profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + return averageWifiActivePower / (((double) wifiBps) / 8 / 2048); } } diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java deleted file mode 100644 index d0a105cfd958..000000000000 --- a/core/java/com/android/internal/os/WifiPowerEstimator.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 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.internal.os; - -import android.os.BatteryStats; -import android.os.Process; -import android.os.UserHandle; -import android.util.Log; -import android.util.SparseArray; - -import java.util.List; - -/** - * Estimates WiFi power usage based on timers in BatteryStats. - */ -public class WifiPowerEstimator extends PowerCalculator { - private static final boolean DEBUG = BatteryStatsHelper.DEBUG; - private static final String TAG = "WifiPowerEstimator"; - private final double mWifiPowerPerPacket; - private final double mWifiPowerOn; - private final double mWifiPowerScan; - private final double mWifiPowerBatchScan; - private long mTotalAppWifiRunningTimeMs = 0; - - public WifiPowerEstimator(PowerProfile profile) { - mWifiPowerPerPacket = getWifiPowerPerPacket(profile); - mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON); - mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN); - mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN); - } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); - - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); - - for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); - } - } - if (bs.sumPower() > 0) { - sippers.add(bs); - } - } - - /** - * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. - */ - private static double getWifiPowerPerPacket(PowerProfile profile) { - final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system - final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) - / 3600; - return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048); - } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - - final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) - * mWifiPowerPerPacket; - - app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; - mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; - final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); - - final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; - final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); - - double wifiBatchScanPower = 0; - for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { - final long batchScanTimeMs = - u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; - final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60); - wifiBatchScanPower += batchScanPower; - } - - app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(app.wifiPowerMah)); - } - } - - void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) - / 1000; - final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) - / (1000*60*60); - app.wifiRunningTimeMs = totalRunningTimeMs; - app.wifiPowerMah = Math.max(0, powerDrain); - } - - @Override - public void reset() { - mTotalAppWifiRunningTimeMs = 0; - } -} diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index d7b4d78c56cf..d49203c731e9 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -193,34 +193,30 @@ public class MeasuredEnergyStats { return mAccumulatedEnergiesMicroJoules.length; } - // TODO: Get rid of the 'accumulate' boolean. It's always true. /** Updates the given standard energy bucket with the given energy if accumulate is true. */ - public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ, - boolean accumulate) { + public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ) { checkValidStandardBucket(bucket); - updateEntry(bucket, energyDeltaUJ, accumulate); + updateEntry(bucket, energyDeltaUJ); } /** Updates the given custom energy bucket with the given energy if accumulate is true. */ - public void updateCustomBucket(int customBucket, long energyDeltaUJ, boolean accumulate) { + public void updateCustomBucket(int customBucket, long energyDeltaUJ) { if (!isValidCustomBucket(customBucket)) { Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket); return; } final int index = customBucketToIndex(customBucket); - updateEntry(index, energyDeltaUJ, accumulate); + updateEntry(index, energyDeltaUJ); } /** Updates the given index with the given energy if accumulate is true. */ - private void updateEntry(int index, long energyDeltaUJ, boolean accumulate) { - if (accumulate) { - if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { - mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; - } else { - Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " - + getBucketName(index) + " whose value was " - + mAccumulatedEnergiesMicroJoules[index]); - } + private void updateEntry(int index, long energyDeltaUJ) { + if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { + mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; + } else { + Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " + + getBucketName(index) + " whose value was " + + mAccumulatedEnergiesMicroJoules[index]); } } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 671cb8481dd9..b40ffb0136f2 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -24,6 +24,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.hardware.SensorPrivacyManager; import android.os.Build; import android.os.CarrierAssociatedAppEntry; import android.os.Environment; @@ -1252,6 +1253,14 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_CROSS_LAYER_BLUR, 0); } + if (SensorPrivacyManager.USE_MICROPHONE_TOGGLE) { + addFeature(PackageManager.FEATURE_MICROPHONE_TOGGLE, 0); + } + + if (SensorPrivacyManager.USE_CAMERA_TOGGLE) { + addFeature(PackageManager.FEATURE_CAMERA_TOGGLE, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto index 4b1ee02a6c30..f64e146f87b7 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -144,7 +144,7 @@ message StateResidencyProto { */ optional int64 total_state_entry_count = 3; /** - * Last time this state was entered. Time in milliseconds since boot + * Last time this state was entered. Walltime in milliseconds since Unix epoch. */ optional int64 last_entry_timestamp_ms = 4; } @@ -208,7 +208,7 @@ message EnergyConsumerResultProto { /** Unique index identifying the energy consumer. */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ @@ -247,7 +247,7 @@ message EnergyMeasurementProto { */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7ad016c8ce47..a89043d813f5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5431,6 +5431,12 @@ @hide --> <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" android:protectionLevel="signature" /> + + <!-- @SystemApi Allows sensor privacy changes to be observed. + @hide --> + <permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES} intent. @hide --> diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index b819d9edb2a8..a1beecc83f43 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -71,6 +71,7 @@ import org.junit.runners.Suite; UserPowerCalculatorTest.class, VideoPowerCalculatorTest.class, WakelockPowerCalculatorTest.class, + WifiPowerCalculatorTest.class, com.android.internal.power.MeasuredEnergyStatsTest.class }) diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java new file mode 100644 index 000000000000..e1005457c289 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -0,0 +1,128 @@ +/* + * 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.internal.os; + + +import static com.google.common.truth.Truth.assertThat; + +import android.net.NetworkCapabilities; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; +import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WifiPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ON, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0); + + @Test + public void testPowerControllerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + WifiActivityEnergyInfo energyInfo = new WifiActivityEnergyInfo(10000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + + batteryStats.updateWifiState(energyInfo, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.2214666); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(5577); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.645200); + } + + @Test + public void testTimerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + batteryStats.noteWifiScanStartedLocked(APP_UID, 1000, 1000); + batteryStats.noteWifiScanStoppedLocked(APP_UID, 2000, 2000); + batteryStats.noteWifiRunningLocked(new WorkSource(APP_UID), 3000, 3000); + batteryStats.noteWifiStoppedLocked(new WorkSource(APP_UID), 4000, 4000); + batteryStats.noteWifiRunningLocked(new WorkSource(Process.WIFI_UID), 1111, 2222); + batteryStats.noteWifiStoppedLocked(new WorkSource(Process.WIFI_UID), 3333, 4444); + + // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively + // on the packet counts. + batteryStats.updateWifiState(/* energyInfo */ null, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1000); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8231573); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(2222); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8759216); + } +} diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java index 5fd5a7838c3a..d217bce24b9c 100644 --- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java @@ -81,11 +81,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats); @@ -114,11 +114,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); stats.writeToParcel(parcel); @@ -149,11 +149,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -185,17 +185,17 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true); - stats.updateCustomBucket(0, 315, true); - stats.updateCustomBucket(1, 316, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63); + stats.updateCustomBucket(0, 315); + stats.updateCustomBucket(1, 316); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -243,8 +243,8 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); // Accumulate energy in one bucket and one custom bucket, the rest should be zero - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateCustomBucket(1, 60); // Let's try parcelling with including zeros final Parcel includeZerosParcel = Parcel.obtain(); @@ -305,11 +305,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -331,14 +331,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -369,14 +369,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(0, 3, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(0, 3); assertEquals(15, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); assertEquals(ENERGY_DATA_UNAVAILABLE, @@ -409,10 +409,10 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(2, 13, true); - stats.updateCustomBucket(1, 70, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(2, 13); + stats.updateCustomBucket(1, 70); final long[] output = stats.getAccumulatedCustomBucketEnergies(); assertEquals(3, output.length); @@ -449,11 +449,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); MeasuredEnergyStats.resetIfNotNull(stats); // All energy should be reset to 0 @@ -471,10 +471,10 @@ public class MeasuredEnergyStatsTest { } // Values should increase as usual. - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70); assertEquals(70L, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); - stats.updateCustomBucket(1, 12, true); + stats.updateCustomBucket(1, 12); assertEquals(12L, stats.getAccumulatedCustomBucketEnergy(1)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index 4564af438f32..f118b1e0b7a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -50,6 +50,7 @@ import com.android.internal.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Contains information about the layout-properties of a display. This refers to internal layout @@ -82,6 +83,31 @@ public class DisplayLayout { private boolean mHasStatusBar = false; private int mNavBarFrameHeight = 0; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DisplayLayout)) return false; + final DisplayLayout other = (DisplayLayout) o; + return mUiMode == other.mUiMode + && mWidth == other.mWidth + && mHeight == other.mHeight + && Objects.equals(mCutout, other.mCutout) + && mRotation == other.mRotation + && mDensityDpi == other.mDensityDpi + && Objects.equals(mNonDecorInsets, other.mNonDecorInsets) + && Objects.equals(mStableInsets, other.mStableInsets) + && mHasNavigationBar == other.mHasNavigationBar + && mHasStatusBar == other.mHasStatusBar + && mNavBarFrameHeight == other.mNavBarFrameHeight; + } + + @Override + public int hashCode() { + return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi, + mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar, + mNavBarFrameHeight); + } + /** * Create empty layout. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 66560d339c5a..a52db24aa184 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -90,15 +90,15 @@ public class PipAnimationController { private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private PipTransitionAnimator mCurrentAnimator; - - private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = ThreadLocal.withInitial(() -> { AnimationHandler handler = new AnimationHandler(); handler.setProvider(new SfVsyncFrameCallbackProvider()); return handler; }); + private PipTransitionAnimator mCurrentAnimator; + public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; } @@ -268,6 +268,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override @@ -275,6 +276,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override public void onAnimationRepeat(Animator animation) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 7fff37d082b1..9a584c67f97c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -65,6 +65,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUtils; import java.io.PrintWriter; +import java.util.Objects; import java.util.function.Consumer; /** @@ -101,9 +102,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb */ private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { - if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { - // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update - // the display layout in the bounds handler in this case. + if (!mPipTaskOrganizer.isInPip() + || mPipBoundsState.getDisplayLayout().rotation() == toRotation + || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { + // Skip if the same rotation has been set or we aren't in PIP or haven't actually + // entered PIP yet. We still need to update the display layout in the bounds handler + // in this case. onDisplayRotationChangedNotInPip(mContext, toRotation); // do not forget to update the movement bounds as well. updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */, @@ -378,6 +382,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) { + if (Objects.equals(layout, mPipBoundsState.getDisplayLayout())) { + return; + } Runnable updateDisplayLayout = () -> { mPipBoundsState.setDisplayLayout(layout); updateMovementBounds(null /* toBounds */, @@ -476,8 +483,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb int launcherRotation, int shelfHeight) { setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); onDisplayRotationChangedNotInPip(mContext, launcherRotation); - return mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, + final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, pictureInPictureParams); + // sync mPipBoundsState with the newly calculated bounds. + mPipBoundsState.setNormalBounds(entryBounds); + return entryBounds; } private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 5e3966032a11..38b48e97771a 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -117,7 +117,8 @@ interface ILocationManager boolean isLocationEnabledForUser(int userId); void setLocationEnabledForUser(boolean enabled, int userId); - void addTestProvider(String name, in ProviderProperties properties, String packageName, @nullable String attributionTag); + void addTestProvider(String name, in ProviderProperties properties, + in List<String> locationTags, String packageName, @nullable String attributionTag); void removeTestProvider(String provider, String packageName, @nullable String attributionTag); void setTestProviderLocation(String provider, in Location location, String packageName, @nullable String attributionTag); void setTestProviderEnabled(String provider, boolean enabled, String packageName, @nullable String attributionTag); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index dd5b6e6b4222..95bae5ae7aab 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -76,6 +76,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -1957,12 +1958,32 @@ public class LocationManager { * allowed} for your app. */ public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) { + addTestProvider(provider, properties, Collections.emptySet()); + } + + /** + * Creates a test location provider and adds it to the set of active providers. This provider + * will replace any provider with the same name that exists prior to this call. + * + * @param provider the provider name + * @param properties the provider properties + * @param locationTags the attribution tags for accessing location from the provider + * + * @throws IllegalArgumentException if provider is null + * @throws IllegalArgumentException if properties is null + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + */ + public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties, + @NonNull Set<String> locationTags) { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(properties != null, "invalid null properties"); + Preconditions.checkArgument(locationTags != null, "invalid null location tags"); try { - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, new ArrayList<>(locationTags), + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index a6a0e7aa24ff..763835c9cbe2 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -21,6 +21,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; +import com.android.internal.annotations.Immutable; + +import java.util.Set; + /** * Location manager local system service interface. * @@ -39,6 +43,21 @@ public abstract class LocationManagerInternal { } /** + * Interface for getting callbacks when a location provider's location tags change. + * + * @see LocationTagInfo + */ + public interface OnProviderLocationTagsChangeListener { + + /** + * Called when the location tags for a provider change. + * + * @param providerLocationTagInfo The tag info for a provider. + */ + void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + } + + /** * Returns true if the given provider is enabled for the given user. * * @param provider A location provider as listed by {@link LocationManager#getAllProviders()} @@ -88,4 +107,60 @@ public abstract class LocationManagerInternal { * provider, and the elapsed nanos since boot the current time was computed at. */ public abstract @Nullable LocationTime getGnssTimeMillis(); + + /** + * Sets a listener for changes in the location providers' tags. Passing + * {@code null} clears the current listener. + * + * @param listener The listener. + */ + public abstract void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener); + + /** + * This class represents the location permission tags used by the location provider + * packages in a given UID. These tags are strictly used for accessing state guarded + * by the location permission(s) by a location provider which are required for the + * provider to fulfill its function as being a location provider. + */ + @Immutable + public static class LocationTagInfo { + private final int mUid; + + @NonNull + private final String mPackageName; + + @Nullable + private final Set<String> mLocationTags; + + public LocationTagInfo(int uid, @NonNull String packageName, + @Nullable Set<String> locationTags) { + mUid = uid; + mPackageName = packageName; + mLocationTags = locationTags; + } + + /** + * @return The UID for which tags are related. + */ + public int getUid() { + return mUid; + } + + /** + * @return The package for which tags are related. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * @return The tags for the package used for location related accesses. + */ + @Nullable + public Set<String> getTags() { + return mLocationTags; + } + } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 3669bf44c42a..4ae68762c08e 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -3571,26 +3571,28 @@ static DemuxFilterSettings getFilterConfiguration( .tpid = tpid, }; - DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); - switch (tsType) { - case DemuxTsFilterType::SECTION: - tsFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxTsFilterType::AUDIO: - case DemuxTsFilterType::VIDEO: - tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxTsFilterType::PES: - tsFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxTsFilterType::RECORD: - tsFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - default: - break; + if (settingsObj != NULL) { + DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); + switch (tsType) { + case DemuxTsFilterType::SECTION: + tsFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxTsFilterType::PES: + tsFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxTsFilterType::RECORD: + tsFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.ts(tsFilterSettings); break; @@ -3602,60 +3604,55 @@ static DemuxFilterSettings getFilterConfiguration( DemuxMmtpFilterSettings mmtpFilterSettings { .mmtpPid = mmtpPid, }; - DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); - switch (mmtpType) { - case DemuxMmtpFilterType::SECTION: - mmtpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::AUDIO: - case DemuxMmtpFilterType::VIDEO: - mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::PES: - mmtpFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::RECORD: - mmtpFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::DOWNLOAD: - mmtpFilterSettings.filterSettings.download( - getFilterDownloadSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); + switch (mmtpType) { + case DemuxMmtpFilterType::SECTION: + mmtpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::AUDIO: + case DemuxMmtpFilterType::VIDEO: + mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::PES: + mmtpFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::RECORD: + mmtpFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::DOWNLOAD: + mmtpFilterSettings.filterSettings.download( + getFilterDownloadSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.mmtp(mmtpFilterSettings); break; } case DemuxFilterMainType::IP: { DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj); - DemuxIpFilterSettings ipFilterSettings { .ipAddr = ipAddr, }; + DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype); - switch (ipType) { - case DemuxIpFilterType::SECTION: { - ipFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxIpFilterType::IP: { - jclass clazz = env->FindClass( - "android/media/tv/tuner/filter/IpFilterConfiguration"); - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - ipFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (ipType == DemuxIpFilterType::SECTION && settingsObj != NULL) { + ipFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (ipType == DemuxIpFilterType::IP) { + jclass clazz = env->FindClass( + "android/media/tv/tuner/filter/IpFilterConfiguration"); + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + ipFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.ip(ipFilterSettings); break; @@ -3672,24 +3669,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .isCompressedIpPacket = isCompressedIpPacket, }; + DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype); - switch (tlvType) { - case DemuxTlvFilterType::SECTION: { - tlvFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxTlvFilterType::TLV: { - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (tlvType == DemuxTlvFilterType::SECTION && settingsObj != NULL) { + tlvFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (tlvType == DemuxTlvFilterType::TLV) { + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.tlv(tlvFilterSettings); break; @@ -3704,14 +3694,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .lengthType = lengthType, }; - DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); - switch (alpType) { - case DemuxAlpFilterType::SECTION: - alpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); + switch (alpType) { + case DemuxAlpFilterType::SECTION: + alpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.alp(alpFilterSettings); break; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7ced202c5e44..f67e039c5ec3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -400,6 +400,7 @@ <!-- Permission required for CTS to test sensor privacy behavior --> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- Permission needed for CTS test - CallLogTest --> <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index fbe58c505662..044216e5423d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -87,6 +87,7 @@ <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- ActivityManager --> <uses-permission android:name="android.permission.REAL_GET_TASKS" /> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 571cbbcc77db..1ce87c1c0236 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -20,7 +20,6 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" - android:minHeight="48dp" android:paddingTop="12dp"> <LinearLayout android:id="@+id/label_group" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f13fdd34e4f3..0893c1488005 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,6 +91,9 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> + <!-- The number of columns in the Quick Settings customizer --> + <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> + <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 8cb1bc4878a5..5db3349b646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -69,7 +69,9 @@ public class NumPadButton extends AlphaOptimizedImageButton { @Override public boolean onTouchEvent(MotionEvent event) { - if (mAnimator != null) mAnimator.start(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (mAnimator != null) mAnimator.start(); + } return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index a4a781dc6ff5..e6a9d4fdd547 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -160,10 +160,9 @@ public class NumPadKey extends ViewGroup { public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); + if (mAnimator != null) mAnimator.start(); } - if (mAnimator != null) mAnimator.start(); - return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 9686c91f2c31..79f0688db869 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; @@ -137,8 +137,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAudioManager = audioManager; mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); @@ -159,8 +159,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); @@ -583,16 +583,16 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { - if (sensor == INDIVIDUAL_SENSOR_CAMERA) { + if (sensor == CAMERA) { mCameraDisabled = blocked; - } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { + } else if (sensor == MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d4678f39e404..127128dda112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -103,6 +103,11 @@ public class KeyguardIndicationRotateTextViewController extends */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean showImmediately) { + if (type == INDICATION_TYPE_NOW_PLAYING + || type == INDICATION_TYPE_REVERSE_CHARGING) { + // temporarily don't show here, instead use AmbientContainer b/181049781 + return; + } final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c8edaec98ee4..fe76668ab68b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -29,7 +29,6 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.tileimpl.QSTileBaseView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -56,7 +55,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final ArrayList<View> mAllViews = new ArrayList<>(); /** * List of {@link View}s representing Quick Settings that are being animated from the quick QS - * position to the normal QS panel. + * position to the normal QS panel. These views will only show once the animation is complete, + * to prevent overlapping of semi transparent views */ private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; @@ -233,7 +233,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Quick tiles. QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle(); getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view); getRelativePosition(loc2, tileIcon, view); @@ -255,11 +254,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - if (mFeatureFlags.isQSLabelsEnabled()) { - firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0); - mAllViews.add(qqsBgCircle); - } - } else { // These tiles disappear when expanding firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); @@ -271,7 +265,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationX); } - mQuickQsViews.add(tileView.getIconWithBackground()); + if (mFeatureFlags.isQSLabelsEnabled()) { + mQuickQsViews.add(tileView); + } else { + mQuickQsViews.add(tileView.getIconWithBackground()); + } mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -362,7 +360,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if(view == parent || view == null) return; // Ignore tile pages as they can have some offset we don't want to take into account in // RTL. - if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) { + if (!isAPage(view)) { loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } @@ -374,6 +372,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha getRelativePositionInt(loc1, (View) view.getParent(), parent); } + // Returns true if the view is a possible page in PagedTileLayout + private boolean isAPage(View view) { + if (view instanceof PagedTileLayout.TilePage) { + return true; + } else if (view instanceof SideLabelTileLayout) { + return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout); + } + return false; + } + public void setPosition(float position) { if (mNeedsAnimatorUpdate) { updateAnimators(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 91ae571d1cfb..05633071be86 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -112,7 +112,7 @@ public class QSPanel extends LinearLayout implements Tunable { private int mMediaTotalBottomMargin; private int mFooterMarginStartHorizontal; private Consumer<Boolean> mMediaVisibilityChangedListener; - private boolean mSideLabels; + protected boolean mSideLabels; public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -201,16 +201,20 @@ public class QSPanel extends LinearLayout implements Tunable { mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages()); } - // Allow the UI to be as big as it want's to, we're in a scroll view - int newHeight = 10000; - int availableHeight = MeasureSpec.getSize(heightMeasureSpec); - int excessHeight = newHeight - availableHeight; - // Measure with EXACTLY. That way, The content will only use excess height and will - // be measured last, after other views and padding is accounted for. This only - // works because our Layouts in here remeasure themselves with the exact content - // height. - heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); - ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + // In landscape, mTileLayout's parent is not the panel but a view that contains the + // tile layout and the media controls. + if (((View) mTileLayout).getParent() == this) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -758,7 +762,7 @@ public class QSPanel extends LinearLayout implements Tunable { // Let's use 3 columns to match the current layout int columns; if (mSideLabels) { - columns = horizontal ? 1 : 2; + columns = horizontal ? 2 : 4; } else { columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index fcb35e2040ea..d60801ef2d03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -18,7 +18,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import android.annotation.NonNull; @@ -66,7 +65,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private BrightnessMirrorController mBrightnessMirrorController; private boolean mGridContentVisible = true; - private boolean mQsLabelsFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -93,7 +91,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSlider.Factory brightnessSliderFactory, - @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); @@ -108,9 +105,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mView.setBrightnessView(mBrightnessSlider.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider); - - mQsLabelsFlag = qsLabelsFlag; - mSideLabels = qsLabelsFlag; } @Override @@ -329,7 +323,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onTuningChanged(String key, String newValue) { if (QS_REMOVE_LABELS.equals(key)) { - if (!mQsLabelsFlag) return; + if (!mQSLabelFlag) return; boolean newShowLabels = newValue == null || "0".equals(newValue); if (mShowLabels == newShowLabels) return; mShowLabels = newShowLabels; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 9426e7122c1c..f1174fbe7aef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -75,7 +75,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; protected boolean mShowLabels = true; - protected boolean mSideLabels; + protected boolean mQSLabelFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -118,11 +118,12 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQSLogger = qsLogger; mDumpManager = dumpManager; mFeatureFlags = featureFlags; + mQSLabelFlag = featureFlags.isQSLabelsEnabled(); } @Override protected void onInit() { - mView.initialize(mSideLabels); + mView.initialize(mQSLabelFlag); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index a29ac3bb77e9..9b66b59c06df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -69,12 +69,22 @@ public class QuickQSPanel extends QSPanel { @Override public TileLayout createRegularTileLayout() { - return new QuickQSPanel.HeaderTileLayout(mContext); + if (mSideLabels) { + return new QQSSideLabelTileLayout(mContext); + } else { + return new QuickQSPanel.HeaderTileLayout(mContext); + } } @Override protected QSTileLayout createHorizontalTileLayout() { - return new DoubleLineTileLayout(mContext); + if (mSideLabels) { + TileLayout t = createRegularTileLayout(); + t.setMaxColumns(2); + return t; + } else { + return new DoubleLineTileLayout(mContext); + } } @Override @@ -331,4 +341,38 @@ public class QuickQSPanel extends QSPanel { } } } + + static class QQSSideLabelTileLayout extends SideLabelTileLayout { + QQSSideLabelTileLayout(Context context) { + super(context, null); + setClipChildren(false); + setClipToPadding(false); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_HORIZONTAL; + setLayoutParams(lp); + setMaxColumns(4); + } + + @Override + public boolean updateResources() { + boolean b = super.updateResources(); + mMaxAllowedRows = 2; + return b; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateResources(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Make sure to always use the correct number of rows. As it's determined by the + // columns, just use as many as needed. + updateMaxRows(10000, mRecords.size()); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 383e932a6955..671f8f7dd2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import com.android.internal.logging.MetricsLogger; @@ -42,8 +41,6 @@ import javax.inject.Named; @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { - private boolean mUseSideLabels; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = newConfig -> { int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); @@ -58,12 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, - FeatureFlags featureFlags + DumpManager dumpManager, FeatureFlags featureFlags ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); - mUseSideLabels = qsLabelsFlag; } @Override @@ -104,19 +99,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> break; } } - if (mUseSideLabels) { - List<QSTile> newTiles = new ArrayList<>(); - for (int i = 0; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - for (int i = 1; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - super.setTiles(newTiles, true); - - } else { - super.setTiles(tiles, true); - } + super.setTiles(tiles, !mQSLabelFlag); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 74a7ac1cb6dd..4de4a78e9e8a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -20,11 +20,13 @@ import android.content.Context import android.util.AttributeSet import com.android.systemui.R -open class SideLabelTileLayout(context: Context, attrs: AttributeSet) : TileLayout(context, attrs) { +open class SideLabelTileLayout( + context: Context, + attrs: AttributeSet? +) : TileLayout(context, attrs) { override fun updateResources(): Boolean { return super.updateResources().also { - mResourceColumns = 2 mMaxAllowedRows = 4 mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() mCellMarginVertical = mCellMarginHorizontal diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index d559e07f3ff6..14cbf980df23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -124,6 +124,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + updateColumns(); mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 048fdc3a0e5a..7a91421b00a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -75,6 +75,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private final Context mContext; private final Handler mHandler = new Handler(); @@ -87,6 +89,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private int mEditIndex; private int mTileDividerIndex; private int mFocusIndex; + private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -109,7 +112,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mDecoration = new TileItemDecoration(context); mMarginDecoration = new MarginTileDecoration(); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); - mNumColumns = context.getResources().getInteger(R.integer.quick_settings_num_columns); + mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); } @@ -129,7 +132,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta * @return {@code true} if the number of columns changed, {@code false} otherwise */ public boolean updateNumColumns() { - int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns); + int numColumns = mContext.getResources().getInteger(NUM_COLUMNS_ID); if (numColumns != mNumColumns) { mNumColumns = numColumns; return true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index a699e2ec7cfc..424aafac1f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -105,22 +105,25 @@ public class QSTileView extends QSTileBaseView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mLabel.setSingleLine(false); super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Remeasure view if the primary label requires more then 2 lines or the secondary label - // text will be cut off. - if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText()) - && mSecondLine.getLineHeight() > mSecondLine.getHeight()) { - if (!mLabel.isSingleLine()) { - mLabel.setSingleLine(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } else { - if (mLabel.isSingleLine()) { - mLabel.setSingleLine(false); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + // Remeasure view if the primary label requires more than mMaxLabelLines lines or the + // secondary label text will be cut off. + if (shouldLabelBeSingleLine()) { + mLabel.setSingleLine(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private boolean shouldLabelBeSingleLine() { + if (mLabel.getLineCount() > mMaxLabelLines) { + return true; + } else if (!TextUtils.isEmpty(mSecondLine.getText()) + && mLabel.getLineCount() > mMaxLabelLines - 1) { + return true; } + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index dc81b702021f..c98de8cb8fab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -24,8 +24,8 @@ import android.graphics.drawable.PaintDrawable import android.graphics.drawable.RippleDrawable import android.service.quicksettings.Tile.STATE_ACTIVE import android.view.Gravity -import android.view.View import android.widget.LinearLayout +import android.widget.RelativeLayout import com.android.systemui.R import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile @@ -37,7 +37,6 @@ class QSTileViewHorizontal( ) : QSTileView(context, icon, false) { private var paintDrawable: PaintDrawable? = null - private var divider: View? = null init { orientation = HORIZONTAL @@ -49,7 +48,12 @@ class QSTileViewHorizontal( override fun createLabel() { super.createLabel() - findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START + findViewById<LinearLayout>(R.id.label_group)?.apply { + gravity = Gravity.START + (layoutParams as? RelativeLayout.LayoutParams)?.apply { + removeRule(RelativeLayout.ALIGN_PARENT_TOP) + } + } mLabel.gravity = Gravity.START mLabel.textDirection = TEXT_DIRECTION_LOCALE mSecondLine.gravity = Gravity.START @@ -57,7 +61,7 @@ class QSTileViewHorizontal( val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) mLabelContainer.setPaddingRelative(0, padding, padding, padding) (mLabelContainer.layoutParams as LayoutParams).gravity = - Gravity.CENTER_VERTICAL or Gravity.START + Gravity.CENTER_VERTICAL or Gravity.START } override fun updateRippleSize() { @@ -93,7 +97,6 @@ class QSTileViewHorizontal( paintDrawable?.setTint(getCircleColor(state.state)) mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - divider?.backgroundTintList = mLabel.textColors } override fun handleExpand(dualTarget: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 3841daca7ebe..70287cd37d01 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; +import static android.content.pm.PackageManager.FEATURE_CAMERA_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,8 +61,8 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return /*getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) - && */whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", false)); } @@ -75,7 +78,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return CAMERA; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2f0071a1f198..e9b712df2154 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; +import static android.content.pm.PackageManager.FEATURE_MICROPHONE_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,7 +61,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager() + .hasSystemFeature(FEATURE_MICROPHONE_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", false)); } @@ -74,7 +79,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return MICROPHONE; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 00703e7f8403..0c582bdbe12f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles; import android.content.Intent; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; @@ -49,7 +49,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS /** * @return Id of the sensor that will be toggled */ - public abstract @IndividualSensor int getSensorId(); + public abstract @Sensor int getSensorId(); /** * @return icon for the QS tile diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 9f182e19efaf..658613796498 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -25,8 +25,8 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_SENSOR -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE import android.os.Bundle import android.os.Handler import android.text.Html @@ -81,7 +81,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene dismiss() } } - if (!sensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)) { + if (!sensorPrivacyManager.isSensorPrivacyEnabled(sensor)) { finish() return } @@ -89,9 +89,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene mAlertParams.apply { try { mMessage = Html.fromHtml(getString(when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> R.string.sensor_privacy_start_use_mic_dialog_content - INDIVIDUAL_SENSOR_CAMERA -> + CAMERA -> R.string.sensor_privacy_start_use_camera_dialog_content else -> Resources.ID_NULL }, packageManager.getApplicationInfo(sensorUsePackageName, 0) @@ -102,9 +102,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } mIconId = when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> com.android.internal.R.drawable.perm_group_microphone - INDIVIDUAL_SENSOR_CAMERA -> com.android.internal.R.drawable.perm_group_camera + CAMERA -> com.android.internal.R.drawable.perm_group_camera else -> Resources.ID_NULL } @@ -121,7 +121,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene override fun onStart() { super.onStart() - sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true) + sensorPrivacyManager.suppressSensorPrivacyReminders(sensorUsePackageName, true) unsuppressImmediately = false } @@ -156,11 +156,11 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene if (unsuppressImmediately) { sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) } else { Handler(mainLooper).postDelayed({ sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) } } @@ -170,7 +170,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } private fun disableSensorPrivacy() { - sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false) + sensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, false) unsuppressImmediately = true setResult(RESULT_OK) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index a76d08a438f2..7f935d28285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.policy; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); - boolean isSensorBlocked(@IndividualSensor int sensor); + boolean isSensorBlocked(@Sensor int sensor); - void setSensorBlocked(@IndividualSensor int sensor, boolean blocked); + void setSensorBlocked(@Sensor int sensor, boolean blocked); interface Callback { - void onSensorBlockedChanged(@IndividualSensor int sensor, boolean blocked); + void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 32d15ed41648..295df05797ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.policy; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.util.ArraySet; import android.util.SparseBooleanArray; @@ -30,8 +30,7 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { - private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, - INDIVIDUAL_SENSOR_MICROPHONE}; + private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); @@ -48,18 +47,18 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mSensorPrivacyManager.addSensorPrivacyListener(sensor, (enabled) -> onSensorPrivacyChanged(sensor, enabled)); - mState.put(sensor, mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)); + mState.put(sensor, mSensorPrivacyManager.isSensorPrivacyEnabled(sensor)); } } @Override - public boolean isSensorBlocked(@IndividualSensor int sensor) { + public boolean isSensorBlocked(@Sensor int sensor) { return mState.get(sensor, false); } @Override - public void setSensorBlocked(@IndividualSensor int sensor, boolean blocked) { - mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, blocked); + public void setSensorBlocked(@Sensor int sensor, boolean blocked) { + mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked); } @Override @@ -72,7 +71,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mCallbacks.remove(listener); } - private void onSensorPrivacyChanged(@IndividualSensor int sensor, boolean blocked) { + private void onSensorPrivacyChanged(@Sensor int sensor, boolean blocked) { mState.put(sensor, blocked); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 9effc6728bab..bbb2f1a5259a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -161,10 +161,10 @@ public class ThemeOverlayApplier implements Dumpable { void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, - Set<UserHandle> userHandles) { + int currentUser, + Set<UserHandle> managedProfiles) { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); - overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()); @@ -175,6 +175,7 @@ public class ThemeOverlayApplier implements Dumpable { .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) + .filter(o -> !categoryToPackage.containsValue(new OverlayIdentifier(o.packageName))) .filter(o -> o.isEnabled()) .map(o -> new Pair<>(o.category, o.packageName)) .collect(Collectors.toList()); @@ -186,17 +187,18 @@ public class ThemeOverlayApplier implements Dumpable { } } - // Toggle overlays in the order of THEME_CATEGORIES. + for (Pair<String, String> packageToDisable : overlaysToDisable) { + OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); + setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser, + managedProfiles, false); + } + for (String category : THEME_CATEGORIES) { if (categoryToPackage.containsKey(category)) { OverlayIdentifier overlayInfo = categoryToPackage.get(category); - setEnabled(transaction, overlayInfo, category, userHandles, true); + setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, true); } } - for (Pair<String, String> packageToDisable : overlaysToDisable) { - OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); - setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false); - } mExecutor.execute(() -> { try { @@ -213,18 +215,30 @@ public class ThemeOverlayApplier implements Dumpable { } private void setEnabled(OverlayManagerTransaction.Builder transaction, - OverlayIdentifier identifier, String category, Set<UserHandle> handles, - boolean enabled) { + OverlayIdentifier identifier, String category, int currentUser, + Set<UserHandle> managedProfiles, boolean enabled) { if (DEBUG) { Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " + category + ": " + enabled); } - for (UserHandle userHandle : handles) { - transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); - } - if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { + + transaction.setEnabled(identifier, enabled, currentUser); + if (currentUser != UserHandle.SYSTEM.getIdentifier() + && SYSTEM_USER_CATEGORIES.contains(category)) { transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); } + + // Do not apply Launcher or Theme picker overlays to managed users. Apps are not + // installed in there. + OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM); + if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage) + || overlayInfo.targetPackageName.equals(mThemePickerPackage)) { + return; + } + + for (UserHandle userHandle : managedProfiles) { + transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 028cbd084677..5d028454a417 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -55,14 +55,13 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; -import com.google.android.collect.Sets; - import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -151,7 +150,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } }, filter, mBgExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( @@ -163,7 +162,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } } }, @@ -180,7 +179,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mLockColors = lockColors; } mSystemColors = systemColor; - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }); }); if (USE_LOCK_SCREEN_WALLPAPER) { @@ -192,7 +191,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } // It's possible that the user has a lock screen wallpaper. On this case we'll // end up with different colors after unlocking. - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); } }); } @@ -209,11 +208,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); } } - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }, null, UserHandle.USER_ALL); } - private void reevaluateSystemTheme() { + private void reevaluateSystemTheme(boolean forceReload) { WallpaperColors currentColors = mKeyguardStateController.isShowing() && mLockColors != null ? mLockColors : mSystemColors; @@ -228,7 +227,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { accentCandidate = getAccentColor(currentColors); } - if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate) { + if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate + && !forceReload) { return; } @@ -309,6 +309,16 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); } + } else if (!mIsMonetEnabled && systemPalette != null) { + try { + // It's possible that we flipped the flag off and still have a @ColorInt in the + // setting. We need to sanitize the input, otherwise the overlay transaction will + // fail. + Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Same for accent color. @@ -322,6 +332,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); } + } else if (!mIsMonetEnabled && accentPalette != null) { + try { + Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Compatibility with legacy themes, where full packages were defined, instead of just @@ -337,10 +354,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier()); } - Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); + Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { - userHandles.add(userInfo.getUserHandle()); + managedProfiles.add(userInfo.getUserHandle()); } } if (DEBUG) { @@ -352,9 +369,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay - }, userHandles); + }, currentUser, managedProfiles); } else { - mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); + mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, + managedProfiles); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 97cb8736f01c..2526990dfd40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static junit.framework.TestCase.assertFalse; @@ -125,9 +125,9 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); mController = new AppOpsControllerImpl( @@ -505,7 +505,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add a camera op, and disable the microphone. The camera op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true); + mController.onSensorBlockedChanged(MICROPHONE, true); mController.onOpActiveChanged( AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -515,7 +515,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Re enable the microphone, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false); + mController.onSensorBlockedChanged(MICROPHONE, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); @@ -538,7 +538,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add an audio op, and disable the camera. The audio op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true); + mController.onSensorBlockedChanged(CAMERA, true); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -547,7 +547,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); // Re enable the camera, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false); + mController.onSensorBlockedChanged(CAMERA, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 0dfebab59feb..a2a179ea29df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -121,7 +121,6 @@ public class QSPanelControllerTest extends SysuiTestCase { mQSTileHost, mQSCustomizerController, true, mMediaHost, mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - /* labelsFlag */ false, mFeatureFlags ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 587020090433..cb380d51bd79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -87,7 +87,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - false, featureFlags ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index e798207c6947..6067b42e0ef8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -32,7 +32,6 @@ import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE; import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -88,7 +87,9 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String THEMEPICKER_PACKAGE = "com.android.wallpaper"; private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; private static final UserHandle TEST_USER = UserHandle.of(5); - private static final Set<UserHandle> TEST_USER_HANDLES = Sets.newHashSet(TEST_USER); + private static final UserHandle TEST_USER_MANAGED_PROFILE = UserHandle.of(6); + private static final Set<UserHandle> TEST_USER_HANDLES = + Sets.newHashSet(TEST_USER_MANAGED_PROFILE); @Mock OverlayManager mOverlayManager; @@ -159,13 +160,19 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER, THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true))); + + OverlayInfo launcherTargetInfo = new OverlayInfo("packageName", LAUNCHER_PACKAGE, + null, null, "/", 0, 0, 0, false); + when(mOverlayManager.getOverlayInfo(any(OverlayIdentifier.class), any())) + .thenReturn(launcherTargetInfo); clearInvocations(mOverlayManager); verify(mDumpManager).registerDumpable(any(), any()); } @Test public void allCategoriesSpecified_allEnabledExclusively() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mOverlayManager).commit(any()); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { @@ -176,7 +183,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { @@ -192,27 +200,25 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + userHandles); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), eq(TEST_USER.getIdentifier())); - verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), - eq(newUserHandle.getIdentifier())); + // Not enabled for work profile because the target package is LAUNCHER_PACKAGE + verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER_MANAGED_PROFILE.getIdentifier())); } } @Test public void applyCurrentUserOverlays_createsPendingOverlays() { - Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] { + FabricatedOverlay[] pendingCreation = new FabricatedOverlay[]{ mock(FabricatedOverlay.class) }; - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, + TEST_USER.getIdentifier(), TEST_USER_HANDLES); for (FabricatedOverlay overlay : pendingCreation) { verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); @@ -220,20 +226,13 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { } @Test - public void allCategoriesSpecified_overlayManagerNotQueried() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); - - verify(mOverlayManager, never()) - .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); - } - - @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -249,7 +248,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { - mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -263,7 +263,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), @@ -273,23 +274,6 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { eq(TEST_USER.getIdentifier())); } - @Test - public void overlayManagerOnlyQueriedForUnspecifiedPackages() { - Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); - categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); - - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); - - verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(LAUNCHER_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(THEMEPICKER_PACKAGE, - UserHandle.SYSTEM); - } - private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { return new OverlayInfo(packageName, null, targetPackageName, null, category, "", diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index aa385effa931..d80c40fcd07b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -143,7 +143,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -175,7 +175,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -198,7 +198,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ebfa9171b2b9..21cae453d702 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -742,6 +742,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } } + + if (association.isNotifyOnDeviceNearby()) { + ServiceConnector<ICompanionDeviceService> serviceConnector = + mDeviceListenerServiceConnectors.forUser(association.getUserId()) + .get(association.getPackageName()); + if (serviceConnector != null) { + serviceConnector.unbind(); + } + } } private void updateSpecialAccessPermissionForAssociatedPackage(Association association) { diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 84e429dc83e4..f2782f64995a 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -25,9 +25,9 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.os.UserHandle.USER_SYSTEM; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; @@ -406,12 +406,12 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void setSensorPrivacy(boolean enable) { + enforceManageSensorPrivacyPermission(); // Keep the state consistent between all users to make it a single global state forAllUsers(userId -> setSensorPrivacy(userId, enable)); } private void setSensorPrivacy(@UserIdInt int userId, boolean enable) { - enforceSensorPrivacyPermission(); synchronized (mLock) { mEnabled.put(userId, enable); persistSensorPrivacyStateLocked(); @@ -421,7 +421,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) { - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId, new SparseBooleanArray()); @@ -448,6 +448,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor, boolean enable) { + enforceManageSensorPrivacyPermission(); int parentId = mUserManagerInternal.getProfileParentId(userId); forAllUsers(userId2 -> { if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { @@ -460,21 +461,35 @@ public final class SensorPrivacyService extends SystemService { * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ - private void enforceSensorPrivacyPermission() { - if (mContext.checkCallingOrSelfPermission( - MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { - return; - } - throw new SecurityException( + private void enforceManageSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY, "Changing sensor privacy requires the following permission: " + MANAGE_SENSOR_PRIVACY); } /** + * Enforces the caller contains the necessary permission to observe changes to the sate of + * sensor privacy. + */ + private void enforceObserveSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY, + "Observing sensor privacy changes requires the following permission: " + + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY); + } + + private void enforcePermission(String permission, String message) { + if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return; + } + throw new SecurityException(message); + } + + /** * Returns whether sensor privacy is enabled. */ @Override public boolean isSensorPrivacyEnabled() { + enforceObserveSensorPrivacyPermission(); return isSensorPrivacyEnabled(USER_SYSTEM); } @@ -486,6 +501,7 @@ public final class SensorPrivacyService extends SystemService { @Override public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { + enforceObserveSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray states = mIndividualEnabled.get(userId); if (states == null) { @@ -703,6 +719,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void addSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -715,6 +732,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void addIndividualSensorPrivacyListener(int userId, int sensor, ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -726,6 +744,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void removeSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -735,6 +754,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void suppressIndividualSensorPrivacyReminders(int userId, String packageName, IBinder token, boolean suppress) { + enforceManageSensorPrivacyPermission(); Objects.requireNonNull(packageName); Objects.requireNonNull(token); @@ -886,13 +906,13 @@ public final class SensorPrivacyService extends SystemService { } /** - * Convert a string into a {@link SensorPrivacyManager.IndividualSensor id}. + * Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}. * * @param sensor The name to convert * * @return The id corresponding to the name */ - private @SensorPrivacyManager.IndividualSensor int sensorStrToId(@Nullable String sensor) { + private @SensorPrivacyManager.Sensors.Sensor int sensorStrToId(@Nullable String sensor) { if (sensor == null) { return UNKNOWN; } @@ -950,7 +970,7 @@ public final class SensorPrivacyService extends SystemService { return -1; } - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray individualEnabled = diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index c2d8fa24157a..8a2894c84cc4 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -45,14 +45,18 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.Immutable; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -87,26 +91,30 @@ public class ServiceWatcher implements ServiceConnection { /** Function to run on binder interface when first bound. */ public interface OnBindRunner { /** Called to run client code with the binder. */ - void run(IBinder binder, ComponentName service) throws RemoteException; + void run(IBinder binder, BoundService service) throws RemoteException; } /** * Information on the service ServiceWatcher has selected as the best option for binding. */ - private static final class ServiceInfo implements Comparable<ServiceInfo> { + @Immutable + public static final class BoundService implements Comparable<BoundService> { - public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null, - UserHandle.USER_NULL, false); + public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null, + false, null, -1); public final int version; - @Nullable public final ComponentName component; - @UserIdInt public final int userId; + @Nullable + public final ComponentName component; public final boolean serviceIsMultiuser; + public final int uid; + @Nullable + public final Bundle metadata; - ServiceInfo(ResolveInfo resolveInfo, int currentUserId) { + BoundService(ResolveInfo resolveInfo) { Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); - Bundle metadata = resolveInfo.serviceInfo.metaData; + metadata = resolveInfo.serviceInfo.metaData; if (metadata != null) { version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); @@ -116,16 +124,17 @@ public class ServiceWatcher implements ServiceConnection { } component = resolveInfo.serviceInfo.getComponentName(); - userId = serviceIsMultiuser ? UserHandle.USER_SYSTEM : currentUserId; + uid = resolveInfo.serviceInfo.applicationInfo.uid; } - private ServiceInfo(int version, @Nullable ComponentName component, int userId, - boolean serviceIsMultiuser) { + private BoundService(int version, @Nullable ComponentName component, + boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) { Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE); this.version = version; this.component = component; - this.userId = userId; this.serviceIsMultiuser = serviceIsMultiuser; + this.metadata = metadata; + this.uid = uid; } public @Nullable String getPackageName() { @@ -137,21 +146,21 @@ public class ServiceWatcher implements ServiceConnection { if (this == o) { return true; } - if (!(o instanceof ServiceInfo)) { + if (!(o instanceof BoundService)) { return false; } - ServiceInfo that = (ServiceInfo) o; - return version == that.version && userId == that.userId + BoundService that = (BoundService) o; + return version == that.version && uid == that.uid && Objects.equals(component, that.component); } @Override public int hashCode() { - return Objects.hash(version, component, userId); + return Objects.hash(version, component, uid); } @Override - public int compareTo(ServiceInfo that) { + public int compareTo(BoundService that) { // ServiceInfos with higher version numbers always win (having a version number > // MIN_VALUE implies having a non-null component). if version numbers are equal, a // non-null component wins over a null component. if the version numbers are equal and @@ -164,10 +173,11 @@ public class ServiceWatcher implements ServiceConnection { } else if (component != null && that.component == null) { ret = 1; } else { - if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) { + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) { ret = -1; - } else if (userId == UserHandle.USER_SYSTEM - && that.userId != UserHandle.USER_SYSTEM) { + } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) { ret = 1; } } @@ -180,7 +190,8 @@ public class ServiceWatcher implements ServiceConnection { if (component == null) { return "none"; } else { - return component.toShortString() + "@" + version + "[u" + userId + "]"; + return component.toShortString() + "@" + version + "[u" + + UserHandle.getUserId(uid) + "]"; } } } @@ -227,17 +238,23 @@ public class ServiceWatcher implements ServiceConnection { } }; - @Nullable private final OnBindRunner mOnBind; - @Nullable private final Runnable mOnUnbind; + // read/write from handler thread only + private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>(); + + @Nullable + private final OnBindRunner mOnBind; + + @Nullable + private final Runnable mOnUnbind; - // write from caller thread only, read anywhere - private volatile boolean mRegistered; + // read/write from handler thread only + private boolean mRegistered; // read/write from handler thread only private int mCurrentUserId; // write from handler thread only, read anywhere - private volatile ServiceInfo mTargetService; + private volatile BoundService mTargetService; private volatile IBinder mBinder; public ServiceWatcher(Context context, String action, @@ -274,7 +291,7 @@ public class ServiceWatcher implements ServiceConnection { mCurrentUserId = UserHandle.USER_NULL; - mTargetService = ServiceInfo.NONE; + mTargetService = BoundService.NONE; mBinder = null; } @@ -299,6 +316,11 @@ public class ServiceWatcher implements ServiceConnection { * Starts the process of determining the best matching service and maintaining a binding to it. */ public void register() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal, + ServiceWatcher.this)); + } + + private void registerInternal() { Preconditions.checkState(!mRegistered); mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler); @@ -309,6 +331,8 @@ public class ServiceWatcher implements ServiceConnection { mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler); + // TODO: This makes the behavior of the class unpredictable as the caller needs + // to know the internal impl detail that calling register would pick the current user. mCurrentUserId = ActivityManager.getCurrentUser(); mRegistered = true; @@ -320,6 +344,11 @@ public class ServiceWatcher implements ServiceConnection { * Stops the process of determining the best matching service and releases any binding. */ public void unregister() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal, + ServiceWatcher.this)); + } + + private void unregisterInternal() { Preconditions.checkState(mRegistered); mRegistered = false; @@ -333,7 +362,7 @@ public class ServiceWatcher implements ServiceConnection { private void onBestServiceChanged(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - ServiceInfo bestServiceInfo = ServiceInfo.NONE; + BoundService bestServiceInfo = BoundService.NONE; if (mRegistered) { List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( @@ -344,7 +373,7 @@ public class ServiceWatcher implements ServiceConnection { if (!mServiceCheckPredicate.test(resolveInfo)) { continue; } - ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId); + BoundService serviceInfo = new BoundService(resolveInfo); if (serviceInfo.compareTo(bestServiceInfo) > 0) { bestServiceInfo = serviceInfo; } @@ -356,21 +385,22 @@ public class ServiceWatcher implements ServiceConnection { } } - private void rebind(ServiceInfo newServiceInfo) { + private void rebind(BoundService newServiceInfo) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - if (!mTargetService.equals(ServiceInfo.NONE)) { + if (!mTargetService.equals(BoundService.NONE)) { if (D) { Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService); } mContext.unbindService(this); onServiceDisconnected(mTargetService.component); - mTargetService = ServiceInfo.NONE; + mPendingBinds.remove(mTargetService.component); + mTargetService = BoundService.NONE; } mTargetService = newServiceInfo; - if (mTargetService.equals(ServiceInfo.NONE)) { + if (mTargetService.equals(BoundService.NONE)) { return; } @@ -381,10 +411,12 @@ public class ServiceWatcher implements ServiceConnection { Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component); if (!mContext.bindServiceAsUser(bindIntent, this, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, - mHandler, UserHandle.of(mTargetService.userId))) { - mTargetService = ServiceInfo.NONE; + mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) { + mTargetService = BoundService.NONE; Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later"); mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS); + } else { + mPendingBinds.put(mTargetService.component, mTargetService); } } @@ -397,10 +429,15 @@ public class ServiceWatcher implements ServiceConnection { Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString()); } + final BoundService boundService = mPendingBinds.remove(component); + if (boundService == null) { + return; + } + mBinder = binder; if (mOnBind != null) { try { - mOnBind.run(binder, component); + mOnBind.run(binder, boundService); } catch (RuntimeException | RemoteException e) { // binders may propagate some specific non-RemoteExceptions from the other side // through the binder as well - we cannot allow those to crash the system server diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 33c0f9d628dc..a36d913ee669 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -307,6 +307,7 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessMap; import com.android.internal.app.SystemUserHomeActivity; +import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.content.PackageHelper; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -1820,6 +1821,15 @@ public class ActivityManagerService extends IActivityManager.Stub ncl.start(); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + mAppOpsService.setAppOpsPolicy(appOpsPolicy); + } + public IAppOpsService getAppOpsService() { return mAppOpsService; } @@ -3779,10 +3789,11 @@ public class ActivityManagerService extends IActivityManager.Stub mi.getTotalUss(), mi.getTotalRss(), false, ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration); proc.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), + state != null ? state.getName() : proc.processName, + state != null ? state.getPackage() : proc.info.packageName, mi.getTotalPss(), mi.getTotalUss(), mi.getTotalRss(), @@ -7214,6 +7225,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long memoryGrowthThreshold = Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD); mProcessList.forEachLruProcessesLOSP(false, proc -> { + if (proc.getThread() == null) { + return; + } final ProcessProfileRecord pr = proc.mProfile; final ProcessStateRecord state = proc.mState; final int setProcState = state.getSetProcState(); @@ -13969,6 +13983,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long uptimeSince = curUptime - mLastPowerCheckUptime; mLastPowerCheckUptime = curUptime; mProcessList.forEachLruProcessesLOSP(false, app -> { + if (app.getThread() == null) { + return; + } if (app.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) { int cpuLimit; long checkDur = curUptime - app.mState.getWhenUnimportant(); @@ -14070,11 +14087,12 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName, uptimeSince, cputimeUsed); app.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write( FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED, app.info.uid, processName, - holder.state.getPackage(), + state != null ? state.getPackage() : app.info.packageName, holder.appVersion); }); return true; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a776458d63c5..44dcc205a9d0 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -339,7 +339,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; @GuardedBy("this") - private CheckOpsDelegate mCheckOpsDelegate; + private CheckOpsDelegate mAppOpsPolicy; + + @GuardedBy("this") + private CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher; /** * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never @@ -1770,6 +1773,17 @@ public class AppOpsService extends IAppOpsService.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + synchronized (AppOpsService.this) { + mAppOpsPolicy = appOpsPolicy; + } + } + public void packageRemoved(int uid, String packageName) { synchronized (this) { UidState uidState = mUidStates.get(uid); @@ -2868,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (this) { - return mCheckOpsDelegate; + return (mCheckOpsDelegateDispatcher != null) + ? mCheckOpsDelegateDispatcher.getCheckOpsDelegate() + : null; } } public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (this) { - mCheckOpsDelegate = delegate; + if (delegate != null) { + mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(delegate); + } else { + mCheckOpsDelegateDispatcher = null; + } } } @@ -2889,19 +2909,28 @@ public class AppOpsService extends IAppOpsService.Stub { } private int checkOperationInternal(int code, int uid, String packageName, boolean raw) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkOperationImpl(code, uid, packageName, raw); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkOperation(code, uid, packageName, raw, + delegateDispatcher::checkOperationImpl); + } else { + return policy.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkOperation(code, uid, + packageName, raw, AppOpsService.this::checkOperationImpl); } - return delegate.checkOperation(code, uid, packageName, raw, - AppOpsService.this::checkOperationImpl); + return checkOperationImpl(code, uid, packageName, raw); } - private int checkOperationImpl(int code, int uid, String packageName, - boolean raw) { + private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { verifyIncomingOp(code); verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); @@ -2956,15 +2985,25 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkAudioOperationImpl(code, usage, uid, packageName); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkAudioOperation(code, usage, uid, packageName, + delegateDispatcher::checkAudioOperationImpl); + } else { + return policy.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkAudioOperation(code, usage, + uid, packageName, AppOpsService.this::checkAudioOperationImpl); } - return delegate.checkAudioOperation(code, usage, uid, packageName, - AppOpsService.this::checkAudioOperationImpl); + return checkAudioOperationImpl(code, usage, uid, packageName); } private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { @@ -3078,17 +3117,29 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int noteOperation(int code, int uid, String packageName, String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; - } - if (delegate == null) { - return noteOperationImpl(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; + } + if (policy != null) { + if (delegateDispatcher != null) { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + delegateDispatcher::noteOperationImpl); + } else { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().noteOperation(code, uid, packageName, + attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); } - return delegate.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + return noteOperationImpl(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private int noteOperationImpl(int code, int uid, @Nullable String packageName, @@ -6589,7 +6640,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** * Async task for writing note op stack trace, op code, package name and version to file * More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces} @@ -6726,4 +6776,34 @@ public class AppOpsService extends IAppOpsService.Stub { } } } + + private final class CheckOpsDelegateDispatcher { + private final @NonNull CheckOpsDelegate mCheckOpsDelegate; + + CheckOpsDelegateDispatcher(@NonNull CheckOpsDelegate checkOpsDelegate) { + mCheckOpsDelegate = checkOpsDelegate; + } + + public @NonNull CheckOpsDelegate getCheckOpsDelegate() { + return mCheckOpsDelegate; + } + + public int checkOperationImpl(int code, int uid, String packageName, boolean raw) { + return mCheckOpsDelegate.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + + public int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { + return mCheckOpsDelegate.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + + public int noteOperationImpl(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String message, boolean shouldCollectMessage) { + return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } } diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java index 7b400b6e0309..6ea4bd2b1d6d 100644 --- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -17,7 +17,6 @@ package com.android.server.location; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.hardware.location.ActivityRecognitionHardware; import android.hardware.location.IActivityRecognitionHardwareClient; @@ -27,6 +26,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; /** * Proxy class to bind GmsCore to the ActivityRecognitionHardware. @@ -82,7 +82,7 @@ public class HardwareActivityRecognitionProxy { return resolves; } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService service) throws RemoteException { String descriptor = binder.getInterfaceDescriptor(); if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index cbbc981d9ed6..57e9fc9cb719 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -63,6 +63,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -82,6 +83,7 @@ import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Log; @@ -255,6 +257,9 @@ public class LocationManagerService extends ILocationManager.Stub { final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = new CopyOnWriteArrayList<>(); + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mInjector = injector; @@ -319,8 +324,9 @@ public class LocationManagerService extends ILocationManager.Stub { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); manager.startManager(); + manager.setOnProviderLocationTagsChangeListener( + mOnProviderLocationTagsChangeListener); if (realProvider != null) { - // custom logic wrapping all non-passive providers if (manager != mPassiveManager) { boolean enableStationaryThrottling = Settings.Global.getInt( @@ -331,7 +337,6 @@ public class LocationManagerService extends ILocationManager.Stub { mInjector, realProvider, mEventLog); } } - manager.setRealProvider(realProvider); } mProviderManagers.add(manager); @@ -456,8 +461,9 @@ public class LocationManagerService extends ILocationManager.Stub { .setPowerUsage(Integer.parseInt(fragments[8])) .setAccuracy(Integer.parseInt(fragments[9])) .build(); - getOrAddLocationProviderManager(name).setMockProvider( - new MockLocationProvider(properties, CallerIdentity.fromContext(mContext))); + final LocationProviderManager manager = getOrAddLocationProviderManager(name); + manager.setMockProvider(new MockLocationProvider(properties, + CallerIdentity.fromContext(mContext), /*locationTags*/ null)); } } @@ -1147,15 +1153,16 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void addTestProvider(String provider, ProviderProperties properties, - String packageName, String attributionTag) { + List<String> locationTags, String packageName, String attributionTag) { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { return; } - getOrAddLocationProviderManager(provider).setMockProvider( - new MockLocationProvider(properties, identity)); + final LocationProviderManager manager = getOrAddLocationProviderManager(provider); + manager.setMockProvider(new MockLocationProvider(properties, identity, + (locationTags != null) ? new ArraySet<>(locationTags) : null)); } @Override @@ -1392,6 +1399,19 @@ public class LocationManagerService extends ILocationManager.Stub { return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); } + + @Override + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + synchronized (mLock) { + mOnProviderLocationTagsChangeListener = listener; + final int providerCount = mProviderManagers.size(); + for (int i = 0; i < providerCount; i++) { + final LocationProviderManager manager = mProviderManagers.get(i); + manager.setOnProviderLocationTagsChangeListener(listener); + } + } + } } private static class SystemInjector implements Injector { diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index f0dd8b559d64..21a9b0442b74 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -75,8 +75,8 @@ class LocationShellCommand extends BasicShellCommandHandler { case "add-test-provider": { String provider = getNextArgRequired(); ProviderProperties properties = parseTestProviderProviderProperties(); - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, /*locationTags*/ null, + mContext.getOpPackageName(), mContext.getFeatureId()); return 0; } case "remove-test-provider": { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 81c1e45504cb..dde45c4ab52a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -295,7 +295,7 @@ public class ContextHubService extends IContextHubService.Stub { }; SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); manager.addSensorPrivacyListener( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE, listener); + SensorPrivacyManager.Sensors.MICROPHONE, listener); } } @@ -1079,8 +1079,8 @@ public class ContextHubService extends IContextHubService.Stub { */ private void sendMicrophoneDisableSettingUpdate() { SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); - boolean disabled = manager.isIndividualSensorPrivacyEnabled( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE); + boolean disabled = manager.isSensorPrivacyEnabled( + SensorPrivacyManager.Sensors.MICROPHONE); Log.d(TAG, "Mic Disabled Setting: " + disabled); mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled); } 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 9216a6b245a6..29da177ee4a1 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -371,7 +371,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, Injector injector, GnssNative gnssNative, GnssMetrics gnssMetrics) { - super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES); + super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); mContext = context; mGnssNative = gnssNative; diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 08deb8698608..9ff6e6bc8e32 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -29,6 +29,7 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -67,7 +68,7 @@ public abstract class AbstractLocationProvider { * Default state value for a location provider that is disabled with no properties and an * empty provider package list. */ - public static final State EMPTY_STATE = new State(false, null, null); + public static final State EMPTY_STATE = new State(false, null, null, null); /** * The provider's allowed state. @@ -84,10 +85,14 @@ public abstract class AbstractLocationProvider { */ @Nullable public final CallerIdentity identity; - private State(boolean allowed, ProviderProperties properties, CallerIdentity identity) { + @Nullable public final Set<String> locationTags; + + private State(boolean allowed, ProviderProperties properties, CallerIdentity identity, + Set<String> locationTags) { this.allowed = allowed; this.properties = properties; this.identity = identity; + this.locationTags = locationTags; } /** @@ -97,7 +102,7 @@ public abstract class AbstractLocationProvider { if (allowed == this.allowed) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -108,7 +113,7 @@ public abstract class AbstractLocationProvider { if (Objects.equals(properties, this.properties)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -119,10 +124,22 @@ public abstract class AbstractLocationProvider { if (Objects.equals(identity, this.identity)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } + /** + * Returns a state the same as the current but with location tags set as specified. + */ + public State withLocationTags(@Nullable Set<String> locationTags) { + if (Objects.equals(locationTags, this.locationTags)) { + return this; + } else { + return new State(allowed, properties, identity, locationTags); + } + } + + @Override public boolean equals(Object o) { if (this == o) { @@ -133,12 +150,13 @@ public abstract class AbstractLocationProvider { } State state = (State) o; return allowed == state.allowed && properties == state.properties - && Objects.equals(identity, state.identity); + && Objects.equals(identity, state.identity) + && Objects.equals(locationTags, state.locationTags); } @Override public int hashCode() { - return Objects.hash(allowed, properties, identity); + return Objects.hash(allowed, properties, identity, locationTags); } } @@ -195,13 +213,14 @@ public abstract class AbstractLocationProvider { * An optional identity and properties may be provided to initialize the location provider. */ protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity, - @Nullable ProviderProperties properties) { + @Nullable ProviderProperties properties, @Nullable Set<String> locationTags) { Preconditions.checkArgument(identity == null || identity.getListenerId() == null); mExecutor = executor; mInternalState = new AtomicReference<>(new InternalState(null, State.EMPTY_STATE .withIdentity(identity) - .withProperties(properties))); + .withProperties(properties).withLocationTags(locationTags)) + ); mController = new Controller(); } @@ -281,6 +300,13 @@ public abstract class AbstractLocationProvider { } /** + * Call this method to report a change in provider location tags. + */ + protected void setLocationTags(@Nullable Set<String> locationTags) { + setState(state -> state.withLocationTags(locationTags)); + } + + /** * Call this method to report a new location. */ protected void reportLocation(LocationResult locationResult) { diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java index a3ec867220dd..49f6e64a1e0c 100644 --- a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java @@ -40,7 +40,7 @@ class DelegateLocationProvider extends AbstractLocationProvider private boolean mInitialized = false; DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) { - super(executor, null, null); + super(executor, null, null, null); mDelegate = delegate; } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 5a35d7f88ff3..1ecf3ee40a53 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -52,6 +52,8 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.LocationTagInfo; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; @@ -85,6 +87,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.LocationPermissions; @@ -117,6 +120,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; @@ -1288,6 +1292,9 @@ public class LocationProviderManager extends @GuardedBy("mLock") private @Nullable OnAlarmListener mDelayedRegister; + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener; + public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog, String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; @@ -1448,6 +1455,19 @@ public class LocationProviderManager extends } } + /** + * Registers a listener for the location tags of the provider. + * + * @param listener The listener + */ + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + Preconditions.checkArgument(mOnLocationTagsChangeListener == null || listener == null); + synchronized (mLock) { + mOnLocationTagsChangeListener = listener; + } + } + public void setMockProvider(@Nullable MockLocationProvider provider) { synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); @@ -2244,6 +2264,27 @@ public class LocationProviderManager extends if (oldState.allowed != newState.allowed) { onEnabledChanged(UserHandle.USER_ALL); } + + if (!Objects.equals(oldState.locationTags, newState.locationTags)) { + if (mOnLocationTagsChangeListener != null) { + if (oldState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + oldState.identity.getUid(), oldState.identity.getPackageName(), + Collections.emptySet()) + )); + } + if (newState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + newState.identity.getUid(), newState.identity.getPackageName(), + newState.locationTags) + )); + } + } + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java index 0d8f64377db0..7660f56f1580 100644 --- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java @@ -28,6 +28,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Set; /** * A mock location provider used by LocationManagerService to implement test providers. @@ -38,9 +39,10 @@ public class MockLocationProvider extends AbstractLocationProvider { @Nullable private Location mLocation; - public MockLocationProvider(ProviderProperties properties, CallerIdentity identity) { + public MockLocationProvider(ProviderProperties properties, CallerIdentity identity, + @Nullable Set<String> locationTags) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, locationTags); } /** Sets the allowed state of this mock provider. */ diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index cb7264e55fa9..4ffa9a509a23 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -75,7 +75,7 @@ public class MockableLocationProvider extends AbstractLocationProvider { public MockableLocationProvider(Object ownerLock) { // using a direct executor is acceptable because all inbound calls are delegated to the // actual provider implementations which will use their own executors - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mOwnerLock = ownerLock; mRequest = ProviderRequest.EMPTY_REQUEST; } diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java index a5758a37b983..ee9d35d21798 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java @@ -47,7 +47,8 @@ public class PassiveLocationProvider extends AbstractLocationProvider { public PassiveLocationProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES); + super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); setAllowed(true); } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index 4c97f645aac9..f00478a3488a 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -18,6 +18,7 @@ package com.android.server.location.provider.proxy; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -32,10 +33,12 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import com.android.server.location.provider.AbstractLocationProvider; import java.io.FileDescriptor; @@ -49,6 +52,9 @@ import java.util.Objects; */ public class ProxyLocationProvider extends AbstractLocationProvider { + private static final String KEY_LOCATION_TAGS = "android:location_allow_listed_tags"; + private static final String LOCATION_TAGS_SEPARATOR = ";"; + /** * Creates and registers this proxy. If no suitable service is available for the proxy, returns * null. @@ -84,7 +90,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { int nonOverlayPackageResId) { // safe to use direct executor since our locks are not acquired in a code path invoked by // our owning provider - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mContext = context; mServiceWatcher = new ServiceWatcher(context, action, this::onBind, @@ -94,22 +100,34 @@ public class ProxyLocationProvider extends AbstractLocationProvider { mRequest = ProviderRequest.EMPTY_REQUEST; } + private void updateLocationTagInfo(@NonNull BoundService boundService) { + if (boundService.metadata != null) { + final String tagsList = boundService.metadata.getString(KEY_LOCATION_TAGS); + if (tagsList != null) { + final String[] tags = tagsList.split(LOCATION_TAGS_SEPARATOR); + setLocationTags(new ArraySet<>(tags)); + } + } + } + private boolean checkServiceResolves() { return mServiceWatcher.checkServiceResolves(); } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService boundService) throws RemoteException { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); synchronized (mLock) { mProxy = new Proxy(); - mService = service; + mService = boundService.component; provider.setLocationProviderManager(mProxy); ProviderRequest request = mRequest; if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { provider.setRequest(request); } + + updateLocationTagInfo(boundService); } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java new file mode 100644 index 000000000000..c9653909adb6 --- /dev/null +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -0,0 +1,151 @@ +/* + * 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.policy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal; +import android.location.LocationManagerInternal; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.QuadFunction; +import com.android.server.LocalServices; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class defines policy for special behaviors around app ops. + */ +public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { + @NonNull + private final Object mLock = new Object(); + + /** + * The locking policy around the location tags is a bit special. Since we want to + * avoid grabbing the lock on every op note we are taking the approach where the + * read and write are being done via a thread-safe data structure such that the + * lookup/insert are single thread-safe calls. When we update the cached state we + * use a lock to ensure the update's lookup and store calls are done atomically, + * so multiple writers would not interleave. The tradeoff is we make is that the + * concurrent data structure would use boxing/unboxing of integers but this is + * preferred to locking. + */ + @GuardedBy("mLock - writes only - see above") + @NonNull + private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags = + new ConcurrentHashMap(); + + public AppOpsPolicy() { + final LocationManagerInternal locationManagerInternal = LocalServices.getService( + LocationManagerInternal.class); + locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { + synchronized (mLock) { + final int uid = providerTagInfo.getUid(); + // We make a copy of the per UID state to limit our mutation to one + // operation in the underlying concurrent data structure. + ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + uidTags = new ArrayMap<>(uidTags); + } + + final String packageName = providerTagInfo.getPackageName(); + ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null; + if (packageTags != null) { + packageTags = new ArraySet<>(packageTags); + } + + final Set<String> providerTags = providerTagInfo.getTags(); + if (providerTags != null && !providerTags.isEmpty()) { + if (packageTags != null) { + packageTags.clear(); + packageTags.addAll(providerTags); + } else { + packageTags = new ArraySet<>(providerTags); + } + if (uidTags == null) { + uidTags = new ArrayMap<>(); + } + uidTags.put(packageName, packageTags); + mLocationTags.put(uid, uidTags); + } else if (uidTags != null) { + uidTags.remove(packageName); + if (!uidTags.isEmpty()) { + mLocationTags.put(uid, uidTags); + } else { + mLocationTags.remove(uid); + } + } + } + }); + } + + @Override + public int checkOperation(int code, int uid, String packageName, boolean raw, + QuadFunction<Integer, Integer, String, Boolean, Integer> superImpl) { + return superImpl.apply(code, uid, packageName, raw); + } + + @Override + public int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { + return superImpl.apply(code, usage, uid, packageName); + } + + @Override + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, + Boolean, String, Boolean, Integer> superImpl) { + if (isHandledOp(code)) { + // Only a single lookup from the underlying concurrent data structure + final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + final ArraySet<String> packageTags = uidTags.get(packageName); + if (packageTags != null && packageTags.contains(featureId)) { + return superImpl.apply(resolveLocationOp(code), uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); + } + } + } + return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); + } + + private static boolean isHandledOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + return true; + } + return false; + } + + private static int resolveLocationOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + return AppOpsManager.OP_FINE_LOCATION_SOURCE; + case AppOpsManager.OP_COARSE_LOCATION: + return AppOpsManager.OP_COARSE_LOCATION_SOURCE; + } + return code; + } +} diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index c4f29ea69218..ef0079e0c01f 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -16,6 +16,8 @@ package com.android.server.powerstats; +import static java.lang.System.currentTimeMillis; + import android.content.Context; import android.hardware.power.stats.Channel; import android.hardware.power.stats.EnergyConsumer; @@ -26,11 +28,14 @@ import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -61,6 +66,8 @@ public final class PowerStatsLogger extends Handler { protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1; protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2; + // TODO(b/181240441): Add a listener to update the Wall clock baseline when changed + private final long mStartWallTime; private final PowerStatsDataStorage mPowerStatsMeterStorage; private final PowerStatsDataStorage mPowerStatsModelStorage; private final PowerStatsDataStorage mPowerStatsResidencyStorage; @@ -79,6 +86,8 @@ public final class PowerStatsLogger extends Handler { // Log power meter data. EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]); + EnergyMeasurementUtils.adjustTimeSinceBootToEpoch(energyMeasurements, + mStartWallTime); mPowerStatsMeterStorage.write( EnergyMeasurementUtils.getProtoBytes(energyMeasurements)); if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements); @@ -86,6 +95,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data without attribution data. EnergyConsumerResult[] ecrNoAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrNoAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false)); if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution); @@ -97,6 +108,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data with attribution data. EnergyConsumerResult[] ecrAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true)); if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution); @@ -108,6 +121,8 @@ public final class PowerStatsLogger extends Handler { // Log state residency data. StateResidencyResult[] stateResidencyResults = mPowerStatsHALWrapper.getStateResidency(new int[0]); + StateResidencyResultUtils.adjustTimeSinceBootToEpoch(stateResidencyResults, + mStartWallTime); mPowerStatsResidencyStorage.write( StateResidencyResultUtils.getProtoBytes(stateResidencyResults)); if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults); @@ -293,12 +308,19 @@ public final class PowerStatsLogger extends Handler { return mDeleteResidencyDataOnBoot; } + @VisibleForTesting + public long getStartWallTime() { + return mStartWallTime; + } + public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); + mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); + if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; mDataStoragePath = dataStoragePath; diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index 11b22a574476..746a09882f93 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -109,6 +109,18 @@ public class ProtoStreamUtils { } static class StateResidencyResultUtils { + public static void adjustTimeSinceBootToEpoch(StateResidencyResult[] stateResidencyResult, + long startWallTime) { + for (int i = 0; i < stateResidencyResult.length; i++) { + final int stateLength = stateResidencyResult[i].stateResidencyData.length; + for (int j = 0; j < stateLength; j++) { + final StateResidency stateResidencyData = + stateResidencyResult[i].stateResidencyData[j]; + stateResidencyData.lastEntryTimestampMs += startWallTime; + } + } + } + public static byte[] getProtoBytes(StateResidencyResult[] stateResidencyResult) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(stateResidencyResult, pos); @@ -306,6 +318,13 @@ public class ProtoStreamUtils { } static class EnergyMeasurementUtils { + public static void adjustTimeSinceBootToEpoch(EnergyMeasurement[] energyMeasurement, + long startWallTime) { + for (int i = 0; i < energyMeasurement.length; i++) { + energyMeasurement[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyMeasurement[] energyMeasurement) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(energyMeasurement, pos); @@ -518,6 +537,13 @@ public class ProtoStreamUtils { } static class EnergyConsumerResultUtils { + public static void adjustTimeSinceBootToEpoch(EnergyConsumerResult[] energyConsumerResult, + long startWallTime) { + for (int i = 0; i < energyConsumerResult.length; i++) { + energyConsumerResult[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult, boolean includeAttribution) { ProtoOutputStream pos = new ProtoOutputStream(); diff --git a/services/core/java/com/android/server/timedetector/DeviceConfig.java b/services/core/java/com/android/server/timedetector/DeviceConfig.java deleted file mode 100644 index 7b9ad0f531aa..000000000000 --- a/services/core/java/com/android/server/timedetector/DeviceConfig.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 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.timedetector; - -import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringDef; - -import java.time.Duration; -import java.util.concurrent.Executor; - -/** - * A helper class for reading / monitoring the {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags. - */ -public final class DeviceConfig { - - /** - * An annotation used to indicate when a {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required. - * - * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider - * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the - * prefix "geotz_" on all of its key strings. - */ - @StringDef(prefix = "KEY_", value = { - KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, - KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, - KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - }) - @interface DeviceConfigKey {} - - /** - * The key to force location time zone detection on for a device. Only intended for use during - * release testing with droidfooders. The user can still disable the feature by turning off the - * master location switch, or disabling automatic time zone detection. - */ - @DeviceConfigKey - public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED = - "force_location_time_zone_detection_enabled"; - - /** - * The key for the default value used to determine whether location time zone detection is - * enabled when the user hasn't explicitly set it yet. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT = - "location_time_zone_detection_enabled_default"; - - /** - * The key for the minimum delay after location time zone detection has been enabled before the - * location time zone manager can report it is uncertain about the time zone. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = - "location_time_zone_detection_uncertainty_delay_millis"; - - /** - * The key for the timeout passed to a location time zone provider that tells it how long it has - * to provide an explicit first suggestion without being declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = - "ltpz_init_timeout_millis"; - - /** - * The key for the extra time added to {@link - * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone - * manager before the location time zone provider will actually be declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = - "ltpz_init_timeout_fuzz_millis"; - - /** Creates an instance. */ - public DeviceConfig() {} - - /** Adds a listener for the system_time namespace. */ - public void addListener( - @NonNull Executor handlerExecutor, @NonNull Runnable listener) { - android.provider.DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_SYSTEM_TIME, - handlerExecutor, - properties -> listener.run()); - } - - /** - * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { - return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); - } - - /** - * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - @Nullable - public Duration getDurationFromMillis( - @DeviceConfigKey String key, @Nullable Duration defaultValue) { - long deviceConfigValue = - android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); - if (deviceConfigValue < 0) { - return defaultValue; - } - return Duration.ofMillis(deviceConfigValue); - } -} diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java new file mode 100644 index 000000000000..8819b371b225 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -0,0 +1,238 @@ +/* + * Copyright 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.timedetector; + +import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.provider.DeviceConfig; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.timezonedetector.ConfigurationChangeListener; +import com.android.server.timezonedetector.ServiceConfigAccessor; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace + * for server-configured flags. + */ +public final class ServerFlags { + + private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); + private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); + + /** + * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is + * required. + * + * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider + * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the + * prefix "geotz_" on all of its key strings. + */ + @StringDef(prefix = "KEY_", value = { + KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, + }) + @interface DeviceConfigKey {} + + /** + * Controls whether the location time zone manager service will started. Only observed if + * the device build is configured to support location-based time zone detection. See + * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link + * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = + "location_time_zone_detection_feature_supported"; + + /** + * The key for the server flag that can override the device config for whether the primary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "primary_location_time_zone_provider_enabled_override"; + + /** + * The key for the server flag that can override the device config for whether the secondary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "secondary_location_time_zone_provider_enabled_override"; + + /** + * The key for the minimum delay after location time zone detection has been enabled before the + * location time zone manager can report it is uncertain about the time zone. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = + "location_time_zone_detection_uncertainty_delay_millis"; + + /** + * The key for the timeout passed to a location time zone provider that tells it how long it has + * to provide an explicit first suggestion without being declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = + "ltpz_init_timeout_millis"; + + /** + * The key for the extra time added to {@link + * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone + * manager before the location time zone provider will actually be declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = + "ltpz_init_timeout_fuzz_millis"; + + /** + * The key for the server flag that can override location time zone detection being enabled for + * a user. Only intended for use during release testing with droidfooders. The user can still + * disable the feature by turning off the master location switch, or by disabling automatic time + * zone detection. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = + "location_time_zone_detection_setting_enabled_override"; + + /** + * The key for the default value used to determine whether location time zone detection is + * enabled when the user hasn't explicitly set it yet. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = + "location_time_zone_detection_setting_enabled_default"; + + @GuardedBy("mListeners") + private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); + + private static final Object SLOCK = new Object(); + + @GuardedBy("SLOCK") + @Nullable + private static ServerFlags sInstance; + + private ServerFlags(Context context) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_SYSTEM_TIME, + context.getMainExecutor(), + this::handlePropertiesChanged); + } + + /** Returns the singleton instance. */ + public static ServerFlags getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServerFlags(context); + } + return sInstance; + } + } + + private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { + synchronized (mListeners) { + for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry + : mListeners.entrySet()) { + if (intersects(listenerEntry.getValue(), properties.getKeyset())) { + listenerEntry.getKey().onChange(); + } + } + } + } + + private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) { + for (String toFind : one) { + if (two.contains(toFind)) { + return true; + } + } + return false; + } + + /** + * Adds a listener for the system_time namespace that will trigger if any of the specified keys + * change. Listener callbacks are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no + * associated remove method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener, + @NonNull Set<String> keys) { + Objects.requireNonNull(listener); + Objects.requireNonNull(keys); + + synchronized (mListeners) { + mListeners.put(listener, keys); + } + } + + /** + * Returns an optional boolean value from {@link DeviceConfig} from the system_time + * namespace, returns {@link Optional#empty()} if there is no explicit value set. + */ + @NonNull + public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { + String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); + return parseOptionalBoolean(value); + } + + @NonNull + private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { + if (value == null) { + return Optional.empty(); + } else { + return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; + } + } + + /** + * Returns a boolean value from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { + return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); + } + + /** + * Returns a positive duration from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + @Nullable + public Duration getDurationFromMillis( + @DeviceConfigKey String key, @Nullable Duration defaultValue) { + long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); + if (deviceConfigValue < 0) { + return defaultValue; + } + return Duration.ofMillis(deviceConfigValue); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java index 4c7b1f38dd5a..aa8ad37815bf 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java @@ -17,10 +17,11 @@ package com.android.server.timezonedetector; /** - * A listener used to receive notification that time zone configuration has changed. + * A listener used to receive notification that configuration has / may have changed (depending on + * the usecase). */ @FunctionalInterface public interface ConfigurationChangeListener { - /** Called when the current user or a configuration value has changed. */ + /** Called when the configuration may have changed. */ void onChange(); } diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 1f73977444f8..3ae9d641e81c 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -33,26 +33,27 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** - * Holds all configuration values that affect time zone behavior and some associated logic, e.g. - * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link - * #createCapabilitiesAndConfig()}. + * Holds configuration values that affect user-facing time zone behavior and some associated logic. + * Some configuration is global, some is user scoped, but this class deliberately doesn't make a + * distinction for simplicity. */ public final class ConfigurationInternal { - private final @UserIdInt int mUserId; - private final boolean mUserConfigAllowed; private final boolean mAutoDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; + private final @UserIdInt int mUserId; + private final boolean mUserConfigAllowed; private final boolean mLocationEnabled; private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { - mUserId = builder.mUserId; - mUserConfigAllowed = builder.mUserConfigAllowed; mAutoDetectionSupported = builder.mAutoDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + + mUserId = builder.mUserId; + mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported @@ -60,22 +61,6 @@ public final class ConfigurationInternal { Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } - /** Returns the ID of the user this configuration is associated with. */ - public @UserIdInt int getUserId() { - return mUserId; - } - - /** Returns the handle of the user this configuration is associated with. */ - @NonNull - public UserHandle getUserHandle() { - return UserHandle.of(mUserId); - } - - /** Returns true if the user allowed to modify time zone configuration. */ - public boolean isUserConfigAllowed() { - return mUserConfigAllowed; - } - /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { return mAutoDetectionSupported; @@ -98,6 +83,22 @@ public final class ConfigurationInternal { return mAutoDetectionSupported && mAutoDetectionEnabled; } + /** Returns the ID of the user this configuration is associated with. */ + public @UserIdInt int getUserId() { + return mUserId; + } + + /** Returns the handle of the user this configuration is associated with. */ + @NonNull + public UserHandle getUserHandle() { + return UserHandle.of(mUserId); + } + + /** Returns true if the user allowed to modify time zone configuration. */ + public boolean isUserConfigAllowed() { + return mUserConfigAllowed; + } + /** Returns true if user's location can be used generally. */ public boolean isLocationEnabled() { return mLocationEnabled; @@ -283,7 +284,7 @@ public final class ConfigurationInternal { /** * Sets whether any form of automatic time zone detection is supported on this device. */ - public Builder setAutoDetectionSupported(boolean supported) { + public Builder setAutoDetectionFeatureSupported(boolean supported) { mAutoDetectionSupported = supported; return this; } @@ -291,7 +292,7 @@ public final class ConfigurationInternal { /** * Sets whether geolocation time zone detection is supported on this device. */ - public Builder setGeoDetectionSupported(boolean supported) { + public Builder setGeoDetectionFeatureSupported(boolean supported) { mGeoDetectionSupported = supported; return this; } diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index f52b9b1b1c58..e3caae9482d9 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -31,9 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.location.LocationManager; -import android.net.ConnectivityManager; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -42,10 +40,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import java.util.Objects; -import java.util.concurrent.Executor; +import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. @@ -59,8 +56,7 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @NonNull private final Handler mHandler; @NonNull private final ContentResolver mCr; @NonNull private final UserManager mUserManager; - @NonNull private final DeviceConfig mDeviceConfig; - @NonNull private final boolean mGeoDetectionSupported; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final LocationManager mLocationManager; // @NonNull after setConfigChangeListener() is called. @@ -68,17 +64,16 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, - @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); - Executor handlerExecutor = new HandlerExecutor(mHandler); mCr = context.getContentResolver(); mUserManager = context.getSystemService(UserManager.class); mLocationManager = context.getSystemService(LocationManager.class); - mDeviceConfig = deviceConfig; - mGeoDetectionSupported = geoDetectionSupported; + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); - // Wire up the change listeners. All invocations are performed on the mHandler thread. + // Wire up the config change listeners. All invocations are performed on the mHandler + // thread. // Listen for the user changing / the user's location mode changing. IntentFilter filter = new IntentFilter(); @@ -112,13 +107,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir handleConfigChangeOnHandlerThread(); } }, UserHandle.USER_ALL); - - // Add async callbacks for changes to server-side flags: some of the flags affect device / - // user config. All changes can be treated like a config change. If flags that affect config - // haven't changed then call will be a no-op. - mDeviceConfig.addListener( - handlerExecutor, - this::handleConfigChangeOnHandlerThread); } private void handleConfigChangeOnHandlerThread() { @@ -140,10 +128,12 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) - .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setAutoDetectionSupported(isAutoDetectionSupported()) - .setGeoDetectionSupported(isGeoDetectionSupported()) + .setAutoDetectionFeatureSupported( + mServiceConfigAccessor.isAutoDetectionFeatureSupported()) + .setGeoDetectionFeatureSupported( + mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) + .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabled(isLocationEnabled(userId)) .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) .build(); @@ -186,18 +176,19 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir // time zone detection: if we wrote it down then we'd set the value explicitly, which would // prevent detecting "default" later. That might influence what happens on later releases // that support new types of auto detection on the same hardware. - if (isAutoDetectionSupported()) { + if (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); setAutoDetectionEnabledIfRequired(autoDetectionEnabled); - // Avoid writing the geo detection enabled setting for devices that do not support geo - // time zone detection: if we wrote it down then we'd set the value explicitly, which - // would prevent detecting "default" later. That might influence what happens on later - // releases that support geo detection on the same hardware. - // Also avoid writing the geo detection enabled setting for devices that are currently - // force-enabled: otherwise we might overwrite a droidfood user's real setting - // permanently. - if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) { + // Avoid writing the geo detection enabled setting for devices with settings that + // are currently overridden by server flags: otherwise we might overwrite a droidfood + // user's real setting permanently. + // Also avoid writing the geo detection enabled setting for devices that do not support + // geo time zone detection: if we wrote it down then we'd set the value explicitly, + // which would prevent detecting "default" later. That might influence what happens on + // later releases that start to support geo detection on the same hardware. + if (!mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride().isPresent() + && mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); } @@ -209,14 +200,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } - private boolean isAutoDetectionSupported() { - return deviceHasTelephonyNetwork() || isGeoDetectionSupported(); - } - - private boolean isGeoDetectionSupported() { - return mGeoDetectionSupported; - } - private boolean isAutoDetectionEnabled() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } @@ -237,24 +220,20 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private boolean isGeoDetectionEnabled(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection - // on for testers (where their other settings allow). - boolean forceEnabled = isGeoDetectionForceEnabled(); - if (forceEnabled) { - return true; + // on/off for testers (but only where their other settings would allow them to turn it on + // for themselves). + Optional<Boolean> override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); + if (override.isPresent()) { + return override.get(); } - final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false); + final boolean geoDetectionEnabledByDefault = + mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); return Settings.Secure.getIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } - private boolean isGeoDetectionForceEnabled() { - return mDeviceConfig.getBoolean( - DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false); - } - private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 if (isGeoDetectionEnabled(userId) != enabled) { @@ -262,10 +241,4 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir enabled ? 1 : 0, userId); } } - - private boolean deviceHasTelephonyNetwork() { - // TODO b/150583524 Avoid the use of a deprecated API. - return mContext.getSystemService(ConnectivityManager.class) - .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); - } } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java new file mode 100644 index 000000000000..86c32f8d7b45 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -0,0 +1,249 @@ +/* + * Copyright 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.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.os.SystemProperties; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.server.timedetector.ServerFlags; + +import java.time.Duration; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A singleton that provides access to service configuration for time zone detection. This hides how + * configuration is split between static, compile-time config and dynamic, server-pushed flags. It + * provides a rudimentary mechanism to signal when values have changed. + */ +public final class ServiceConfigAccessor { + + private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet( + new ArraySet<>(new String[] { + 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_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS + })); + + // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); + // TODO(b/179488561): Put this back to 1 minute when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = + Duration.ofSeconds(20); + private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + + private static final Object SLOCK = new Object(); + + /** The singleton instance. Initialized once in {@link #getInstance(Context)}. */ + @GuardedBy("SLOCK") + @Nullable + private static ServiceConfigAccessor sInstance; + + @NonNull private final Context mContext; + + /** + * An ultimate "feature switch" for location-based time zone detection. If this is + * {@code false}, the device cannot support the feature without a config change or a reboot: + * This affects what services are started on boot to minimize expense when the feature is not + * wanted. + */ + private final boolean mGeoDetectionFeatureSupportedInConfig; + + @NonNull private final ServerFlags mServerFlags; + + private ServiceConfigAccessor(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + + // The config value is expected to be the main feature flag. Platform developers can also + // force enable the feature using a persistent system property. Because system properties + // can change, this value is cached and only changes on reboot. + mGeoDetectionFeatureSupportedInConfig = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) + || SystemProperties.getBoolean( + "persist.sys.location_time_zone_detection_feature_supported", false); + + mServerFlags = ServerFlags.getInstance(mContext); + } + + /** Returns the singleton instance. */ + public static ServiceConfigAccessor getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServiceConfigAccessor(context); + } + return sInstance; + } + } + + /** + * Adds a listener that will be called server flags related to this class change. The callbacks + * are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove + * method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener) { + mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH); + } + + /** Returns {@code true} if any form of automatic time zone detection is supported. */ + public boolean isAutoDetectionFeatureSupported() { + return deviceHasTelephonyNetwork() || isGeoTimeZoneDetectionFeatureSupported(); + } + + private boolean deviceHasTelephonyNetwork() { + // TODO b/150583524 Avoid the use of a deprecated API. + return mContext.getSystemService(ConnectivityManager.class) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } + + /** + * Returns {@code true} if the location-based time zone detection feature can be supported on + * this device at all according to config. When {@code false}, implies that various other + * location-based settings will be turned off or rendered meaningless. Typically {@link + * #isGeoTimeZoneDetectionFeatureSupported()} should be used instead. + */ + public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() { + return mGeoDetectionFeatureSupportedInConfig; + } + + /** + * Returns {@code true} if the location-based time zone detection feature is supported on the + * device. This can be used during feature testing on builds that are capable of location time + * zone detection to enable / disable the feature for some users. + */ + public boolean isGeoTimeZoneDetectionFeatureSupported() { + return mGeoDetectionFeatureSupportedInConfig + && isGeoTimeZoneDetectionFeatureSupportedInternal(); + } + + private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() { + final boolean defaultEnabled = true; + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + defaultEnabled); + } + + /** + * Returns {@code true} if the primary location time zone provider can be used. + */ + public boolean isPrimaryLocationTimeZoneProviderEnabled() { + return getPrimaryLocationTimeZoneProviderEnabledOverride() + .orElse(isPrimaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isPrimaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getPrimaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns {@code true} if the secondary location time zone provider can be used. + */ + public boolean isSecondaryLocationTimeZoneProviderEnabled() { + return getSecondaryLocationTimeZoneProviderEnabledOverride() + .orElse(isSecondaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isSecondaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getSecondaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns whether location time zone detection is enabled for users when there's no setting + * value. Intended for use during feature release testing to "opt-in" users that haven't shown + * an explicit preference. + */ + public boolean isGeoDetectionEnabledForUsersByDefault() { + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, false); + } + + /** + * Returns whether location time zone detection is force enabled/disabled for users. Intended + * for use during feature release testing to force a given state. + */ + @NonNull + public Optional<Boolean> getGeoDetectionSettingEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); + } + + /** + * Returns the time to send to a location time zone provider that informs it how long it has + * to return its first time zone suggestion. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeout() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + } + + /** + * Returns the time added to {@link #getLocationTimeZoneProviderInitializationTimeout()} by the + * server before unilaterally declaring the provider is uncertain. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + } + + /** + * Returns the time after uncertainty is detected by providers before the location time zone + * manager makes a suggestion to the time zone detector. + */ + @NonNull + public Duration getLocationTimeZoneUncertaintyDelay() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + } + + private boolean getConfigBoolean(int providerEnabledConfigId) { + Resources resources = mContext.getResources(); + return resources.getBoolean(providerEnabledConfigId); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index 203a8a4e02cc..cd220b164851 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -27,16 +27,21 @@ import android.annotation.NonNull; */ public interface TimeZoneDetectorInternal extends Dumpable.Container { - /** Adds a listener that will be invoked when time zone detection configuration is changed. */ - void addConfigurationListener(ConfigurationChangeListener listener); + /** Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. */ + void addConfigurationListener(@NonNull ConfigurationChangeListener listener); /** * Removes a listener previously added via {@link * #addConfigurationListener(ConfigurationChangeListener)}. */ - void removeConfigurationListener(ConfigurationChangeListener listener); + void removeConfigurationListener(@NonNull ConfigurationChangeListener listener); - /** Returns the {@link ConfigurationInternal} for the current user. */ + /** + * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a + * snapshot so callers must use {@link #addConfigurationListener(ConfigurationChangeListener)} + * to be notified when it changes. + */ + @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index bd71ddf67094..c20400ae7a4b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -33,7 +33,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemProperties; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -62,27 +61,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub static final String TAG = "time_zone_detector"; /** - * A "feature switch" for location-based time zone detection. If this is {@code false}. It is - * initialized and never refreshed; it affects what services are started on boot so consistency - * is important. - */ - @Nullable - private static Boolean sGeoLocationTimeZoneDetectionSupported; - - /** Returns {@code true} if the location-based time zone detection feature is enabled. */ - public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) { - if (sGeoLocationTimeZoneDetectionSupported == null) { - // The config value is expected to be the main switch. Platform developers can also - // enable the feature using a persistent system property. - sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean( - com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) - || SystemProperties.getBoolean( - "persist.sys.location_time_zone_detection_feature_enabled", false); - } - return sGeoLocationTimeZoneDetectionSupported; - } - - /** * Handles the service lifecycle for {@link TimeZoneDetectorService} and * {@link TimeZoneDetectorInternalImpl}. */ @@ -98,11 +76,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Context context = getContext(); Handler handler = FgThread.getHandler(); - boolean geolocationTimeZoneDetectionSupported = - isGeoLocationTimeZoneDetectionSupported(context); + ServiceConfigAccessor serviceConfigAccessor = + ServiceConfigAccessor.getInstance(context); TimeZoneDetectorStrategy timeZoneDetectorStrategy = - TimeZoneDetectorStrategyImpl.create( - context, handler, geolocationTimeZoneDetectionSupported); + TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); // Create and publish the local service for use by internal callers. TimeZoneDetectorInternal internal = @@ -330,7 +307,8 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean isGeoTimeZoneDetectionSupported() { enforceManageTimeZoneDetectorPermission(); - return isGeoLocationTimeZoneDetectionSupported(mContext); + return ServiceConfigAccessor.getInstance(mContext) + .isGeoTimeZoneDetectionFeatureSupported(); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 0b1d6d71ea7b..8266f121822e 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -75,17 +75,21 @@ import android.util.IndentingPrintWriter; public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container { /** - * Sets a listener that will be triggered whenever time zone detection configuration is + * Adds a listener that will be triggered whenever {@link ConfigurationInternal} may have * changed. */ void addConfigChangeListener(@NonNull ConfigurationChangeListener listener); - /** Returns the user's time zone configuration. */ + /** + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * specified user. + */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** - * Returns the configuration that controls time zone detector behavior for the current user. + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * current user. */ @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index c464b74ca726..d163a0e22320 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -38,7 +38,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.timedetector.DeviceConfig; import java.util.ArrayList; import java.util.List; @@ -204,11 +203,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ public static TimeZoneDetectorStrategyImpl create( @NonNull Context context, @NonNull Handler handler, - boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { - DeviceConfig deviceConfig = new DeviceConfig(); - EnvironmentImpl environment = new EnvironmentImpl( - context, handler, deviceConfig, geoDetectionSupported); + Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor); return new TimeZoneDetectorStrategyImpl(environment); } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index e463ee22452d..98e984d2c3ac 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -19,9 +19,9 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import com.android.server.timezonedetector.ConfigurationChangeListener; import com.android.server.timezonedetector.ConfigurationInternal; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; import java.time.Duration; @@ -33,28 +33,19 @@ import java.util.Objects; */ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment { - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = - Duration.ofSeconds(20); - private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); - @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal; - @NonNull private final LocationTimeZoneProviderController mController; - @NonNull private final DeviceConfig mDeviceConfig; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final ConfigurationChangeListener mConfigurationChangeListener; ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull DeviceConfig deviceConfig, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull LocationTimeZoneProviderController controller) { super(threadingDomain); - mController = Objects.requireNonNull(controller); - mDeviceConfig = Objects.requireNonNull(deviceConfig); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class); // Listen for configuration changes. - mConfigurationChangeListener = () -> mThreadingDomain.post(mController::onConfigChanged); + mConfigurationChangeListener = () -> mThreadingDomain.post(controller::onConfigChanged); mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener); } @@ -73,24 +64,18 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir @Override @NonNull Duration getProviderInitializationTimeout() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeout(); } @Override @NonNull Duration getProviderInitializationTimeoutFuzz() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeoutFuzz(); } @Override @NonNull Duration getUncertaintyDelay() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay(); } } 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 364eaf8dac04..0d1692a8781d 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -21,11 +21,11 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -43,9 +43,8 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.SystemService; -import com.android.server.timedetector.DeviceConfig; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; -import com.android.server.timezonedetector.TimeZoneDetectorService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -96,28 +95,31 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneManagerService mService; + @NonNull + private final ServiceConfigAccessor mServerConfigAccessor; + public Lifecycle(@NonNull Context context) { super(Objects.requireNonNull(context)); + mServerConfigAccessor = ServiceConfigAccessor.getInstance(context); } @Override public void onStart() { Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { mService = new LocationTimeZoneManagerService(context); // The service currently exposes no LocalService or Binder API, but it extends // Binder and is registered as a binder service so it can receive shell commands. - publishBinderService("location_time_zone_manager", mService); + publishBinderService(SERVICE_NAME, mService); } else { - Slog.i(TAG, getClass() + " is disabled"); + Slog.d(TAG, "Geo time zone detection feature is disabled in config"); } } @Override - public void onBootPhase(int phase) { - Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + public void onBootPhase(@BootPhase int phase) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { if (phase == PHASE_SYSTEM_SERVICES_READY) { // The location service must be functioning after this boot phase. mService.onSystemReady(); @@ -159,6 +161,9 @@ public class LocationTimeZoneManagerService extends Binder { /** The shared lock from {@link #mThreadingDomain}. */ @NonNull private final Object mSharedLock; + @NonNull + private final ServiceConfigAccessor mServiceConfigAccessor; + // Lazily initialized. Can be null if the service has been stopped. @GuardedBy("mSharedLock") private ControllerImpl mLocationTimeZoneDetectorController; @@ -180,24 +185,38 @@ public class LocationTimeZoneManagerService extends Binder { mHandler = FgThread.getHandler(); mThreadingDomain = new HandlerThreadingDomain(mHandler); mSharedLock = mThreadingDomain.getLockObject(); + mServiceConfigAccessor = ServiceConfigAccessor.getInstance(mContext); } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemReady() { - // Called on an arbitrary thread during initialization. - synchronized (mSharedLock) { - // TODO(b/152744911): LocationManagerService watches for packages disappearing. Need to - // do anything here? + mServiceConfigAccessor.addListener(this::handleServiceConfigurationChangedOnMainThread); + } - // TODO(b/152744911): LocationManagerService watches for foreground app changes. Need to - // do anything here? - // TODO(b/152744911): LocationManagerService watches screen state. Need to do anything - // here? + private void handleServiceConfigurationChangedOnMainThread() { + // This method is called on the main thread, but service logic takes place on the threading + // domain thread, so we post the work there. + + // The way all service-level configuration changes are handled is to just restart this + // service - this is simple and effective, and service configuration changes should be rare. + mThreadingDomain.post(this::restartIfRequiredOnDomainThread); + } + + private void restartIfRequiredOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + // Stop and start the service, waiting until completion. + stopOnDomainThread(); + startOnDomainThread(); } } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemThirdPartyAppsCanStart() { - // Called on an arbitrary thread during initialization. We do not want to wait for - // completion as it would delay boot. + // Do not wait for completion as it would delay boot. final boolean waitForCompletion = false; startInternal(waitForCompletion); } @@ -205,6 +224,9 @@ public class LocationTimeZoneManagerService extends Binder { /** * Starts the service during server initialization or during tests after a call to * {@link #stop()}. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for + * completion, it cannot be called from the {@code mThreadingDomain} thread. */ void start() { enforceManageTimeZoneDetectorPermission(); @@ -214,28 +236,17 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Starts the service during server initialization or during tests after a call to - * {@link #stop()}. + * Starts the service during server initialization, if the configuration changes or during tests + * after a call to {@link #stop()}. * * <p>To avoid tests needing to sleep, when {@code waitForCompletion} is {@code true}, this * method will not return until all the system server components have started. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread, it cannot be + * called from the {@code mThreadingDomain} thread when {@code waitForCompletion} is true. */ private void startInternal(boolean waitForCompletion) { - Runnable runnable = () -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController == null) { - LocationTimeZoneProvider primary = createPrimaryProvider(); - LocationTimeZoneProvider secondary = createSecondaryProvider(); - mLocationTimeZoneDetectorController = - new ControllerImpl(mThreadingDomain, primary, secondary); - DeviceConfig deviceConfig = new DeviceConfig(); - mEnvironment = new ControllerEnvironmentImpl( - mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController); - ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); - mLocationTimeZoneDetectorController.initialize(mEnvironment, callback); - } - } - }; + Runnable runnable = this::startOnDomainThread; if (waitForCompletion) { mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS); } else { @@ -243,11 +254,38 @@ public class LocationTimeZoneManagerService extends Binder { } } + private void startOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (!mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { + debugLog("Not starting " + SERVICE_NAME + ": it is disabled in service config"); + return; + } + + if (mLocationTimeZoneDetectorController == null) { + LocationTimeZoneProvider primary = createPrimaryProvider(); + LocationTimeZoneProvider secondary = createSecondaryProvider(); + + ControllerImpl controller = + new ControllerImpl(mThreadingDomain, primary, secondary); + ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( + mThreadingDomain, mServiceConfigAccessor, controller); + ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); + controller.initialize(environment, callback); + + mEnvironment = environment; + mLocationTimeZoneDetectorController = controller; + } + } + } + + @NonNull private LocationTimeZoneProvider createPrimaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(PRIMARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -262,11 +300,12 @@ public class LocationTimeZoneManagerService extends Binder { return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy); } + @NonNull private LocationTimeZoneProvider createSecondaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(SECONDARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -282,33 +321,27 @@ public class LocationTimeZoneManagerService extends Binder { } /** Used for bug triage and in tests to simulate provider events. */ - private boolean isProviderInSimulationMode(String providerName) { + private boolean isProviderInSimulationMode(@NonNull String providerName) { return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED); } - /** Used for bug triage, tests and experiments to remove a provider. */ - private boolean isProviderDisabled(String providerName) { - return !isProviderEnabledInConfig(providerName) - || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED); - } + /** Used for bug triage, and by tests and experiments to remove a provider. */ + private boolean isProviderEnabled(@NonNull String providerName) { + if (isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED)) { + return false; + } - private boolean isProviderEnabledInConfig(String providerName) { - int providerEnabledConfigId; switch (providerName) { case PRIMARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isPrimaryLocationTimeZoneProviderEnabled(); } case SECONDARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isSecondaryLocationTimeZoneProviderEnabled(); } default: { throw new IllegalArgumentException(providerName); } } - Resources resources = mContext.getResources(); - return resources.getBoolean(providerEnabledConfigId); } private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) { @@ -326,22 +359,29 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Stops the service for tests. To avoid tests needing to sleep, this method will not return - * until all the system server components have stopped. + * Stops the service for tests and other rare cases. To avoid tests needing to sleep, this + * method will not return until all the system server components have stopped. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits it cannot + * be called from the {@code mThreadingDomain} thread. */ void stop() { enforceManageTimeZoneDetectorPermission(); - mThreadingDomain.postAndWait(() -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController != null) { - mLocationTimeZoneDetectorController.destroy(); - mLocationTimeZoneDetectorController = null; - mEnvironment.destroy(); - mEnvironment = null; - } + mThreadingDomain.postAndWait(this::stopOnDomainThread, BLOCKING_OP_WAIT_DURATION_MILLIS); + } + + private void stopOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController != null) { + mLocationTimeZoneDetectorController.destroy(); + mLocationTimeZoneDetectorController = null; + mEnvironment.destroy(); + mEnvironment = null; } - }, BLOCKING_OP_WAIT_DURATION_MILLIS); + } } @Override diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index b53150c729bc..bdf4a70a6a2b 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -21,6 +21,7 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND; @@ -102,7 +103,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); - pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:"); + pw.printf("Location Time Zone Manager (%s) commands for tests:\n", SERVICE_NAME); pw.println(" help"); pw.println(" Print this help text."); pw.printf(" %s\n", SHELL_COMMAND_START); diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 38211efc1c63..531c62c5b4e9 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -25,7 +25,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneManag import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -41,6 +40,7 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import java.util.Objects; import java.util.function.Predicate; @@ -123,7 +123,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { return resolves; } - private void onBind(IBinder binder, ComponentName componentName) { + private void onBind(IBinder binder, BoundService boundService) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java index 14820319d9df..e8386bc22403 100644 --- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java @@ -63,8 +63,6 @@ final class TimeZoneProviderRequest { return mSendUpdates; } - // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to - // be passed to the TimeZoneProviderService and remove if it is not useful. /** * Returns the maximum time that the provider is allowed to initialize before it is expected to * send an event of any sort. Only valid when {@link #sendUpdates()} is {@code true}. Failure to diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 71d2b2f7b882..cca85b217a74 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2393,15 +2393,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - // We may be deferring layout passes at the moment, but since the client is interested - // in the new out values right now we need to force a layout. - mWindowPlacerLocked.performSurfacePlacement(true /* force */); - + // Create surfaceControl before surface placement otherwise layout will be skipped + // (because WS.isGoneForLayout() is true when there is no surface. if (shouldRelayout) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); - - result = win.relayoutVisibleWindow(result); - try { result = createSurfaceControl(outSurfaceControl, result, win, winAnimator); } catch (Exception e) { @@ -2413,6 +2407,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); return 0; } + } + + // We may be deferring layout passes at the moment, but since the client is interested + // in the new out values right now we need to force a layout. + mWindowPlacerLocked.performSurfacePlacement(true /* force */); + + if (shouldRelayout) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); + + result = win.relayoutVisibleWindow(result); + if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { focusMayChange = true; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6bbd320017a7..a3d335340e9f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -162,6 +162,7 @@ import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.policy.AppOpsPolicy; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.RoleServicePlatformHelperImpl; @@ -2656,6 +2657,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("RegisterAppOpsPolicy"); + try { + mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy()); + } catch (Throwable e) { + reportWtf("registering app ops policy", e); + } + t.traceEnd(); + // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 68d6557880c4..c4c9ad088e45 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -352,7 +352,8 @@ public class LocationProviderManagerTest { @Test public void testGetLastLocation_ClearOnMockRemoval() { - MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY); + MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY, + null); mockProvider.setAllowed(true); mManager.setMockProvider(mockProvider); @@ -1048,7 +1049,7 @@ public class LocationProviderManagerTest { private final ArrayList<Runnable> mFlushCallbacks = new ArrayList<>(); TestProvider(ProviderProperties properties, CallerIdentity identity) { - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, null); } public void setProviderAllowed(boolean allowed) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index 07170dacb4da..e8a0bb51e20f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -71,7 +71,8 @@ public class MockableLocationProviderTest { .setPowerUsage(POWER_USAGE_LOW) .setAccuracy(ACCURACY_FINE) .build(), - CallerIdentity.forTest(0, 1, "testpackage", "test")); + CallerIdentity.forTest(0, 1, "testpackage", "test"), + null); mProvider = new MockableLocationProvider(lock); mProvider.getController().setListener(mListener); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 775bdd580157..a1eadbe4a64f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -40,7 +40,7 @@ public class FakeProvider extends AbstractLocationProvider { private final FakeProviderInterface mFakeInterface; public FakeProvider(FakeProviderInterface fakeInterface) { - super(Runnable::run, null, null); + super(Runnable::run, null, null, null); mFakeInterface = fakeInterface; } diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index ddbe81c81d6d..26b34fdd4e04 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -318,7 +318,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyMeasurement.length == ENERGY_METER_COUNT); for (int i = 0; i < pssProto.energyMeasurement.length; i++) { assertTrue(pssProto.energyMeasurement[i].id == i); - assertTrue(pssProto.energyMeasurement[i].timestampMs == i); + assertTrue(pssProto.energyMeasurement[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyMeasurement[i].durationMs == i); assertTrue(pssProto.energyMeasurement[i].energyUws == i); } @@ -359,7 +360,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyConsumerResult.length == ENERGY_CONSUMER_COUNT); for (int i = 0; i < pssProto.energyConsumerResult.length; i++) { assertTrue(pssProto.energyConsumerResult[i].id == i); - assertTrue(pssProto.energyConsumerResult[i].timestampMs == i); + assertTrue(pssProto.energyConsumerResult[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyConsumerResult[i].energyUws == i); assertTrue(pssProto.energyConsumerResult[i].attribution.length == ENERGY_CONSUMER_ATTRIBUTION_COUNT); @@ -420,7 +422,8 @@ public class PowerStatsServiceTest { assertTrue(stateResidency.id == j); assertTrue(stateResidency.totalTimeInStateMs == j); assertTrue(stateResidency.totalStateEntryCount == j); - assertTrue(stateResidency.lastEntryTimestampMs == j); + assertTrue(stateResidency.lastEntryTimestampMs == + j + mPowerStatsLogger.getStartWallTime()); } } } 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 682a80c9b297..5d2755221288 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -46,8 +46,8 @@ public class ConfigurationInternalTest { public void test_unrestricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -108,8 +108,8 @@ public class ConfigurationInternalTest { public void test_restricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -170,8 +170,8 @@ public class ConfigurationInternalTest { public void test_autoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -232,8 +232,8 @@ public class ConfigurationInternalTest { public void test_geoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) 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 d2452eaee657..14e0bbd6fa42 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -364,8 +364,8 @@ public class TimeZoneDetectorServiceTest { // the tests. final boolean geoDetectionEnabled = autoDetectionEnabled; return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(autoDetectionEnabled) .setLocationEnabled(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 c8dba5f40882..f1f8b2f5e81a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -90,8 +90,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -100,8 +100,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -110,8 +110,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -120,8 +120,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -130,8 +130,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -139,8 +139,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -149,8 +149,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) 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 d319488ba73b..8280cdcb18c4 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 @@ -44,8 +44,8 @@ final class TestSupport { @UserIdInt int userId, boolean geoDetectionEnabled) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(geoDetectionEnabled) |