Merge "Update methods of PacProcessor."
diff --git a/api/current.txt b/api/current.txt
index 8776d2d..09a65c3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,6 +21,7 @@
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@
field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -12235,6 +12237,7 @@
field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+ field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12344,6 +12347,7 @@
field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+ field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
field public static final int PROTECTION_DANGEROUS = 1; // 0x1
field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
@@ -31607,6 +31611,7 @@
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isMultiStaConcurrencySupported();
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
diff --git a/api/system-current.txt b/api/system-current.txt
index b820ae0..aa9536e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2157,6 +2157,7 @@
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -7565,10 +7566,12 @@
public final class WifiNetworkSuggestion implements android.os.Parcelable {
method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+ method public boolean isOemPaid();
}
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
}
public class WifiScanner {
@@ -10718,16 +10721,12 @@
method public int getTimeoutSeconds();
method public boolean isEnabled();
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
- field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
- field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
- field public static final int ERROR_UNKNOWN = 1; // 0x1
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
field public static final int REASON_BUSY = 1; // 0x1
field public static final int REASON_NOT_REACHABLE = 3; // 0x3
field public static final int REASON_NO_REPLY = 2; // 0x2
field public static final int REASON_UNCONDITIONAL = 0; // 0x0
- field public static final int SUCCESS = 0; // 0x0
}
public final class CallQuality implements android.os.Parcelable {
@@ -11470,7 +11469,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -11569,6 +11568,10 @@
public static interface TelephonyManager.CallForwardingInfoCallback {
method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c2aa5a..0540054 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1037,6 +1037,7 @@
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d50cdee..9100d57 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -33,6 +33,7 @@
import android.app.role.RoleControllerManager;
import android.app.role.RoleManager;
import android.app.slice.SliceManager;
+import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
import android.app.timezone.RulesManager;
@@ -1218,6 +1219,14 @@
return new TimeZoneDetectorImpl();
}});
+ registerService(Context.TIME_MANAGER, TimeManager.class,
+ new CachedServiceFetcher<TimeManager>() {
+ @Override
+ public TimeManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new TimeManager();
+ }});
+
registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
new CachedServiceFetcher<PermissionManager>() {
@Override
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
similarity index 84%
copy from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
copy to core/java/android/app/time/ITimeZoneDetectorListener.aidl
index 62240ba..723ad59 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
-parcelable TimeZoneConfiguration;
+/** {@hide} */
+oneway interface ITimeZoneDetectorListener {
+ void onChange();
+}
\ No newline at end of file
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
new file mode 100644
index 0000000..951905b
--- /dev/null
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.app.time."
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
new file mode 100644
index 0000000..9864afb
--- /dev/null
+++ b/core/java/android/app/time/TimeManager.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The interface through which system components can interact with time and time zone services.
+ *
+ * @hide
+ */
+// @SystemApi
+@SystemService(Context.TIME_MANAGER)
+public final class TimeManager {
+ private static final String TAG = "time.TimeManager";
+ private static final boolean DEBUG = false;
+
+ private final Object mLock = new Object();
+ private final ITimeZoneDetectorService mITimeZoneDetectorService;
+
+ @GuardedBy("mLock")
+ private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
+
+ /**
+ * The registered listeners. The key is the actual listener that was registered, the value is a
+ * wrapper that ensures the listener is executed on the correct Executor.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners;
+
+ /** @hide */
+ public TimeManager() throws ServiceNotFoundException {
+ // TimeManager is an API over one or possibly more services. At least until there's an
+ // internal refactoring.
+ mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+ }
+
+ /**
+ * Returns the calling user's time zone capabilities and configuration.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ @NonNull
+ public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
+ if (DEBUG) {
+ Log.d(TAG, "getTimeZoneCapabilities called");
+ }
+ try {
+ return mITimeZoneDetectorService.getCapabilitiesAndConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Modifies the time zone detection configuration.
+ *
+ * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+ * specific to the current user.
+ *
+ * <p>The ability to modify configuration settings can be subject to restrictions. For
+ * example, they may be determined by device hardware, general policy (i.e. only the primary
+ * user can set them), or by a managed device policy. Use {@link
+ * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's
+ * capabilities.
+ *
+ * <p>Attempts to modify configuration settings with capabilities that are {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
+ * will be returned. Modifying configuration settings with capabilities that are {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
+ * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
+ * TimeZoneCapabilities} for further details.
+ *
+ * <p>If the supplied configuration only has some values set, then only the specified settings
+ * will be updated (where the user's capabilities allow) and other settings will be left
+ * unchanged.
+ *
+ * @return {@code true} if all the configuration settings specified have been set to the
+ * new values, {@code false} if none have
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) {
+ if (DEBUG) {
+ Log.d(TAG, "updateConfiguration called: " + configuration);
+ }
+ try {
+ return mITimeZoneDetectorService.updateConfiguration(configuration);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * An interface that can be used to listen for changes to the time zone detector behavior.
+ */
+ @FunctionalInterface
+ public interface TimeZoneDetectorListener {
+ /**
+ * Called when something about the time zone detector behavior on the device has changed.
+ * For example, this could be because the current user has switched, one of the global or
+ * user's settings been changed, or something that could affect a user's capabilities with
+ * respect to the time zone detector has changed. Because different users can have different
+ * configuration and capabilities, this method may be called when nothing has changed for
+ * the receiving user.
+ */
+ void onChange();
+ }
+
+ /**
+ * Registers a listener that will be informed when something about the time zone detector
+ * behavior changes.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public void addTimeZoneDetectorListener(@NonNull Executor executor,
+ @NonNull TimeZoneDetectorListener listener) {
+
+ if (DEBUG) {
+ Log.d(TAG, "addTimeZoneDetectorListener called: " + listener);
+ }
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null) {
+ mTimeZoneDetectorListeners = new ArrayMap<>();
+ } else if (mTimeZoneDetectorListeners.containsKey(listener)) {
+ return;
+ }
+
+ if (mTimeZoneDetectorReceiver == null) {
+ ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() {
+ @Override
+ public void onChange() {
+ notifyTimeZoneDetectorListeners();
+ }
+ };
+ mTimeZoneDetectorReceiver = iListener;
+ try {
+ mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange));
+ }
+ }
+
+ private void notifyTimeZoneDetectorListeners() {
+ ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners;
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+ return;
+ }
+ timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners);
+ }
+ int size = timeZoneDetectorListeners.size();
+ for (int i = 0; i < size; i++) {
+ timeZoneDetectorListeners.valueAt(i).onChange();
+ }
+ }
+
+ /**
+ * Removes a listener previously passed to
+ * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)}
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "removeConfigurationListener called: " + listener);
+ }
+
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+ return;
+ }
+ mTimeZoneDetectorListeners.remove(listener);
+
+ // If the last local listener has been removed, remove and discard the
+ // mTimeZoneDetectorReceiver.
+ if (mTimeZoneDetectorListeners.isEmpty()) {
+ try {
+ mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mTimeZoneDetectorReceiver = null;
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilities.aidl
similarity index 94%
rename from core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
rename to core/java/android/app/time/TimeZoneCapabilities.aidl
index fede645..f744bf1 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
+++ b/core/java/android/app/time/TimeZoneCapabilities.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
parcelable TimeZoneCapabilities;
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
new file mode 100644
index 0000000..c62c2b3
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TimeZoneDetector;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Time zone-related capabilities for a user. A capability is the ability for the user to configure
+ * something or perform an action. This information is exposed so that system apps like SettingsUI
+ * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
+ * / available to the user.
+ *
+ * <p>Capabilities have states that users cannot change directly. They may influence some
+ * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
+ * by changing the configuration. See the {@code CAPABILITY_} constants for details.
+ *
+ * <p>Actions have associated methods, see the documentation for each action for details.
+ *
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities
+ * allow).
+ *
+ * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class TimeZoneCapabilities implements Parcelable {
+
+ /** @hide */
+ @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
+ CAPABILITY_POSSESSED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CapabilityState {}
+
+ /**
+ * Indicates that a capability is not supported on this device, e.g. because of form factor or
+ * hardware. The associated UI should usually not be shown to the user.
+ */
+ public static final int CAPABILITY_NOT_SUPPORTED = 10;
+
+ /**
+ * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+ * if the capability relates to the ability to modify settings the user is not able to.
+ * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
+ * device policy. Depending on the capability, this could mean the associated UI
+ * should be hidden, or displayed but disabled.
+ */
+ public static final int CAPABILITY_NOT_ALLOWED = 20;
+
+ /**
+ * Indicates that a capability is possessed but not currently applicable, e.g. if the
+ * capability relates to the ability to modify settings, the user has the ability to modify
+ * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+ * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+ * ineffective) depending on requirements.
+ */
+ public static final int CAPABILITY_NOT_APPLICABLE = 30;
+
+ /** Indicates that a capability is possessed by the user. */
+ public static final int CAPABILITY_POSSESSED = 40;
+
+ public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
+ new Creator<TimeZoneCapabilities>() {
+ public TimeZoneCapabilities createFromParcel(Parcel in) {
+ return TimeZoneCapabilities.createFromParcel(in);
+ }
+
+ public TimeZoneCapabilities[] newArray(int size) {
+ return new TimeZoneCapabilities[size];
+ }
+ };
+
+ /**
+ * The user the capabilities are for. This is used for object equality and debugging but there
+ * is no accessor.
+ */
+ @NonNull private final UserHandle mUserHandle;
+ private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+ private final @CapabilityState int mSuggestManualTimeZoneCapability;
+
+ private TimeZoneCapabilities(@NonNull Builder builder) {
+ this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
+ this.mConfigureAutoDetectionEnabledCapability =
+ builder.mConfigureAutoDetectionEnabledCapability;
+ this.mConfigureGeoDetectionEnabledCapability =
+ builder.mConfigureGeoDetectionEnabledCapability;
+ this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
+ }
+
+ @NonNull
+ private static TimeZoneCapabilities createFromParcel(Parcel in) {
+ UserHandle userHandle = UserHandle.readFromParcel(in);
+ return new TimeZoneCapabilities.Builder(userHandle)
+ .setConfigureAutoDetectionEnabledCapability(in.readInt())
+ .setConfigureGeoDetectionEnabledCapability(in.readInt())
+ .setSuggestManualTimeZoneCapability(in.readInt())
+ .build();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ UserHandle.writeToParcel(mUserHandle, dest);
+ dest.writeInt(mConfigureAutoDetectionEnabledCapability);
+ dest.writeInt(mConfigureGeoDetectionEnabledCapability);
+ dest.writeInt(mSuggestManualTimeZoneCapability);
+ }
+
+ /**
+ * Returns the capability state associated with the user's ability to modify the automatic time
+ * zone detection setting. The setting can be updated via {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+ */
+ @CapabilityState
+ public int getConfigureAutoDetectionEnabledCapability() {
+ return mConfigureAutoDetectionEnabledCapability;
+ }
+
+ /**
+ * Returns the capability state associated with the user's ability to modify the geolocation
+ * detection setting. The setting can be updated via {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+ */
+ @CapabilityState
+ public int getConfigureGeoDetectionEnabledCapability() {
+ return mConfigureGeoDetectionEnabledCapability;
+ }
+
+ /**
+ * Returns the capability state associated with the user's ability to manually set the time zone
+ * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
+ *
+ * <p>The suggestion will be ignored in all cases unless the value is {@link
+ * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
+ *
+ * @hide
+ */
+ @CapabilityState
+ public int getSuggestManualTimeZoneCapability() {
+ return mSuggestManualTimeZoneCapability;
+ }
+
+ /**
+ * Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of
+ * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is
+ * returned. If the capabilities do not permit one or more of the requested changes then {@code
+ * null} is returned.
+ *
+ * @hide
+ */
+ @Nullable
+ public TimeZoneConfiguration tryApplyConfigChanges(
+ @NonNull TimeZoneConfiguration config,
+ @NonNull TimeZoneConfiguration requestedChanges) {
+ TimeZoneConfiguration.Builder newConfigBuilder =
+ new TimeZoneConfiguration.Builder(config);
+ if (requestedChanges.hasIsAutoDetectionEnabled()) {
+ if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+ return null;
+ }
+ newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
+ }
+
+ if (requestedChanges.hasIsGeoDetectionEnabled()) {
+ if (this.getConfigureGeoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+ return null;
+ }
+ newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
+ }
+
+ return newConfigBuilder.build();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneCapabilities that = (TimeZoneCapabilities) o;
+ return mUserHandle.equals(that.mUserHandle)
+ && mConfigureAutoDetectionEnabledCapability
+ == that.mConfigureAutoDetectionEnabledCapability
+ && mConfigureGeoDetectionEnabledCapability
+ == that.mConfigureGeoDetectionEnabledCapability
+ && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
+ mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorCapabilities{"
+ + "mUserHandle=" + mUserHandle
+ + ", mConfigureAutoDetectionEnabledCapability="
+ + mConfigureAutoDetectionEnabledCapability
+ + ", mConfigureGeoDetectionEnabledCapability="
+ + mConfigureGeoDetectionEnabledCapability
+ + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+ + '}';
+ }
+
+ /** @hide */
+ public static class Builder {
+
+ @NonNull private UserHandle mUserHandle;
+ private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+ private @CapabilityState int mSuggestManualTimeZoneCapability;
+
+ public Builder(@NonNull UserHandle userHandle) {
+ mUserHandle = Objects.requireNonNull(userHandle);
+ }
+
+ /** Sets the state for the automatic time zone detection enabled config. */
+ public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
+ this.mConfigureAutoDetectionEnabledCapability = value;
+ return this;
+ }
+
+ /** Sets the state for the geolocation time zone detection enabled config. */
+ public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
+ this.mConfigureGeoDetectionEnabledCapability = value;
+ return this;
+ }
+
+ /** Sets the state for the suggestManualTimeZone action. */
+ public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
+ this.mSuggestManualTimeZoneCapability = value;
+ return this;
+ }
+
+ /** Returns the {@link TimeZoneCapabilities}. */
+ @NonNull
+ public TimeZoneCapabilities build() {
+ verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
+ "configureAutoDetectionEnabledCapability");
+ verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
+ "configureGeoDetectionEnabledCapability");
+ verifyCapabilitySet(mSuggestManualTimeZoneCapability,
+ "suggestManualTimeZoneCapability");
+ return new TimeZoneCapabilities(this);
+ }
+
+ private void verifyCapabilitySet(int value, String name) {
+ if (value == 0) {
+ throw new IllegalStateException(name + " not set");
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
similarity index 89%
copy from core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
copy to core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
index fede645..d7b6b58 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
-parcelable TimeZoneCapabilities;
+parcelable TimeZoneCapabilitiesAndConfig;
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
new file mode 100644
index 0000000..6a04f3f
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
+
+ public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
+ new Creator<TimeZoneCapabilitiesAndConfig>() {
+ public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+ }
+
+ public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeZoneCapabilitiesAndConfig[size];
+ }
+ };
+
+
+ @NonNull private final TimeZoneCapabilities mCapabilities;
+ @NonNull private final TimeZoneConfiguration mConfiguration;
+
+ /**
+ * Creates a new instance.
+ *
+ * @hide
+ */
+ public TimeZoneCapabilitiesAndConfig(
+ @NonNull TimeZoneCapabilities capabilities,
+ @NonNull TimeZoneConfiguration configuration) {
+ this.mCapabilities = Objects.requireNonNull(capabilities);
+ this.mConfiguration = Objects.requireNonNull(configuration);
+ }
+
+ @NonNull
+ private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ TimeZoneCapabilities capabilities = in.readParcelable(null);
+ TimeZoneConfiguration configuration = in.readParcelable(null);
+ return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mCapabilities, flags);
+ dest.writeParcelable(mConfiguration, flags);
+ }
+
+ /**
+ * Returns the user's time zone behavior capabilities.
+ */
+ @NonNull
+ public TimeZoneCapabilities getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Returns the user's time zone behavior configuration.
+ */
+ @NonNull
+ public TimeZoneConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
+ return mCapabilities.equals(that.mCapabilities)
+ && mConfiguration.equals(that.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCapabilities, mConfiguration);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorCapabilitiesAndConfig{"
+ + "mCapabilities=" + mCapabilities
+ + ", mConfiguration=" + mConfiguration
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/TimeZoneConfiguration.aidl
similarity index 94%
rename from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
rename to core/java/android/app/time/TimeZoneConfiguration.aidl
index 62240ba..8e85929 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/app/time/TimeZoneConfiguration.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
parcelable TimeZoneConfiguration;
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java
similarity index 65%
rename from core/java/android/app/timezonedetector/TimeZoneConfiguration.java
rename to core/java/android/app/time/TimeZoneConfiguration.java
index e879091..488818a 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/time/TimeZoneConfiguration.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -36,15 +35,13 @@
* several settings, the device behavior may not be directly affected by the setting value.
*
* <p>Settings can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} and those settings will not be
* changed. Not all configuration settings can be modified by all users: see {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
- *
- * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
- * of individual settings.
+ * TimeManager#getTimeZoneCapabilitiesAndConfig()} and {@link TimeZoneCapabilities} for details.
*
* @hide
*/
+// @SystemApi
public final class TimeZoneConfiguration implements Parcelable {
public static final @NonNull Creator<TimeZoneConfiguration> CREATOR =
@@ -58,53 +55,48 @@
}
};
- /** All configuration properties */
+ /**
+ * All configuration properties
+ *
+ * @hide
+ */
@StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
@Retention(RetentionPolicy.SOURCE)
@interface Setting {}
/** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
@Setting
- public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+ private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
/** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
@Setting
- public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+ private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
- private final @UserIdInt int mUserId;
@NonNull private final Bundle mBundle;
private TimeZoneConfiguration(Builder builder) {
- this.mUserId = builder.mUserId;
this.mBundle = Objects.requireNonNull(builder.mBundle);
}
private static TimeZoneConfiguration createFromParcel(Parcel in) {
- return new TimeZoneConfiguration.Builder(in.readInt())
+ return new TimeZoneConfiguration.Builder()
.setPropertyBundleInternal(in.readBundle())
.build();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mUserId);
dest.writeBundle(mBundle);
}
- /** Returns the ID of the user this configuration is associated with. */
- public @UserIdInt int getUserId() {
- return mUserId;
- }
-
- /** Returns {@code true} if all known settings are present. */
+ /**
+ * Returns {@code true} if all known settings are present.
+ *
+ * @hide
+ */
public boolean isComplete() {
- return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
- && hasSetting(SETTING_GEO_DETECTION_ENABLED);
- }
-
- /** Returns true if the specified setting is set. */
- public boolean hasSetting(@Setting String setting) {
- return mBundle.containsKey(setting);
+ return hasIsAutoDetectionEnabled()
+ && hasIsGeoDetectionEnabled();
}
/**
@@ -112,9 +104,10 @@
* controls whether a device will attempt to determine the time zone automatically using
* contextual information if the device supports auto detection.
*
- * <p>This setting is global and can be updated by some users.
+ * <p>See {@link TimeZoneCapabilities#getConfigureAutoDetectionEnabledCapability()} for how to
+ * tell if the setting is meaningful for the current user at this time.
*
- * @throws IllegalStateException if the setting has not been set
+ * @throws IllegalStateException if the setting is not present
*/
public boolean isAutoDetectionEnabled() {
enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
@@ -122,21 +115,39 @@
}
/**
+ * Returns {@code true} if the {@link #isAutoDetectionEnabled()} setting is present.
+ *
+ * @hide
+ */
+ public boolean hasIsAutoDetectionEnabled() {
+ return mBundle.containsKey(SETTING_AUTO_DETECTION_ENABLED);
+ }
+
+ /**
* Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
- * controls whether a device can use geolocation to determine time zone. Only used when
- * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
- * location to be used.
+ * controls whether the device can use geolocation to determine time zone. This value may only
+ * be used by Android under some circumstances. For example, it is not used when
+ * {@link #isGeoDetectionEnabled()} is {@code false}.
*
- * <p>This setting is user-scoped and can be updated by some users.
- * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
+ * <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to
+ * tell if the setting is meaningful for the current user at this time.
*
- * @throws IllegalStateException if the setting has not been set
+ * @throws IllegalStateException if the setting is not present
*/
public boolean isGeoDetectionEnabled() {
enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
}
+ /**
+ * Returns {@code true} if the {@link #isGeoDetectionEnabled()} setting is present.
+ *
+ * @hide
+ */
+ public boolean hasIsGeoDetectionEnabled() {
+ return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -151,20 +162,18 @@
return false;
}
TimeZoneConfiguration that = (TimeZoneConfiguration) o;
- return mUserId == that.mUserId
- && mBundle.kindofEquals(that.mBundle);
+ return mBundle.kindofEquals(that.mBundle);
}
@Override
public int hashCode() {
- return Objects.hash(mUserId, mBundle);
+ return Objects.hash(mBundle);
}
@Override
public String toString() {
return "TimeZoneConfiguration{"
- + "mUserId=" + mUserId
- + ", mBundle=" + mBundle
+ + "mBundle=" + mBundle
+ '}';
}
@@ -174,43 +183,43 @@
}
}
- /** @hide */
- public static class Builder {
+ /**
+ * A builder for {@link TimeZoneConfiguration} objects.
+ *
+ * @hide
+ */
+ // @SystemApi
+ public static final class Builder {
- private final @UserIdInt int mUserId;
private final Bundle mBundle = new Bundle();
/**
- * Creates a new Builder for a userId with no settings held.
+ * Creates a new Builder with no settings held.
*/
- public Builder(@UserIdInt int userId) {
- mUserId = userId;
+ public Builder() {
}
/**
- * Creates a new Builder by copying the user ID and settings from an existing instance.
+ * Creates a new Builder by copying the settings from an existing instance.
*/
- public Builder(TimeZoneConfiguration toCopy) {
- this.mUserId = toCopy.mUserId;
+ public Builder(@NonNull TimeZoneConfiguration toCopy) {
mergeProperties(toCopy);
}
/**
* Merges {@code other} settings into this instances, replacing existing values in this
* where the settings appear in both.
+ *
+ * @hide
*/
- public Builder mergeProperties(TimeZoneConfiguration other) {
- if (mUserId != other.mUserId) {
- throw new IllegalArgumentException(
- "Cannot merge configurations for different user IDs."
- + " this.mUserId=" + this.mUserId
- + ", other.mUserId=" + other.mUserId);
- }
+ @NonNull
+ public Builder mergeProperties(@NonNull TimeZoneConfiguration other) {
this.mBundle.putAll(other.mBundle);
return this;
}
- Builder setPropertyBundleInternal(Bundle bundle) {
+ @NonNull
+ Builder setPropertyBundleInternal(@NonNull Bundle bundle) {
this.mBundle.putAll(bundle);
return this;
}
@@ -218,6 +227,7 @@
/**
* Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
*/
+ @NonNull
public Builder setAutoDetectionEnabled(boolean enabled) {
this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
return this;
@@ -226,6 +236,7 @@
/**
* Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
*/
+ @NonNull
public Builder setGeoDetectionEnabled(boolean enabled) {
this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
return this;
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
deleted file mode 100644
index 6d0fe72..0000000
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import android.app.timezonedetector.TimeZoneConfiguration;
-
-/** {@hide} */
-oneway interface ITimeZoneConfigurationListener {
- void onChange();
-}
\ No newline at end of file
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 4f7e1f6..af0389a 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -16,11 +16,11 @@
package android.app.timezonedetector;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
/**
* System private API to communicate with time zone detector service.
@@ -35,9 +35,9 @@
* {@hide}
*/
interface ITimeZoneDetectorService {
- TimeZoneCapabilities getCapabilities();
- void addConfigurationListener(ITimeZoneConfigurationListener listener);
- void removeConfigurationListener(ITimeZoneConfigurationListener listener);
+ TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig();
+ void addListener(ITimeZoneDetectorListener listener);
+ void removeListener(ITimeZoneDetectorListener listener);
boolean updateConfiguration(in TimeZoneConfiguration configuration);
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
deleted file mode 100644
index 09fffe9..0000000
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Time zone-related capabilities for a user. A capability is the ability for the user to configure
- * something or perform an action. This information is exposed so that system apps like SettingsUI
- * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
- * / available to the user.
- *
- * <p>Capabilities have states that users cannot change directly. They may influence some
- * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
- * by changing the configuration. See the {@code CAPABILITY_} constants for details.
- *
- * <p>Actions have associated methods, see the documentation for each action for details.
- *
- * <p>For configuration settings capabilities, the associated settings value can be found via
- * {@link #getConfiguration()} and may be changed using {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
- *
- * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
- *
- * @hide
- */
-public final class TimeZoneCapabilities implements Parcelable {
-
- @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
- CAPABILITY_POSSESSED })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CapabilityState {}
-
- /**
- * Indicates that a capability is not supported on this device, e.g. because of form factor or
- * hardware. The associated UI should usually not be shown to the user.
- */
- public static final int CAPABILITY_NOT_SUPPORTED = 10;
-
- /**
- * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
- * if the capability relates to the ability to modify settings the user is not able to.
- * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
- * device policy. Depending on the capability, this could mean the associated UI
- * should be hidden, or displayed but disabled.
- */
- public static final int CAPABILITY_NOT_ALLOWED = 20;
-
- /**
- * Indicates that a capability is possessed but not currently applicable, e.g. if the
- * capability relates to the ability to modify settings, the user has the ability to modify
- * it, but it is currently rendered irrelevant by other settings or other device state (flags,
- * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
- * ineffective) depending on requirements.
- */
- public static final int CAPABILITY_NOT_APPLICABLE = 30;
-
- /** Indicates that a capability is possessed by the user. */
- public static final int CAPABILITY_POSSESSED = 40;
-
- public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
- new Creator<TimeZoneCapabilities>() {
- public TimeZoneCapabilities createFromParcel(Parcel in) {
- return TimeZoneCapabilities.createFromParcel(in);
- }
-
- public TimeZoneCapabilities[] newArray(int size) {
- return new TimeZoneCapabilities[size];
- }
- };
-
-
- @NonNull private final TimeZoneConfiguration mConfiguration;
- private final @CapabilityState int mConfigureAutoDetectionEnabled;
- private final @CapabilityState int mConfigureGeoDetectionEnabled;
- private final @CapabilityState int mSuggestManualTimeZone;
-
- private TimeZoneCapabilities(@NonNull Builder builder) {
- this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
- this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
- this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
- this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
- }
-
- @NonNull
- private static TimeZoneCapabilities createFromParcel(Parcel in) {
- return new TimeZoneCapabilities.Builder()
- .setConfiguration(in.readParcelable(null))
- .setConfigureAutoDetectionEnabled(in.readInt())
- .setConfigureGeoDetectionEnabled(in.readInt())
- .setSuggestManualTimeZone(in.readInt())
- .build();
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mConfiguration, flags);
- dest.writeInt(mConfigureAutoDetectionEnabled);
- dest.writeInt(mConfigureGeoDetectionEnabled);
- dest.writeInt(mSuggestManualTimeZone);
- }
-
- /**
- * Returns the user's time zone behavior configuration.
- */
- public @NonNull TimeZoneConfiguration getConfiguration() {
- return mConfiguration;
- }
-
- /**
- * Returns the capability state associated with the user's ability to modify the automatic time
- * zone detection setting. The setting can be updated via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
- * #getConfiguration()}.
- */
- @CapabilityState
- public int getConfigureAutoDetectionEnabled() {
- return mConfigureAutoDetectionEnabled;
- }
-
- /**
- * Returns the capability state associated with the user's ability to modify the geolocation
- * detection setting. The setting can be updated via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
- * #getConfiguration()}.
- */
- @CapabilityState
- public int getConfigureGeoDetectionEnabled() {
- return mConfigureGeoDetectionEnabled;
- }
-
- /**
- * Returns the capability state associated with the user's ability to manually set the time zone
- * on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
- *
- * <p>The suggestion will be ignored in all cases unless the value is {@link
- * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
- */
- @CapabilityState
- public int getSuggestManualTimeZone() {
- return mSuggestManualTimeZone;
- }
-
- /**
- * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
- * {@code requestedChanges}, if the current capabilities allow. The new configuration is
- * returned and the capabilities are left unchanged. If the capabilities do not permit one or
- * more of the changes then {@code null} is returned.
- */
- @Nullable
- public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
- if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
- throw new IllegalArgumentException("User does not match:"
- + " this=" + mConfiguration + ", other=" + requestedChanges);
- }
-
- TimeZoneConfiguration.Builder newConfigBuilder =
- new TimeZoneConfiguration.Builder(mConfiguration);
- if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
- if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
- return null;
- }
- newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
- }
-
- if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
- if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
- return null;
- }
- newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
- }
-
- return newConfigBuilder.build();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- TimeZoneCapabilities that = (TimeZoneCapabilities) o;
- return Objects.equals(mConfiguration, that.mConfiguration)
- && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
- && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
- && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mConfiguration,
- mConfigureAutoDetectionEnabled,
- mConfigureGeoDetectionEnabled,
- mSuggestManualTimeZone);
- }
-
- @Override
- public String toString() {
- return "TimeZoneDetectorCapabilities{"
- + "mConfiguration=" + mConfiguration
- + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
- + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
- + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
- + '}';
- }
-
- /** @hide */
- public static class Builder {
-
- private TimeZoneConfiguration mConfiguration;
- private @CapabilityState int mConfigureAutoDetectionEnabled;
- private @CapabilityState int mConfigureGeoDetectionEnabled;
- private @CapabilityState int mSuggestManualTimeZone;
-
- /** Sets the user-visible configuration settings. */
- public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
- if (!configuration.isComplete()) {
- throw new IllegalArgumentException(configuration + " is not complete");
- }
- this.mConfiguration = configuration;
- return this;
- }
-
- /** Sets the state for the automatic time zone detection enabled config. */
- public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) {
- this.mConfigureAutoDetectionEnabled = value;
- return this;
- }
-
- /** Sets the state for the geolocation time zone detection enabled config. */
- public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
- this.mConfigureGeoDetectionEnabled = value;
- return this;
- }
-
- /** Sets the state for the suggestManualTimeZone action. */
- public Builder setSuggestManualTimeZone(@CapabilityState int value) {
- this.mSuggestManualTimeZone = value;
- return this;
- }
-
- /** Returns the {@link TimeZoneCapabilities}. */
- @NonNull
- public TimeZoneCapabilities build() {
- verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
- verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
- verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
- return new TimeZoneCapabilities(this);
- }
-
- private void verifyCapabilitySet(int value, String name) {
- if (value == 0) {
- throw new IllegalStateException(name + " not set");
- }
- }
- }
-}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 2b1cbf2..486232d 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -30,70 +30,6 @@
public interface TimeZoneDetector {
/**
- * Returns the current user's time zone capabilities. See {@link TimeZoneCapabilities}.
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- @NonNull
- TimeZoneCapabilities getCapabilities();
-
- /**
- * Modifies the time zone detection configuration.
- *
- * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
- * specific to the current user.
- *
- * <p>The ability to modify configuration settings can be subject to restrictions. For
- * example, they may be determined by device hardware, general policy (i.e. only the primary
- * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
- * information at runtime about the user's capabilities.
- *
- * <p>Attempts to modify configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
- * will be returned. Modifying configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
- * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
- * TimeZoneCapabilities} for further details.
- *
- * <p>If the supplied configuration only has some values set, then only the specified settings
- * will be updated (where the user's capabilities allow) and other settings will be left
- * unchanged.
- *
- * @return {@code true} if all the configuration settings specified have been set to the
- * new values, {@code false} if none have
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
-
- /**
- * An interface that can be used to listen for changes to the time zone detector configuration.
- */
- @FunctionalInterface
- interface TimeZoneConfigurationListener {
- /**
- * Called when something about the time zone configuration on the device has changed.
- * This could be because the current user has changed, one of the device's relevant settings
- * has changed, or something that could affect a user's capabilities has changed.
- * There are no guarantees about the thread used.
- */
- void onChange();
- }
-
- /**
- * Registers a listener that will be informed when something about the time zone configuration
- * changes.
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
- /**
- * Removes a listener previously passed to
- * {@link #addConfigurationListener(ITimeZoneConfigurationListener)}
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
- /**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
*
* @hide
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 4c69732..3bd6b4b 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -21,7 +21,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.ArraySet;
import android.util.Log;
/**
@@ -35,108 +34,12 @@
private final ITimeZoneDetectorService mITimeZoneDetectorService;
- private ITimeZoneConfigurationListener mConfigurationReceiver;
- private ArraySet<TimeZoneConfigurationListener> mConfigurationListeners;
-
public TimeZoneDetectorImpl() throws ServiceNotFoundException {
mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
}
@Override
- @NonNull
- public TimeZoneCapabilities getCapabilities() {
- if (DEBUG) {
- Log.d(TAG, "getCapabilities called");
- }
- try {
- return mITimeZoneDetectorService.getCapabilities();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
- public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
- if (DEBUG) {
- Log.d(TAG, "updateConfiguration called: " + configuration);
- }
- try {
- return mITimeZoneDetectorService.updateConfiguration(configuration);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
- public void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
- if (DEBUG) {
- Log.d(TAG, "addConfigurationListener called: " + listener);
- }
- synchronized (this) {
- if (mConfigurationListeners.contains(listener)) {
- return;
- }
- if (mConfigurationReceiver == null) {
- ITimeZoneConfigurationListener iListener =
- new ITimeZoneConfigurationListener.Stub() {
- @Override
- public void onChange() {
- notifyConfigurationListeners();
- }
- };
- mConfigurationReceiver = iListener;
- }
- if (mConfigurationListeners == null) {
- mConfigurationListeners = new ArraySet<>();
- }
-
- boolean wasEmpty = mConfigurationListeners.isEmpty();
- mConfigurationListeners.add(listener);
- if (wasEmpty) {
- try {
- mITimeZoneDetectorService.addConfigurationListener(mConfigurationReceiver);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- private void notifyConfigurationListeners() {
- final ArraySet<TimeZoneConfigurationListener> configurationListeners;
- synchronized (this) {
- configurationListeners = new ArraySet<>(mConfigurationListeners);
- }
- int size = configurationListeners.size();
- for (int i = 0; i < size; i++) {
- configurationListeners.valueAt(i).onChange();
- }
- }
-
- @Override
- public void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
- if (DEBUG) {
- Log.d(TAG, "removeConfigurationListener called: " + listener);
- }
-
- synchronized (this) {
- if (mConfigurationListeners == null) {
- return;
- }
- boolean wasEmpty = mConfigurationListeners.isEmpty();
- mConfigurationListeners.remove(listener);
- if (mConfigurationListeners.isEmpty() && !wasEmpty) {
- try {
- mITimeZoneDetectorService.removeConfigurationListener(mConfigurationReceiver);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- @Override
public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 005648f..666ba32 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
+import android.app.time.TimeManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -5090,6 +5091,14 @@
public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_MANAGER = "time_manager";
+
+ /**
* Binder service name for {@link AppBindingService}.
* @hide
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 79e23b3..a600d6c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -866,6 +866,12 @@
* is set the restricted permissions will be whitelisted for all users, otherwise
* only to the owner.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @hide
*/
public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
@@ -3505,6 +3511,17 @@
public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17;
/**
+ * Permission flag: The permission is restricted but the app is exempt
+ * from the restriction and is allowed to hold this permission in its
+ * full form and the exemption is provided by the held roles.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18;
+
+ /**
* Permission flags: Reserved for use by the permission controller. The platform and any
* packages besides the permission controller should not assume any definition about these
* flags.
@@ -3522,7 +3539,8 @@
public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
| FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+ | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
/**
* Mask for all permission flags.
@@ -3568,13 +3586,27 @@
/**
* Permission whitelist flag: permissions whitelisted by the system.
- * Permissions can also be whitelisted by the installer or on upgrade.
+ * Permissions can also be whitelisted by the installer, on upgrade, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1 << 0;
/**
* Permission whitelist flag: permissions whitelisted by the installer.
- * Permissions can also be whitelisted by the system or on upgrade.
+ * Permissions can also be whitelisted by the system, on upgrade, or on role
+ * grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 1 << 1;
@@ -3582,15 +3614,31 @@
* Permission whitelist flag: permissions whitelisted by the system
* when upgrading from an OS version where the permission was not
* restricted to an OS version where the permission is restricted.
- * Permissions can also be whitelisted by the installer or the system.
+ * Permissions can also be whitelisted by the installer, the system, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2;
+ /**
+ * Permission allowlist flag: permissions exempted by the system
+ * when being granted a role.
+ * Permissions can also be exempted by the installer, the system, or on
+ * upgrade.
+ */
+ public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3;
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = {
FLAG_PERMISSION_WHITELIST_SYSTEM,
FLAG_PERMISSION_WHITELIST_INSTALLER,
- FLAG_PERMISSION_WHITELIST_UPGRADE
+ FLAG_PERMISSION_WHITELIST_UPGRADE,
+ FLAG_PERMISSION_ALLOWLIST_ROLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionWhitelistFlags {}
@@ -4536,7 +4584,7 @@
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four allowlists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the{@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4553,6 +4601,17 @@
* Can be accessed by pre-installed holders of a dedicated permission or the
* installer on record.
*
+ * <li>one for cases where the system exempts the permission when granting a role.
+ * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can
+ * be accessed by pre-installed holders of a dedicated permission.
+ * </ol>
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param whitelistFlag The flag to determine which whitelist to query. Only one flag
* can be passed.s
@@ -4563,6 +4622,7 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to access a whitelist that you have no access to.
*/
@@ -4584,7 +4644,7 @@
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four whitelists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4602,10 +4662,21 @@
* Can be modified by pre-installed holders of a dedicated permission or the installer
* on record.
*
+ * <li>one for cases where the system exempts the permission when permission when
+ * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE}
+ * flag. Can be modified by pre-installed holders of a dedicated permission.
+ * </ol>
+ *
* <p>You need to specify the whitelists for which to set the whitelisted permissions
* which will clear the previous whitelisted permissions and replace them with the
* provided ones.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param permName The whitelisted permission to add.
* @param whitelistFlags The whitelists to which to add. Passing multiple flags
@@ -4617,6 +4688,7 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
@@ -4638,7 +4710,7 @@
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four whitelists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4656,10 +4728,24 @@
* Can be modified by pre-installed holders of a dedicated permission or the installer
* on record.
*
+ * <li>one for cases where the system exempts the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+ * holders of a dedicated permission. The installer on record can only remove
+ * permissions from this allowlist.
+ * </ol>
+ *
* <p>You need to specify the whitelists for which to set the whitelisted permissions
* which will clear the previous whitelisted permissions and replace them with the
* provided ones.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param permName The whitelisted permission to remove.
* @param whitelistFlags The whitelists from which to remove. Passing multiple flags
@@ -4671,6 +4757,7 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
@@ -4691,6 +4778,12 @@
* un-whitelist the packages it installs, unless auto-revoking permissions from that package
* would cause breakages beyond having to re-request the permission(s).
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to set exemption.
* @param whitelisted Whether the app should be whitelisted.
*
@@ -4712,6 +4805,13 @@
*
* Only the installer on record that installed the given package, or a holder of
* {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to set exemption.
*
* @return Whether the app is whitelisted.
@@ -8026,6 +8126,12 @@
}
/**
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @return whether this package is whitelisted from having its runtime permission be
* auto-revoked if unused for an extended period of time.
*/
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 04e15c2..5d4c843 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -377,6 +377,14 @@
public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4;
/**
+ * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ *
+ * <p> Modifier for permission restriction. This permission cannot be exempted by the installer.
+ */
+ public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5;
+
+ /**
* Flag for {@link #flags}, indicating that this permission has been
* installed into the system's globally defined permissions.
*/
@@ -656,6 +664,11 @@
}
/** @hide */
+ public boolean isInstallerExemptIgnored() {
+ return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+ }
+
+ /** @hide */
public boolean isAppOp() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 986e6ea..9d4ab0b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -72,6 +72,7 @@
import android.util.Size;
import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -351,6 +352,7 @@
if (mMetadataPtr == 0) {
throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
}
+ updateNativeAllocation();
}
/**
@@ -362,6 +364,7 @@
if (mMetadataPtr == 0) {
throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
}
+ updateNativeAllocation();
}
/**
@@ -443,6 +446,7 @@
public void readFromParcel(Parcel in) {
nativeReadFromParcel(in, mMetadataPtr);
+ updateNativeAllocation();
}
/**
@@ -533,6 +537,11 @@
// Delete native pointer, but does not clear it
nativeClose(mMetadataPtr);
mMetadataPtr = 0;
+
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+ }
+ mBufferSize = 0;
}
private <T> T getBase(CameraCharacteristics.Key<T> key) {
@@ -1645,9 +1654,26 @@
return true;
}
+ private void updateNativeAllocation() {
+ long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
+
+ if (currentBufferSize != mBufferSize) {
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+ }
+
+ mBufferSize = currentBufferSize;
+
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeAllocation(mBufferSize);
+ }
+ }
+ }
+
private int mCameraId = -1;
private boolean mHasMandatoryConcurrentStreams = false;
private Size mDisplaySize = new Size(0, 0);
+ private long mBufferSize = 0;
/**
* Set the current camera Id.
@@ -1705,6 +1731,8 @@
private static synchronized native boolean nativeIsEmpty(long ptr);
@FastNative
private static synchronized native int nativeGetEntryCount(long ptr);
+ @FastNative
+ private static synchronized native long nativeGetBufferSize(long ptr);
@UnsupportedAppUsage
@FastNative
@@ -1744,6 +1772,8 @@
mCameraId = other.mCameraId;
mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
mDisplaySize = other.mDisplaySize;
+ updateNativeAllocation();
+ other.updateNativeAllocation();
}
/**
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 042808a4..be52667 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -660,6 +660,8 @@
ThreadedRenderer.setFPSDivisor(divisor);
}
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+ + "#postFrameCallback} instead")
void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
final long startNanos;
synchronized (mLock) {
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 51474d3..430e2cf 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -17,6 +17,7 @@
package android.view;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
@@ -157,6 +158,8 @@
* @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame
* produced by HWUI with the timeline data stored in Surface Flinger.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+ + "Choreographer#postFrameCallback} instead")
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
long frameTimelineVsyncId) {
}
@@ -200,6 +203,8 @@
// Called from native code.
@SuppressWarnings("unused")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
+ + "Choreographer#postFrameCallback} instead")
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
long frameTimelineVsyncId) {
onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 44c754c..9ba886a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2465,8 +2465,7 @@
* {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar
* changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished
* when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE}
- * (because the autofill service or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill
- * service disabled further autofill requests for the activity).
+ * (because the autofill service disabled further autofill requests for the activity).
* @param autofillableIds list of ids that could trigger autofill, use to not handle a new
* session when they're entered.
*/
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 0535365..16fd383 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -643,54 +643,58 @@
// change should also get get rid of the "internalNotifyXXXX" methods above
void notifyChildSessionStarted(int parentSessionId, int childSessionId,
@NonNull ContentCaptureContext clientContext) {
- sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
.setParentSessionId(parentSessionId).setClientContext(clientContext),
- FORCE_FLUSH);
+ FORCE_FLUSH));
}
void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
- sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
- .setParentSessionId(parentSessionId), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId), FORCE_FLUSH));
}
void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
- .setViewNode(node.mNode));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+ .setViewNode(node.mNode)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
}
void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
- .setText(text));
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+ .setAutofillId(id).setText(text)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
- .setInsets(viewInsets));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+ .setInsets(viewInsets)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewTreeEvent(int sessionId, boolean started) {
final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
- sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
}
void notifySessionResumed(int sessionId) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
}
void notifySessionPaused(int sessionId) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
}
void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
- .setClientContext(context));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+ .setClientContext(context)));
}
@Override
diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl
new file mode 100644
index 0000000..a8a29b2
--- /dev/null
+++ b/core/java/android/window/ITransitionPlayer.aidl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * Implemented by WMShell to initiate and play transition animations.
+ * The flow (with {@link IWindowOrganizerController}) looks like this:
+ * <p><ol>
+ * <li>Core starts an activity and calls {@link #requestStartTransition}
+ * <li>This TransitionPlayer impl does whatever, then calls
+ * {@link IWindowOrganizerController#startTransition} to tell Core to formally start (until
+ * this happens, Core will collect changes on the transition, but won't consider it ready to
+ * animate).
+ * <li>Once all collected changes on the transition have finished drawing, Core will then call
+ * {@link #onTransitionReady} here to delegate the actual animation.
+ * <li>Once this TransitionPlayer impl finishes animating, it notifies Core via
+ * {@link IWindowOrganizerController#finishTransition}. At this point, ITransitionPlayer's
+ * responsibilities end.
+ * </ul>
+ *
+ * {@hide}
+ */
+oneway interface ITransitionPlayer {
+
+ /**
+ * Called when all participants of a transition are ready to animate. This is in response to
+ * {@link IWindowOrganizerController#startTransition}.
+ *
+ * @param transitionToken An identifying token for the transition that is now ready to animate.
+ * @param info A collection of all the changes encapsulated by this transition.
+ * @param t A surface transaction containing the surface state prior to animating.
+ */
+ void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
+ in SurfaceControl.Transaction t);
+
+ /**
+ * Called when something in WMCore requires a transition to play -- for example when an Activity
+ * is started in a new Task.
+ *
+ * @param type The {@link WindowManager#TransitionType} of the transition to start.
+ * @param transitionToken An identifying token for the transition that needs to be started.
+ * Pass this to {@link IWindowOrganizerController#startTransition}.
+ */
+ void requestStartTransition(int type, in IBinder transitionToken);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 7e9c783..0cd9b36 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -18,8 +18,10 @@
import android.view.SurfaceControl;
+import android.os.IBinder;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -45,6 +47,30 @@
int applySyncTransaction(in WindowContainerTransaction t,
in IWindowContainerTransactionCallback callback);
+ /**
+ * Starts a transition.
+ * @param type The transition type.
+ * @param transitionToken A token associated with the transition to start. If null, a new
+ * transition will be created of the provided type.
+ * @param t Operations that are part of the transition.
+ * @return a token representing the transition. This will just be transitionToken if it was
+ * non-null.
+ */
+ IBinder startTransition(int type, in @nullable IBinder transitionToken,
+ in @nullable WindowContainerTransaction t);
+
+ /**
+ * Finishes a transition. This must be called for all created transitions.
+ * @param transitionToken Which transition to finish
+ * @param t Changes to make before finishing but in the same SF Transaction. Can be null.
+ * @param callback Called when t is finished applying.
+ * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
+ * negative if no sync transaction was attached (null t or callback)
+ */
+ int finishTransition(in IBinder transitionToken,
+ in @nullable WindowContainerTransaction t,
+ in IWindowContainerTransactionCallback callback);
+
/** @return An interface enabling the management of task organizers. */
ITaskOrganizerController getTaskOrganizerController();
@@ -61,4 +87,10 @@
* @return true if the screenshot was successful, false otherwise.
*/
boolean takeScreenshot(in WindowContainerToken token, out SurfaceControl outSurfaceControl);
+
+ /**
+ * Registers a transition player with Core. There is only one of these at a time and calling
+ * this will replace the existing one if set.
+ */
+ void registerTransitionPlayer(in ITransitionPlayer player);
}
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/window/TransitionInfo.aidl
similarity index 87%
copy from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
copy to core/java/android/window/TransitionInfo.aidl
index 62240ba..6c33e97 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/window/TransitionInfo.aidl
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.window;
-parcelable TimeZoneConfiguration;
+parcelable TransitionInfo;
+parcelable TransitionInfo.Change;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
new file mode 100644
index 0000000..34d1d4e
--- /dev/null
+++ b/core/java/android/window/TransitionInfo.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to communicate information about what is changing during a transition to a TransitionPlayer.
+ * @hide
+ */
+public final class TransitionInfo implements Parcelable {
+
+ /** No transition mode. This is a placeholder, don't use this as an actual mode. */
+ public static final int TRANSIT_NONE = 0;
+
+ /** The container didn't exist before but will exist and be visible after. */
+ public static final int TRANSIT_OPEN = 1;
+
+ /** The container existed and was visible before but won't exist after. */
+ public static final int TRANSIT_CLOSE = 2;
+
+ /** The container existed before but was invisible and will be visible after. */
+ public static final int TRANSIT_SHOW = 3;
+
+ /** The container is going from visible to invisible but it will still exist after. */
+ public static final int TRANSIT_HIDE = 4;
+
+ /** The container exists and is visible before and after but it changes. */
+ public static final int TRANSIT_CHANGE = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "TRANSIT_" }, value = {
+ TRANSIT_NONE,
+ TRANSIT_OPEN,
+ TRANSIT_CLOSE,
+ TRANSIT_SHOW,
+ TRANSIT_HIDE,
+ TRANSIT_CHANGE
+ })
+ public @interface TransitionMode {}
+
+ private final @WindowManager.TransitionType int mType;
+ private final ArrayList<Change> mChanges = new ArrayList<>();
+
+ /** @hide */
+ public TransitionInfo(@WindowManager.TransitionType int type) {
+ mType = type;
+ }
+
+ private TransitionInfo(Parcel in) {
+ mType = in.readInt();
+ in.readList(mChanges, null /* classLoader */);
+ }
+
+ @Override
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeList(mChanges);
+ }
+
+ @NonNull
+ public static final Creator<TransitionInfo> CREATOR =
+ new Creator<TransitionInfo>() {
+ @Override
+ public TransitionInfo createFromParcel(Parcel in) {
+ return new TransitionInfo(in);
+ }
+
+ @Override
+ public TransitionInfo[] newArray(int size) {
+ return new TransitionInfo[size];
+ }
+ };
+
+ @Override
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ @NonNull
+ public List<Change> getChanges() {
+ return mChanges;
+ }
+
+ /**
+ * @return the Change that a window is undergoing or {@code null} if not directly
+ * represented.
+ */
+ @Nullable
+ public Change getChange(@NonNull WindowContainerToken token) {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ if (mChanges.get(i).mContainer == token) {
+ return mChanges.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a {@link Change} to this transition.
+ */
+ public void addChange(@NonNull Change change) {
+ mChanges.add(change);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{t=" + mType + " c=[");
+ for (int i = 0; i < mChanges.size(); ++i) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mChanges.get(i));
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+
+ /** Converts a transition mode/action to its string representation. */
+ @NonNull
+ public static String modeToString(@TransitionMode int mode) {
+ switch(mode) {
+ case TRANSIT_NONE: return "NONE";
+ case TRANSIT_OPEN: return "OPEN";
+ case TRANSIT_CLOSE: return "CLOSE";
+ case TRANSIT_SHOW: return "SHOW";
+ case TRANSIT_HIDE: return "HIDE";
+ case TRANSIT_CHANGE: return "CHANGE";
+ default: return "<unknown:" + mode + ">";
+ }
+ }
+
+ /** Represents the change a WindowContainer undergoes during a transition */
+ public static final class Change implements Parcelable {
+ private final WindowContainerToken mContainer;
+ private WindowContainerToken mParent;
+ private final SurfaceControl mLeash;
+ private int mMode = TRANSIT_NONE;
+ private final Rect mStartBounds = new Rect();
+ private final Rect mEndBounds = new Rect();
+
+ public Change(@NonNull WindowContainerToken container, @NonNull SurfaceControl leash) {
+ mContainer = container;
+ mLeash = leash;
+ }
+
+ private Change(Parcel in) {
+ mContainer = WindowContainerToken.CREATOR.createFromParcel(in);
+ mParent = in.readParcelable(WindowContainerToken.class.getClassLoader());
+ mLeash = new SurfaceControl();
+ mLeash.readFromParcel(in);
+ mMode = in.readInt();
+ mStartBounds.readFromParcel(in);
+ mEndBounds.readFromParcel(in);
+ }
+
+ /** Sets the parent of this change's container. The parent must be a participant or null. */
+ public void setParent(@Nullable WindowContainerToken parent) {
+ mParent = parent;
+ }
+
+ /** Sets the transition mode for this change */
+ public void setMode(@TransitionMode int mode) {
+ mMode = mode;
+ }
+
+ /** Sets the bounds this container occupied before the change */
+ public void setStartBounds(@Nullable Rect rect) {
+ mStartBounds.set(rect);
+ }
+
+ /** Sets the bounds this container will occupy after the change */
+ public void setEndBounds(@Nullable Rect rect) {
+ mEndBounds.set(rect);
+ }
+
+ /** @return the container that is changing */
+ @NonNull
+ public WindowContainerToken getContainer() {
+ return mContainer;
+ }
+
+ /**
+ * @return the parent of the changing container. This is the parent within the participants,
+ * not necessarily the actual parent.
+ */
+ @Nullable
+ public WindowContainerToken getParent() {
+ return mParent;
+ }
+
+ /** @return which action this change represents. */
+ public @TransitionMode int getMode() {
+ return mMode;
+ }
+
+ /**
+ * @return the bounds of the container before the change. It may be empty if the container
+ * is coming into existence.
+ */
+ @NonNull
+ public Rect getStartBounds() {
+ return mStartBounds;
+ }
+
+ /**
+ * @return the bounds of the container after the change. It may be empty if the container
+ * is disappearing.
+ */
+ @NonNull
+ public Rect getEndBounds() {
+ return mEndBounds;
+ }
+
+ /** @return the leash or surface to animate for this container */
+ @NonNull
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
+ @Override
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mContainer.writeToParcel(dest, flags);
+ dest.writeParcelable(mParent, 0);
+ mLeash.writeToParcel(dest, flags);
+ dest.writeInt(mMode);
+ mStartBounds.writeToParcel(dest, flags);
+ mEndBounds.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public static final Creator<Change> CREATOR =
+ new Creator<Change>() {
+ @Override
+ public Change createFromParcel(Parcel in) {
+ return new Change(in);
+ }
+
+ @Override
+ public Change[] newArray(int size) {
+ return new Change[size];
+ }
+ };
+
+ @Override
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ + " m=" + modeToString(mMode) + " sb=" + mStartBounds
+ + " eb=" + mEndBounds + "}";
+ }
+ }
+}
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index c92ccae..96e8b44 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -78,6 +78,11 @@
}
@Override
+ public String toString() {
+ return "WCT{" + mRealToken + "}";
+ }
+
+ @Override
public boolean equals(Object obj) {
if (!(obj instanceof WindowContainerToken)) {
return false;
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 97a97d9..5ac19fa 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -19,8 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityTaskManager;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Singleton;
import android.view.SurfaceControl;
@@ -66,6 +68,48 @@
}
/**
+ * Start a transition.
+ * @param type The type of the transition. This is ignored if a transitionToken is provided.
+ * @param transitionToken An existing transition to start. If null, a new transition is created.
+ * @param t The set of window operations that are part of this transition.
+ * @return A token identifying the transition. This will be the same as transitionToken if it
+ * was provided.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ @NonNull
+ public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ @Nullable WindowContainerTransaction t) {
+ try {
+ return getWindowOrganizerController().startTransition(type, transitionToken, t);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Finishes a running transition.
+ * @param transitionToken The transition to finish. Can't be null.
+ * @param t A set of window operations to apply before finishing.
+ * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
+ * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
+ *
+ * @hide
+ */
+ @SuppressLint("ExecutorRegistration")
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public int finishTransition(@NonNull IBinder transitionToken,
+ @Nullable WindowContainerTransaction t,
+ @Nullable WindowContainerTransactionCallback callback) {
+ try {
+ return getWindowOrganizerController().finishTransition(transitionToken, t,
+ callback != null ? callback.mInterface : null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Take a screenshot for a specified Window
* @param token The token for the WindowContainer that should get a screenshot taken.
* @return A SurfaceControl where the screenshot will be attached, or null if failed.
@@ -87,6 +131,19 @@
}
}
+ /**
+ * Register an ITransitionPlayer to handle transition animations.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ try {
+ getWindowOrganizerController().registerTransitionPlayer(player);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 9874c6a..50ba42f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -74,6 +74,8 @@
Consts.TAG_WM),
WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index cc2934f..caae518 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -242,4 +242,10 @@
* @param connect {@code true} if needs connection, otherwise set the connection to null.
*/
void requestWindowMagnificationConnection(boolean connect);
+
+ /**
+ * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the
+ * file descriptor passed in.
+ */
+ void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 396a84f..ed663cf 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -55,7 +56,6 @@
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -95,7 +95,7 @@
private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku";
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
- int[] mGlobalGids;
+ int[] mGlobalGids = EmptyArray.INT;
// These are the built-in uid -> permission mappings that were read from the
// system configuration files.
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 859b40a..919e351 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -249,6 +249,16 @@
return metadata->entryCount();
}
+static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) {
+ ALOGV("%s", __FUNCTION__);
+
+ CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
+
+ if (metadata == NULL) return 0;
+
+ return metadata->bufferSize();
+}
+
// idempotent. calling more than once has no effect.
static void CameraMetadata_close(JNIEnv *env, jclass thiz, jlong ptr) {
ALOGV("%s", __FUNCTION__);
@@ -561,6 +571,9 @@
{ "nativeGetEntryCount",
"(J)I",
(void*)CameraMetadata_getEntryCount },
+ { "nativeGetBufferSize",
+ "(J)J",
+ (void*)CameraMetadata_getBufferSize },
{ "nativeClose",
"(J)V",
(void*)CameraMetadata_close },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 723cceb..7247e4f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1244,8 +1244,19 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_recordAudio"
android:description="@string/permdesc_recordAudio"
+ android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
android:protectionLevel="dangerous|instant" />
+ <!-- Allows an application to record audio while in the background.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_recordBackgroundAudio"
+ android:description="@string/permdesc_recordBackgroundAudio"
+ android:permissionFlags="hardRestricted|installerExemptIgnored"
+ android:protectionLevel="dangerous" />
+
<!-- ====================================================================== -->
<!-- Permissions for activity recognition -->
<!-- ====================================================================== -->
@@ -1313,8 +1324,19 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera"
+ android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
android:protectionLevel="dangerous|instant" />
+ <!-- Required to be able to access the camera device in the background.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.BACKGROUND_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_backgroundCamera"
+ android:description="@string/permdesc_backgroundCamera"
+ android:permissionFlags="hardRestricted|installerExemptIgnored"
+ android:protectionLevel="dangerous" />
+
<!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
<p>Protection level: system|signature
@@ -2687,6 +2709,14 @@
<permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
android:protectionLevel="signature" />
+ <!-- Allows applications like settings to manage configuration associated with automatic time
+ and time zone detection.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
+ android:protectionLevel="signature|privileged" />
+
<!-- ==================================================== -->
<!-- Permissions related to changing status bar -->
<!-- ==================================================== -->
@@ -3546,6 +3576,13 @@
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
+ <!-- This permission is required by Media Resource Observer Service when
+ accessing its registerObserver Api.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+ android:protectionLevel="signature|privileged" />
<!-- Must be required by a {@link android.media.routing.MediaRouteService}
to ensure that only the system can interact with it.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1c71bae..96ebc12 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -344,6 +344,11 @@
the app is uninstalled.
-->
<flag name="immutablyRestricted" value="0x10" />
+ <!--
+ Modifier for permission restriction. This permission cannot
+ be exempted by the installer.
+ -->
+ <flag name="installerExemptIgnored" value="0x20" />
</attr>
<!-- Specified the name of a group that this permission is associated
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 25a9bbd..fc489b1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1166,7 +1166,12 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_recordAudio">record audio</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_recordAudio">This app can record audio using the microphone at any time.</string>
+ <string name="permdesc_recordAudio">This app can record audio using the microphone while the app is in use.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_recordBackgroundAudio">record audio in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_sim_communication">send commands to the SIM</string>
@@ -1181,7 +1186,12 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_camera">take pictures and videos</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string>
+ <string name="permdesc_camera">This app can take pictures and record videos using the camera while the app is in use.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_backgroundCamera">take pictures and videos in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_backgroundCamera">This app can take pictures and record videos using the camera at any time.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
<string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string>
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
new file mode 100644
index 0000000..01a25b2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TimeZoneCapabilitiesTest {
+
+ private static final UserHandle TEST_USER_HANDLE = UserHandle.of(12345);
+
+ @Test
+ public void testEquals() {
+ TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+ }
+
+ @Test
+ public void testTryApplyConfigChanges_permitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .build();
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertEquals(expected, capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+ }
+
+ @Test
+ public void testTryApplyConfigChanges_notPermitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .build();
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ assertNull(capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
similarity index 81%
rename from core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
rename to core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
index faf908d..3948eb8 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
@@ -23,16 +23,20 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
+@RunWith(AndroidJUnit4.class)
+@SmallTest
public class TimeZoneConfigurationTest {
- private static final int ARBITRARY_USER_ID = 9876;
-
@Test
public void testBuilder_copyConstructor() {
TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true);
TimeZoneConfiguration configuration1 = builder1.build();
@@ -45,27 +49,27 @@
@Test
public void testIntrospectionMethods() {
- TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+ TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder().build();
assertFalse(empty.isComplete());
- assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+ assertFalse(empty.hasIsAutoDetectionEnabled());
- TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true)
.build();
assertTrue(completeConfig.isComplete());
- assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+ assertTrue(completeConfig.hasIsGeoDetectionEnabled());
}
@Test
public void testBuilder_mergeProperties() {
- TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.build();
{
TimeZoneConfiguration mergedEmptyAnd1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.mergeProperties(configuration1)
.build();
assertEquals(configuration1, mergedEmptyAnd1);
@@ -73,7 +77,7 @@
{
TimeZoneConfiguration configuration2 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(false)
.build();
@@ -90,22 +94,14 @@
@Test
public void testEquals() {
TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
{
TimeZoneConfiguration one = builder1.build();
assertEquals(one, one);
}
- {
- TimeZoneConfiguration.Builder differentUserBuilder =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
- TimeZoneConfiguration one = builder1.build();
- TimeZoneConfiguration two = differentUserBuilder.build();
- assertNotEquals(one, two);
- }
-
TimeZoneConfiguration.Builder builder2 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
{
TimeZoneConfiguration one = builder1.build();
TimeZoneConfiguration two = builder2.build();
@@ -159,7 +155,7 @@
@Test
public void testParcelable() {
TimeZoneConfiguration.Builder builder =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
assertRoundTripParcelable(builder.build());
builder.setAutoDetectionEnabled(true);
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
deleted file mode 100644
index db127c6..0000000
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-
-public class TimeZoneCapabilitiesTest {
-
- private static final int ARBITRARY_USER_ID = 12345;
-
- @Test
- public void testEquals() {
- TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .setGeoDetectionEnabled(false)
- .build();
-
- TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration1)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration1)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfiguration(configuration2);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfiguration(configuration2);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
- }
-
- @Test
- public void testParcelable() {
- TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- assertRoundTripParcelable(builder.build());
-
- builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
-
- builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
-
- builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
- }
-
- @Test
- public void testApplyUpdate_permitted() {
- TimeZoneConfiguration oldConfiguration =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
- .setConfiguration(oldConfiguration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
- .build();
- assertEquals(oldConfiguration, capabilities.getConfiguration());
-
- TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .build();
-
- TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
- .setAutoDetectionEnabled(false)
- .build();
- assertEquals(expected, capabilities.applyUpdate(configChange));
- }
-
- @Test
- public void testApplyUpdate_notPermitted() {
- TimeZoneConfiguration oldConfiguration =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
- .setConfiguration(oldConfiguration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
- .build();
- assertEquals(oldConfiguration, capabilities.getConfiguration());
-
- TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .build();
-
- assertNull(capabilities.applyUpdate(configChange));
- }
-}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 10c2b09..977703d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -153,6 +153,7 @@
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
<assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
+ <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
<assign-permission name="android.permission.INTERNET" uid="media" />
@@ -222,6 +223,15 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
+ <split-permission name="android.permission.RECORD_AUDIO"
+ targetSdk="31">
+ <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
+ </split-permission>
+ <split-permission name="android.permission.CAMERA"
+ targetSdk="31">
+ <new-permission name="android.permission.BACKGROUND_CAMERA" />
+ </split-permission>
+
<!-- This is a list of all the libraries available for application
code to link against. -->
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2779a75..81da5c8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -435,6 +435,8 @@
<permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<!-- Permissions required for CTS test - AdbManagerTest -->
<permission name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permissions required for CTS test - TimeManagerTest -->
+ <permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9eaeed1..c53ea87 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -205,6 +205,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1844540996": {
+ "message": " Initial targets: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1838803135": {
"message": "Attempted to set windowing mode to a display that does not exist: %d",
"level": "WARN",
@@ -343,12 +349,24 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "-1587921395": {
+ "message": " Top targets: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1568331821": {
"message": "Enabling listeners",
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "-1567866547": {
+ "message": "Collecting in transition %d: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1554521902": {
"message": "showInsets(ime) was requested by different window: %s ",
"level": "WARN",
@@ -421,6 +439,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1452274694": {
+ "message": " CAN PROMOTE: promoting to parent %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1443029505": {
"message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
"level": "INFO",
@@ -475,6 +499,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "-1375751630": {
+ "message": " --- Start combine pass ---",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1364754753": {
"message": "Task vanished taskId=%d",
"level": "VERBOSE",
@@ -787,6 +817,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-855366859": {
+ "message": " merging children in from %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-853404763": {
"message": "\twallpaper=%s",
"level": "DEBUG",
@@ -871,6 +907,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-703543418": {
+ "message": " check sibling %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-694710814": {
"message": "Pausing rotation during drag",
"level": "DEBUG",
@@ -925,6 +967,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-622017164": {
+ "message": "Finish Transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"-618015844": {
"message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s",
"level": "INFO",
@@ -979,12 +1027,24 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "-532081937": {
+ "message": " Commit activity becoming invisible: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-519504830": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "-509601642": {
+ "message": " checking %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-507657818": {
"message": "Window %s is already added",
"level": "WARN",
@@ -1027,6 +1087,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-446752714": {
+ "message": " SKIP: sibling contains top target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-445944810": {
"message": "finish(%b): mCanceled=%b",
"level": "DEBUG",
@@ -1153,6 +1219,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-302335479": {
+ "message": " remove from topTargets %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-272719931": {
"message": "startLockTaskModeLocked: %s",
"level": "WARN",
@@ -1441,12 +1513,24 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
+ "182319432": {
+ "message": " remove from targets %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"184362060": {
"message": "screenshotTask(%d): mCanceled=%b",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "184610856": {
+ "message": "Start calculating TransitionInfo based on participants: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"186668272": {
"message": "Now changing app %s",
"level": "VERBOSE",
@@ -1525,6 +1609,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "259206414": {
+ "message": "Creating Transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"269576220": {
"message": "Resuming rotation after drag",
"level": "DEBUG",
@@ -1657,6 +1747,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "430260320": {
+ "message": " sibling is a top target with mode %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"435494046": {
"message": "Attempted to add window to a display for which the application does not have access: %d. Aborting.",
"level": "WARN",
@@ -1711,6 +1807,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "528150092": {
+ "message": " keep as target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"531242746": {
"message": " THUMBNAIL %s: CREATE",
"level": "INFO",
@@ -1903,6 +2005,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "744171317": {
+ "message": " SKIP: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"745391677": {
"message": " CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
"level": "INFO",
@@ -1921,6 +2029,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskPositioner.java"
},
+ "793568608": {
+ "message": " SKIP: sibling is visible but not part of transition",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"794570322": {
"message": "Now closing app %s",
"level": "VERBOSE",
@@ -1957,6 +2071,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "849147756": {
+ "message": "Finish collecting in transition %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"853091290": {
"message": "Moved stack=%s behind stack=%s",
"level": "DEBUG",
@@ -2041,6 +2161,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "996960396": {
+ "message": "Starting Transition %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1000601037": {
"message": "SyncSet{%x:%d} Set ready",
"level": "VERBOSE",
@@ -2101,6 +2227,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1115248873": {
+ "message": "Calling onTransitionReady: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1115417974": {
"message": "FORCED DISPLAY SIZE: %dx%d",
"level": "INFO",
@@ -2125,6 +2257,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1186730970": {
+ "message": " no common mode yet, so set it",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1208313423": {
"message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d",
"level": "WARN",
@@ -2293,6 +2431,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "1469310004": {
+ "message": " SKIP: common mode mismatch. was %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1495525537": {
"message": "createWallpaperAnimations()",
"level": "DEBUG",
@@ -2509,6 +2653,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1794249572": {
+ "message": "Requesting StartTransition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1822843721": {
"message": "Aborted starting %s: startingData=%s",
"level": "VERBOSE",
@@ -2826,6 +2976,9 @@
"WM_DEBUG_WINDOW_ORGANIZER": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WINDOW_TRANSITIONS": {
+ "tag": "WindowManager"
+ },
"WM_ERROR": {
"tag": "WindowManager"
},
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 964640b..28d7911 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3111,7 +3111,7 @@
@CriticalNative
private static native boolean nGetFillPath(long paintPtr, long src, long dst);
@CriticalNative
- private static native void nSetShader(long paintPtr, long shader);
+ private static native long nSetShader(long paintPtr, long shader);
@CriticalNative
private static native long nSetColorFilter(long paintPtr, long filter);
@CriticalNative
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 2a52ce9..e1a1795 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -591,14 +591,14 @@
*
* @param glyphId a glyph ID
* @param paint a paint object used for resolving glyph style
- * @param rect a nullable destination object. If null is passed, this function just return the
- * horizontal advance. If non-null is passed, this function fills bounding box
- * information to this object.
+ * @param outBoundingBox a nullable destination object. If null is passed, this function just
+ * return the horizontal advance. If non-null is passed, this function
+ * fills bounding box information to this object.
* @return the amount of horizontal advance in pixels
*/
public float getGlyphBounds(@IntRange(from = 0) int glyphId, @NonNull Paint paint,
- @Nullable RectF rect) {
- return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), rect);
+ @Nullable RectF outBoundingBox) {
+ return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), outBoundingBox);
}
/**
@@ -607,15 +607,15 @@
* Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
*
* @param paint a paint object used for retrieving font metrics.
- * @param metrics a nullable destination object. If null is passed, this function only retrieve
- * recommended interline spacing. If non-null is passed, this function fills to
- * font metrics to it.
+ * @param outMetrics a nullable destination object. If null is passed, this function only
+ * retrieve recommended interline spacing. If non-null is passed, this function
+ * fills to font metrics to it.
*
* @see Paint#getFontMetrics()
* @see Paint#getFontMetricsInt()
*/
- public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics metrics) {
- nGetFontMetrics(mNativePtr, paint.getNativeInstance(), metrics);
+ public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics outMetrics) {
+ nGetFontMetrics(mNativePtr, paint.getNativeInstance(), outMetrics);
}
/** @hide */
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index a13e98c..227eec2 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -1,12 +1,24 @@
{
"version": "1.0.0",
"messages": {
+ "-1534364071": {
+ "message": "onTransitionReady %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
+ },
"-1501874464": {
"message": "Fullscreen Task Appeared: #%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
},
+ "-1480787369": {
+ "message": "Transition requested: type=%d %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
+ },
"-1340279385": {
"message": "Remove listener=%s",
"level": "VERBOSE",
@@ -31,6 +43,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
+ "-191422040": {
+ "message": "Transition animations finished, notifying core %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
+ },
"157713005": {
"message": "Task info changed taskId=%d",
"level": "VERBOSE",
@@ -53,6 +71,9 @@
"groups": {
"WM_SHELL_TASK_ORG": {
"tag": "WindowManagerShell"
+ },
+ "WM_SHELL_TRANSITIONS": {
+ "tag": "WindowManagerShell"
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index b275331..9d6271b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -50,9 +50,13 @@
// properties in a bad state).
t.setPosition(leash, 0, 0);
t.setWindowCrop(leash, null);
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
+ // TODO(shell-transitions): Eventually set everything in transition so there's no
+ // SF Transaction here.
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d650a95..8f496d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -28,7 +28,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -59,16 +61,25 @@
// require us to report to both old and new listeners)
private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
- public ShellTaskOrganizer(SyncTransactionQueue syncQueue) {
+ // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks
+ private final Transitions mTransitions;
+
+ public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+ ShellExecutor mainExecutor, ShellExecutor animExecutor) {
super();
addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
+ mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+ ShellExecutor mainExecutor, ShellExecutor animExecutor) {
super(taskOrganizerController);
addListener(new FullscreenTaskListener(syncQueue), WINDOWING_MODE_FULLSCREEN);
+ mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
new file mode 100644
index 0000000..36e49d9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static android.window.TransitionInfo.TRANSIT_CLOSE;
+import static android.window.TransitionInfo.TRANSIT_HIDE;
+import static android.window.TransitionInfo.TRANSIT_OPEN;
+import static android.window.TransitionInfo.TRANSIT_SHOW;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+import android.window.TransitionInfo;
+import android.window.WindowOrganizer;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+
+/** Plays transition animations */
+public class Transitions extends ITransitionPlayer.Stub {
+ private static final String TAG = "ShellTransitions";
+
+ /** Set to {@code true} to enable shell transitions. */
+ public static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
+ private final WindowOrganizer mOrganizer;
+ private final TransactionPool mTransactionPool;
+ private final ShellExecutor mMainExecutor;
+ private final ShellExecutor mAnimExecutor;
+
+ /** Keeps track of currently tracked transitions and all the animations associated with each */
+ private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
+
+ Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ mOrganizer = organizer;
+ mTransactionPool = pool;
+ mMainExecutor = mainExecutor;
+ mAnimExecutor = animExecutor;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
+ boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mMainExecutor.execute(() -> {
+ mActiveTransitions.get(transition).remove(va);
+ onFinish(transition);
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mActiveTransitions.get(transition).add(va);
+ mAnimExecutor.execute(va::start);
+ }
+
+ private static boolean isOpeningType(@WindowManager.TransitionType int legacyType) {
+ // TODO(shell-transitions): consider providing and using z-order vs the global type for
+ // this determination.
+ return legacyType == WindowManager.TRANSIT_TASK_OPEN
+ || legacyType == WindowManager.TRANSIT_TASK_TO_FRONT
+ || legacyType == WindowManager.TRANSIT_TASK_OPEN_BEHIND
+ || legacyType == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
+ transitionToken, info);
+ // start task
+ mMainExecutor.execute(() -> {
+ if (!mActiveTransitions.containsKey(transitionToken)) {
+ Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
+ + " expecting one of " + mActiveTransitions.keySet());
+ }
+ if (mActiveTransitions.get(transitionToken) != null) {
+ throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+ + transitionToken);
+ }
+ mActiveTransitions.put(transitionToken, new ArrayList<>());
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final SurfaceControl leash = info.getChanges().get(i).getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
+ t.show(leash);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ if (isOpeningType(info.getType())) {
+ t.setAlpha(leash, 0.f);
+ startExampleAnimation(transitionToken, leash, true /* show */);
+ } else {
+ t.setAlpha(leash, 1.f);
+ }
+ } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
+ if (!isOpeningType(info.getType())) {
+ startExampleAnimation(transitionToken, leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish(transitionToken);
+ });
+ }
+
+ @MainThread
+ private void onFinish(IBinder transition) {
+ if (!mActiveTransitions.get(transition).isEmpty()) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Transition animations finished, notifying core %s", transition);
+ mActiveTransitions.remove(transition);
+ mOrganizer.finishTransition(transition, null, null);
+ }
+
+ @Override
+ public void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
+ type, transitionToken);
+ mMainExecutor.execute(() -> {
+ if (mActiveTransitions.containsKey(transitionToken)) {
+ throw new RuntimeException("Transition already started " + transitionToken);
+ }
+ IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
+ mActiveTransitions.put(transition, null);
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
new file mode 100644
index 0000000..96b9f86
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
+import android.annotation.NonNull;
+import android.os.HandlerThread;
+import android.util.Singleton;
+
+/**
+ * A singleton thread for Shell to run animations on.
+ */
+public class AnimationThread extends HandlerThread {
+ private ShellExecutor mExecutor;
+
+ private AnimationThread() {
+ super("wmshell.anim", THREAD_PRIORITY_DISPLAY);
+ }
+
+ /** Get the singleton instance of this thread */
+ public static AnimationThread instance() {
+ return sAnimationThreadSingleton.get();
+ }
+
+ /**
+ * @return a shared {@link ShellExecutor} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public ShellExecutor getExecutor() {
+ if (mExecutor == null) {
+ mExecutor = new HandlerExecutor(getThreadHandler());
+ }
+ return mExecutor;
+ }
+
+ private static final Singleton<AnimationThread> sAnimationThreadSingleton =
+ new Singleton<AnimationThread>() {
+ @Override
+ protected AnimationThread create() {
+ final AnimationThread animThread = new AnimationThread();
+ animThread.start();
+ return animThread;
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
new file mode 100644
index 0000000..cd75840
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+/** Executor implementation which is backed by a Handler. */
+public class HandlerExecutor implements ShellExecutor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void executeDelayed(@NonNull Runnable r, long delayMillis) {
+ if (!mHandler.postDelayed(r, delayMillis)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+
+ @Override
+ public void removeCallbacks(@NonNull Runnable r) {
+ mHandler.removeCallbacks(r);
+ }
+
+ @Override
+ public void execute(@NonNull Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
new file mode 100644
index 0000000..aafe240
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Super basic Executor interface that adds support for delayed execution and removing callbacks.
+ * Intended to wrap Handler while better-supporting testing.
+ */
+public interface ShellExecutor extends Executor {
+ /**
+ * See {@link android.os.Handler#postDelayed(Runnable, long)}.
+ */
+ void executeDelayed(Runnable r, long delayMillis);
+
+ /**
+ * See {@link android.os.Handler#removeCallbacks}.
+ */
+ void removeCallbacks(Runnable r);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index e3029e5..a0ce9da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -28,6 +28,8 @@
// with those in the framework ProtoLogGroup
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 823e0b7..1bc5cea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -33,7 +33,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +57,8 @@
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
+ private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+ private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -85,7 +89,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue);
+ mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
+ mTransactionPool, mTestExecutor, mTestExecutor);
}
@Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 975265e..155bb6b 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -464,14 +464,6 @@
"RenderNode.cpp",
"RenderProperties.cpp",
"RootRenderNode.cpp",
- "shader/Shader.cpp",
- "shader/BitmapShader.cpp",
- "shader/BlurShader.cpp",
- "shader/ComposeShader.cpp",
- "shader/LinearGradientShader.cpp",
- "shader/RadialGradientShader.cpp",
- "shader/RuntimeShader.cpp",
- "shader/SweepGradientShader.cpp",
"SkiaCanvas.cpp",
"VectorDrawable.cpp",
],
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index a690840..1dbce58 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -42,8 +42,6 @@
#include <SkTextBlob.h>
#include <SkVertices.h>
-#include <shader/BitmapShader.h>
-
#include <memory>
#include <optional>
#include <utility>
@@ -51,7 +49,6 @@
namespace android {
using uirenderer::PaintUtils;
-using uirenderer::BitmapShader;
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
return new SkiaCanvas(bitmap);
@@ -684,9 +681,7 @@
if (paint) {
pnt = *paint;
}
-
- pnt.setShader(sk_ref_sp(new BitmapShader(
- bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr)));
+ pnt.setShader(bitmap.makeImage()->makeShader());
auto v = builder.detach();
apply_looper(&pnt, [&](const SkPaint& p) {
mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 0f566e4..cd908354 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -23,6 +23,7 @@
#include "PathParser.h"
#include "SkColorFilter.h"
#include "SkImageInfo.h"
+#include "SkShader.h"
#include "hwui/Paint.h"
#ifdef __ANDROID__
@@ -158,10 +159,10 @@
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
- Paint paint;
+ SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient())));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -178,7 +179,7 @@
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient())));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index d4086f1..ac7d41e 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,8 +31,8 @@
#include <SkPath.h>
#include <SkPathMeasure.h>
#include <SkRect.h>
+#include <SkShader.h>
#include <SkSurface.h>
-#include <shader/Shader.h>
#include <cutils/compiler.h>
#include <stddef.h>
@@ -227,20 +227,20 @@
strokeGradient = prop.strokeGradient;
onPropertyChanged();
}
- void setFillGradient(Shader* gradient) {
+ void setFillGradient(SkShader* gradient) {
if (fillGradient.get() != gradient) {
fillGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- void setStrokeGradient(Shader* gradient) {
+ void setStrokeGradient(SkShader* gradient) {
if (strokeGradient.get() != gradient) {
strokeGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- Shader* getFillGradient() const { return fillGradient.get(); }
- Shader* getStrokeGradient() const { return strokeGradient.get(); }
+ SkShader* getFillGradient() const { return fillGradient.get(); }
+ SkShader* getStrokeGradient() const { return strokeGradient.get(); }
float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
void setStrokeWidth(float strokeWidth) {
VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -320,8 +320,8 @@
count,
};
PrimitiveFields mPrimitiveFields;
- sk_sp<Shader> fillGradient;
- sk_sp<Shader> strokeGradient;
+ sk_sp<SkShader> fillGradient;
+ sk_sp<SkShader> strokeGradient;
};
// Called from UI thread
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2001b56..8df2770b 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -73,7 +73,7 @@
static void simplifyPaint(int color, Paint* paint) {
paint->setColor(color);
- paint->setShader((sk_sp<uirenderer::Shader>)nullptr);
+ paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4e2016a..e75e9e7 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,8 +30,6 @@
#include <minikin/FamilyVariant.h>
#include <minikin/Hyphenator.h>
-#include <shader/Shader.h>
-
namespace android {
class Paint : public SkPaint {
@@ -151,13 +149,8 @@
// The only respected flags are : [ antialias, dither, filterBitmap ]
static uint32_t GetSkPaintJavaFlags(const SkPaint&);
static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
-
- void setShader(sk_sp<uirenderer::Shader> shader);
private:
-
- using SkPaint::setShader;
-
SkFont mFont;
sk_sp<SkDrawLooper> mLooper;
@@ -176,7 +169,6 @@
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
- sk_sp<uirenderer::Shader> mShader;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 21f60fd..fa2674f 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,8 +24,7 @@
, mWordSpacing(0)
, mFontFeatureSettings()
, mMinikinLocaleListId(0)
- , mFamilyVariant(minikin::FamilyVariant::DEFAULT)
- , mShader(nullptr) {
+ , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
// SkPaint::antialiasing defaults to false, but
// SkFont::edging defaults to kAntiAlias. To keep them
// insync, we manually set the font to kAilas.
@@ -46,8 +45,7 @@
, mAlign(paint.mAlign)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern)
- , mShader(paint.mShader){}
+ , mDevKern(paint.mDevKern) {}
Paint::~Paint() {}
@@ -67,30 +65,9 @@
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
- mShader = other.mShader;
return *this;
}
-void Paint::setShader(sk_sp<uirenderer::Shader> shader) {
- if (shader) {
- // If there is an SkShader compatible shader, apply it
- sk_sp<SkShader> skShader = shader->asSkShader();
- if (skShader.get()) {
- SkPaint::setShader(skShader);
- SkPaint::setImageFilter(nullptr);
- } else {
- // ... otherwise the specified shader can only be represented as an ImageFilter
- SkPaint::setShader(nullptr);
- SkPaint::setImageFilter(shader->asSkImageFilter());
- }
- } else {
- // No shader is provided at all, clear out both the SkShader and SkImageFilter slots
- SkPaint::setShader(nullptr);
- SkPaint::setImageFilter(nullptr);
- }
- mShader = shader;
-}
-
bool operator==(const Paint& a, const Paint& b) {
return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
a.mFont == b.mFont &&
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index f6c8496..3c86b28 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -47,7 +47,6 @@
#include <minikin/LocaleList.h>
#include <minikin/Measurement.h>
#include <minikin/MinikinPaint.h>
-#include <shader/Shader.h>
#include <unicode/utf16.h>
#include <cassert>
@@ -55,8 +54,6 @@
#include <memory>
#include <vector>
-using namespace android::uirenderer;
-
namespace android {
static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
@@ -745,10 +742,11 @@
return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
}
- static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
- auto* paint = reinterpret_cast<Paint*>(objHandle);
- auto* shader = reinterpret_cast<Shader*>(shaderHandle);
- paint->setShader(sk_ref_sp(shader));
+ static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
+ obj->setShader(sk_ref_sp(shader));
+ return reinterpret_cast<jlong>(obj->getShader());
}
static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
@@ -1059,7 +1057,7 @@
{"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
{"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
{"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"nSetShader","(JJ)V", (void*) PaintGlue::setShader},
+ {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
{"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
{"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
{"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 0a194f9..e76aace 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -5,14 +5,6 @@
#include "SkShader.h"
#include "SkBlendMode.h"
#include "include/effects/SkRuntimeEffect.h"
-#include "shader/Shader.h"
-#include "shader/BitmapShader.h"
-#include "shader/BlurShader.h"
-#include "shader/ComposeShader.h"
-#include "shader/LinearGradientShader.h"
-#include "shader/RadialGradientShader.h"
-#include "shader/RuntimeShader.h"
-#include "shader/SweepGradientShader.h"
#include <vector>
@@ -58,7 +50,7 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static void Shader_safeUnref(Shader* shader) {
+static void Shader_safeUnref(SkShader* shader) {
SkSafeUnref(shader);
}
@@ -82,15 +74,15 @@
SkBitmap bitmap;
image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
}
+ sk_sp<SkShader> shader = image->makeShader(
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY);
+ ThrowIAE_IfNull(env, shader.get());
- auto* shader = new BitmapShader(
- image,
- static_cast<SkTileMode>(tileModeX),
- static_cast<SkTileMode>(tileModeY),
- matrix
- );
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -126,18 +118,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* shader = new LinearGradientShader(
- pts,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- static_cast<SkTileMode>(tileMode),
- sGradientShaderFlags,
- matrix
- );
+ sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
+ ThrowIAE_IfNull(env, shader);
- return reinterpret_cast<jlong>(shader);
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
+
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -157,20 +148,17 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr);
+ ThrowIAE_IfNull(env, shader);
- auto* shader = new RadialGradientShader(
- center,
- radius,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- static_cast<SkTileMode>(tileMode),
- sGradientShaderFlags,
- matrix
- );
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////
@@ -186,75 +174,54 @@
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ sGradientShaderFlags, nullptr);
+ ThrowIAE_IfNull(env, shader);
- auto* shader = new SweepGradientShader(
- x,
- y,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- sGradientShaderFlags,
- matrix
- );
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle);
- auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle);
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
+ SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
+ SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
+ sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
+ sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
- auto mode = static_cast<SkBlendMode>(xfermodeHandle);
+ SkShader* shader;
- auto* composeShader = new ComposeShader(
- *shaderA,
- *shaderB,
- mode,
- matrix
- );
-
- return reinterpret_cast<jlong>(composeShader);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
-static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX,
- jfloat sigmaY, jlong shaderHandle, jint edgeTreatment) {
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* inputShader = reinterpret_cast<Shader*>(shaderHandle);
-
- auto* blurShader = new BlurShader(
- sigmaX,
- sigmaY,
- inputShader,
- static_cast<SkTileMode>(edgeTreatment),
- matrix
- );
- return reinterpret_cast<jlong>(blurShader);
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) {
- auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
+ SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
AutoJavaByteArray arInputs(env, inputs);
- auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkData> fData;
+ fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = effect->makeShader(fData, nullptr, 0, matrix, isOpaque == JNI_TRUE);
+ ThrowIAE_IfNull(env, shader);
- auto* shader = new RuntimeShader(
- *effect,
- std::move(data),
- isOpaque == JNI_TRUE,
- matrix
- );
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -272,8 +239,12 @@
///////////////////////////////////////////////////////////////////////////////////////////////
+static void Effect_safeUnref(SkRuntimeEffect* effect) {
+ SkSafeUnref(effect);
+}
+
static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -291,10 +262,6 @@
{ "nativeCreate", "(JJII)J", (void*)BitmapShader_constructor },
};
-static const JNINativeMethod gBlurShaderMethods[] = {
- { "nativeCreate", "(JFFJI)J", (void*)BlurShader_create }
-};
-
static const JNINativeMethod gLinearGradientMethods[] = {
{ "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create },
};
@@ -326,8 +293,6 @@
NELEM(gShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
NELEM(gBitmapShaderMethods));
- android::RegisterMethodsOrDie(env, "android/graphics/BlurShader", gBlurShaderMethods,
- NELEM(gBlurShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods,
NELEM(gLinearGradientMethods));
android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods,
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index a1adcb3..9cffceb 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -143,13 +143,13 @@
static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) {
VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
- auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr);
+ SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr);
path->mutateStagingProperties()->setFillGradient(fillShader);
}
static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) {
VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
- auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr);
+ SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr);
path->mutateStagingProperties()->setStrokeGradient(strokeShader);
}
diff --git a/libs/hwui/shader/BitmapShader.cpp b/libs/hwui/shader/BitmapShader.cpp
deleted file mode 100644
index fe653e8..0000000
--- a/libs/hwui/shader/BitmapShader.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "BitmapShader.h"
-
-#include "SkImagePriv.h"
-
-namespace android::uirenderer {
-BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
- const SkTileMode tileModeY, const SkMatrix* matrix)
- : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {}
-
-sk_sp<SkShader> BitmapShader::makeSkShader() {
- return skShader;
-}
-
-BitmapShader::~BitmapShader() {}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/BitmapShader.h b/libs/hwui/shader/BitmapShader.h
deleted file mode 100644
index ed6a6e6..0000000
--- a/libs/hwui/shader/BitmapShader.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter
- */
-class BitmapShader : public Shader {
-public:
- BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
- const SkTileMode tileModeY, const SkMatrix* matrix);
- ~BitmapShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp
deleted file mode 100644
index 3765489..0000000
--- a/libs/hwui/shader/ComposeShader.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ComposeShader.h"
-
-#include "SkImageFilters.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
- const SkMatrix* matrix)
- : Shader(matrix) {
- // If both Shaders can be represented as SkShaders then use those, if not
- // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter
- sk_sp<SkShader> skShaderA = shaderA.asSkShader();
- sk_sp<SkShader> skShaderB = shaderB.asSkShader();
- if (skShaderA.get() && skShaderB.get()) {
- skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB);
- skImageFilter = nullptr;
- } else {
- sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter();
- sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter();
- skShader = nullptr;
- skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB);
- }
-}
-
-sk_sp<SkShader> ComposeShader::makeSkShader() {
- return skShader;
-}
-
-sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() {
- return skImageFilter;
-}
-
-ComposeShader::~ComposeShader() {}
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h
deleted file mode 100644
index a246b52..0000000
--- a/libs/hwui/shader/ComposeShader.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that can composite 2 Shaders together with the specified blend mode.
- * This implementation can appropriately convert the composed result to either an SkShader or
- * SkImageFilter depending on the inputs
- */
-class ComposeShader : public Shader {
-public:
- ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
- const SkMatrix* matrix);
- ~ComposeShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
- sk_sp<SkImageFilter> makeSkImageFilter() override;
-
-private:
- sk_sp<SkShader> skShader;
- sk_sp<SkImageFilter> skImageFilter;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp
deleted file mode 100644
index 868fa44..0000000
--- a/libs/hwui/shader/LinearGradientShader.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "LinearGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-LinearGradientShader::LinearGradientShader(const SkPoint pts[2],
- const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(),
- tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> LinearGradientShader::makeSkShader() {
- return skShader;
-}
-
-LinearGradientShader::~LinearGradientShader() {}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h
deleted file mode 100644
index 596f4e0..0000000
--- a/libs/hwui/shader/LinearGradientShader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp of colors to either as either SkShader or
- * SkImageFilter
- */
-class LinearGradientShader : public Shader {
-public:
- LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix);
- ~LinearGradientShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp
deleted file mode 100644
index 21ff56f..0000000
--- a/libs/hwui/shader/RadialGradientShader.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "RadialGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius,
- const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos,
- colors.size(), tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> RadialGradientShader::makeSkShader() {
- return skShader;
-}
-
-RadialGradientShader::~RadialGradientShader() {}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h
deleted file mode 100644
index 9a2ff13..0000000
--- a/libs/hwui/shader/RadialGradientShader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp from the center outward to either as either
- * a SkShader or SkImageFilter
- */
-class RadialGradientShader : public Shader {
-public:
- RadialGradientShader(const SkPoint& center, const float radius,
- const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace,
- const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix);
- ~RadialGradientShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp
deleted file mode 100644
index dd0b698..0000000
--- a/libs/hwui/shader/RuntimeShader.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "RuntimeShader.h"
-
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
-
-RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
- const SkMatrix* matrix)
- : Shader(nullptr)
- , // Explicitly passing null as RuntimeShader is created with the
- // matrix directly
- skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {}
-
-sk_sp<SkShader> RuntimeShader::makeSkShader() {
- return skShader;
-}
-
-RuntimeShader::~RuntimeShader() {}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.h b/libs/hwui/shader/RuntimeShader.h
deleted file mode 100644
index 7fe0b02..0000000
--- a/libs/hwui/shader/RuntimeShader.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
-
-/**
- * RuntimeShader implementation that can map to either a SkShader or SkImageFilter
- */
-class RuntimeShader : public Shader {
-public:
- RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
- const SkMatrix* matrix);
- ~RuntimeShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp
deleted file mode 100644
index 45123dd..0000000
--- a/libs/hwui/shader/Shader.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Shader.h"
-
-#include "SkImageFilters.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-namespace android::uirenderer {
-
-Shader::Shader(const SkMatrix* matrix)
- : localMatrix(matrix ? *matrix : SkMatrix::I())
- , skShader(nullptr)
- , skImageFilter(nullptr) {}
-
-Shader::~Shader() {}
-
-sk_sp<SkShader> Shader::asSkShader() {
- // If we already have created a shader with these parameters just return the existing
- // shader we have already created
- if (!this->skShader.get()) {
- this->skShader = makeSkShader();
- if (this->skShader.get()) {
- if (!localMatrix.isIdentity()) {
- this->skShader = this->skShader->makeWithLocalMatrix(localMatrix);
- }
- }
- }
- return this->skShader;
-}
-
-/**
- * By default return null as we cannot convert all visual effects to SkShader instances
- */
-sk_sp<SkShader> Shader::makeSkShader() {
- return nullptr;
-}
-
-sk_sp<SkImageFilter> Shader::asSkImageFilter() {
- // If we already have created an ImageFilter with these parameters just return the existing
- // ImageFilter we have already created
- if (!this->skImageFilter.get()) {
- // Attempt to create an SkImageFilter from the current Shader implementation
- this->skImageFilter = makeSkImageFilter();
- if (this->skImageFilter) {
- if (!localMatrix.isIdentity()) {
- // If we have created an SkImageFilter and we have a transformation, wrap
- // the created SkImageFilter to apply the given matrix
- this->skImageFilter = SkImageFilters::MatrixTransform(
- localMatrix, kMedium_SkFilterQuality, this->skImageFilter);
- }
- } else {
- // Otherwise if no SkImageFilter implementation is provided, create one from
- // the result of asSkShader. Note the matrix is already applied to the shader in
- // this case so just convert it to an SkImageFilter using SkImageFilters::Paint
- SkPaint paint;
- paint.setShader(asSkShader());
- sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint);
- this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn,
- std::move(paintFilter));
- }
- }
- return this->skImageFilter;
-}
-
-/**
- * By default return null for subclasses to implement. If there is not a direct SkImageFilter
- * conversion
- */
-sk_sp<SkImageFilter> Shader::makeSkImageFilter() {
- return nullptr;
-}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h
deleted file mode 100644
index 6403e11..0000000
--- a/libs/hwui/shader/Shader.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "SkImageFilter.h"
-#include "SkShader.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-class SkMatrix;
-
-namespace android::uirenderer {
-
-/**
- * Shader class that can optionally wrap an SkShader or SkImageFilter depending
- * on the implementation
- */
-class Shader: public SkRefCnt {
-public:
- /**
- * Creates a Shader instance with an optional transformation matrix. The transformation matrix
- * is copied internally and ownership is unchanged. It is the responsibility of the caller to
- * deallocate it appropriately.
- * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter
- */
- Shader(const SkMatrix* matrix);
- virtual ~Shader();
-
- /**
- * Create an SkShader from the current Shader instance or return a previously
- * created instance. This can be null if no SkShader could be created from this
- * Shader instance.
- */
- sk_sp<SkShader> asSkShader();
-
- /**
- * Create an SkImageFilter from the current Shader instance or return a previously
- * created instance. Unlike asSkShader, this method cannot return null.
- */
- sk_sp<SkImageFilter> asSkImageFilter();
-
-protected:
- /**
- * Create a new SkShader instance based on this Shader instance
- */
- virtual sk_sp<SkShader> makeSkShader();
-
- /**
- * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter
- * can be created then return nullptr
- */
- virtual sk_sp<SkImageFilter> makeSkImageFilter();
-
-private:
- /**
- * Optional matrix transform
- */
- const SkMatrix localMatrix;
-
- /**
- * Cached SkShader instance to be returned on subsequent queries
- */
- sk_sp<SkShader> skShader;
-
- /**
- * Cached SkImageFilter instance to be returned on subsequent queries
- */
- sk_sp<SkImageFilter> skImageFilter;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp
deleted file mode 100644
index 3b1f37f..0000000
--- a/libs/hwui/shader/SweepGradientShader.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SweepGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-#include "SkImageFilters.h"
-
-namespace android::uirenderer {
-
-SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
- const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
- const uint32_t shaderFlags, const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(),
- shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> SweepGradientShader::makeSkShader() {
- return skShader;
-}
-
-SweepGradientShader::~SweepGradientShader() {}
-} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h
deleted file mode 100644
index dad3ef0..0000000
--- a/libs/hwui/shader/SweepGradientShader.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp clockwise such that the start and end colors
- * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter
- */
-class SweepGradientShader : public Shader {
-public:
- SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
- const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
- const uint32_t shaderFlags, const SkMatrix* matrix);
- virtual ~SweepGradientShader() override;
-
-protected:
- virtual sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index e2c1651..c4067af 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -18,7 +18,6 @@
#include "hwui/Paint.h"
#include "TestSceneBase.h"
#include "tests/common/BitmapAllocationTestUtils.h"
-#include <shader/BitmapShader.h>
#include "utils/Color.h"
class BitmapShaders;
@@ -46,24 +45,15 @@
});
Paint paint;
- sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
- hwuiBitmap->makeImage(),
- SkTileMode::kRepeat,
- SkTileMode::kRepeat,
- nullptr
- );
-
sk_sp<SkImage> image = hwuiBitmap->makeImage();
- paint.setShader(std::move(bitmapShader));
+ sk_sp<SkShader> repeatShader =
+ image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
+ paint.setShader(std::move(repeatShader));
canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
- sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>(
- image,
- SkTileMode::kMirror,
- SkTileMode::kMirror,
- nullptr
- );
- paint.setShader(std::move(mirrorBitmapShader));
+ sk_sp<SkShader> mirrorShader =
+ image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror);
+ paint.setShader(std::move(mirrorShader));
canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
}
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index d37bc3c..5886ea3 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -20,10 +20,6 @@
#include <SkGradientShader.h>
#include <SkImagePriv.h>
#include <ui/PixelFormat.h>
-#include <shader/BitmapShader.h>
-#include <shader/LinearGradientShader.h>
-#include <shader/RadialGradientShader.h>
-#include <shader/ComposeShader.h>
class HwBitmapInCompositeShader;
@@ -54,41 +50,20 @@
pixels[4000 + 4 * i + 3] = 255;
}
buffer->unlock();
-
- sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
- Bitmap::createFrom(
- buffer->toAHardwareBuffer(),
- SkColorSpace::MakeSRGB()
- )->makeImage(),
- SkTileMode::kClamp,
- SkTileMode::kClamp,
- nullptr
- );
+ sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(),
+ SkColorSpace::MakeSRGB()));
+ sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
SkPoint center;
center.set(50, 50);
+ SkColor colors[2];
+ colors[0] = Color::Black;
+ colors[1] = Color::White;
+ sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(
+ center, 50, colors, nullptr, 2, SkTileMode::kRepeat);
- std::vector<SkColor4f> vColors(2);
- vColors[0] = SkColors::kBlack;
- vColors[1] = SkColors::kWhite;
-
- sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>(
- center,
- 50,
- vColors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kRepeat,
- 0,
- nullptr
- );
-
- sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>(
- *bitmapShader.get(),
- *radialShader.get(),
- SkBlendMode::kDstATop,
- nullptr
- );
+ sk_sp<SkShader> compositeShader(
+ SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader));
Paint paint;
paint.setShader(std::move(compositeShader));
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 76e39de..a9449b6 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,8 +17,7 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
#include "hwui/Paint.h"
-#include "SkColor.h"
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
class ListOfFadedTextAnimation;
@@ -43,26 +42,15 @@
pts[0].set(0, 0);
pts[1].set(0, 1);
+ SkColor colors[2] = {Color::Black, Color::Transparent};
+ sk_sp<SkShader> s(
+ SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp));
+
SkMatrix matrix;
matrix.setScale(1, length);
matrix.postRotate(-90);
-
- std::vector<SkColor4f> vColors(2);
- vColors[0] = SkColors::kBlack;
- vColors[1] = SkColors::kTransparent;
-
- sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>(
- pts,
- vColors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kClamp,
- 0,
- &matrix
- );
-
Paint fadingPaint;
- fadingPaint.setShader(linearGradientShader);
+ fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
fadingPaint.setBlendMode(SkBlendMode::kDstOut);
canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index bdc157f..a0bc5aa 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -17,7 +17,7 @@
#include "TestSceneBase.h"
#include <SkColorMatrixFilter.h>
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
class SimpleColorMatrixAnimation;
@@ -65,12 +65,9 @@
// enough renderer might apply it directly to the paint color)
float pos[] = {0, 1};
SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)};
- std::vector<SkColor4f> colors(2);
- colors[0] = SkColor4f::FromColor(Color::DeepPurple_500);
- colors[1] = SkColor4f::FromColor(Color::DeepOrange_500);
- paint.setShader(sk_make_sp<LinearGradientShader>(
- pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp,
- 0, nullptr));
+ SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500};
+ paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
+ SkTileMode::kClamp));
// overdraw several times to emphasize shader cost
for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 9a15c9d..57a260c 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -17,7 +17,6 @@
#include "TestSceneBase.h"
#include <SkGradientShader.h>
-#include <shader/LinearGradientShader.h>
class SimpleGradientAnimation;
@@ -56,24 +55,9 @@
// overdraw several times to emphasize shader cost
for (int i = 0; i < 10; i++) {
// use i%2 start position to pick 2 color combo with black in it
- std::vector<SkColor4f> vColors(2);
- vColors[0] = ((i % 2) == 0) ?
- SkColor4f::FromColor(Color::Transparent) :
- SkColor4f::FromColor(Color::Black);
- vColors[1] = (((i + 1) % 2) == 0) ?
- SkColor4f::FromColor(Color::Black) :
- SkColor4f::FromColor(Color::Cyan_500);
-
- sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>(
- pts,
- vColors,
- SkColorSpace::MakeSRGB(),
- pos,
- SkTileMode::kClamp,
- 0,
- nullptr
- );
- paint.setShader(gradient);
+ SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500};
+ paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
+ SkTileMode::kClamp));
canvas.drawRect(i, i, width, height, paint);
}
});
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 5e56b26..6d4c574 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -17,14 +17,9 @@
#include <gtest/gtest.h>
#include "PathParser.h"
-#include "GraphicsJNI.h"
-#include "SkGradientShader.h"
-#include "SkShader.h"
#include "VectorDrawable.h"
#include "utils/MathUtils.h"
#include "utils/VectorDrawableUtils.h"
-#include <shader/Shader.h>
-#include <shader/LinearGradientShader.h>
#include <functional>
@@ -400,21 +395,7 @@
bitmap.allocN32Pixels(5, 5, false);
SkCanvas canvas(bitmap);
- SkPoint pts[2];
- pts[0].set(0, 0);
- pts[1].set(0, 0);
-
- std::vector<SkColor4f> colors(2);
- colors[0] = SkColors::kBlack;
- colors[1] = SkColors::kBlack;
-
- sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts,
- colors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kClamp,
- SkGradientShader::kInterpolateColorsInPremul_Flag,
- nullptr));
+ sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
// Initial ref count is 1
EXPECT_TRUE(shader->unique());
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9aa0c87..46bd221 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,8 @@
package android.location;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -586,6 +588,11 @@
}
/** @hide */
+ public long getElapsedRealtimeMillis() {
+ return NANOSECONDS.toMillis(getElapsedRealtimeNanos());
+ }
+
+ /** @hide */
public long getElapsedRealtimeAgeNanos(long referenceRealtimeNs) {
return referenceRealtimeNs - mElapsedRealtimeNanos;
}
@@ -595,6 +602,11 @@
return getElapsedRealtimeAgeNanos(SystemClock.elapsedRealtimeNanos());
}
+ /** @hide */
+ public long getElapsedRealtimeAgeMillis() {
+ return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+ }
+
/**
* Set the time of this fix, in elapsed real-time since system boot.
*
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 6e597b2..ff00409 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -89,6 +89,16 @@
public class LocationManager {
/**
+ * For apps targeting Android S and above, location clients may receive historical locations
+ * (from before the present time) under some circumstances.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long DELIVER_HISTORICAL_LOCATIONS = 73144566L;
+
+ /**
* For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
* security exceptions.
*
@@ -1256,13 +1266,15 @@
* arguments. The same listener may be used across multiple providers with different requests
* for each provider.
*
- * <p>It may take a while to receive the first location update. If an immediate location is
- * required, applications may use the {@link #getLastKnownLocation(String)} method.
+ * <p>It may take some time to receive the first location update depending on the conditions the
+ * device finds itself in. In order to take advantage of cached locations, application may
+ * consider using {@link #getLastKnownLocation(String)} or {@link #getCurrentLocation(String,
+ * LocationRequest, CancellationSignal, Executor, Consumer)} instead.
*
* <p>See {@link LocationRequest} documentation for an explanation of various request parameters
* and how they can affect the received locations.
*
- * <p> If your application wants to passively observe location updates from any provider, then
+ * <p>If your application wants to passively observe location updates from all providers, then
* use the {@link #PASSIVE_PROVIDER}. This provider does not turn on or modify active location
* providers, so you do not need to be as careful about minimum time and minimum distance
* parameters. However, if your application performs heavy work on a location update (such as
@@ -1271,13 +1283,20 @@
*
* <p>In case the provider you have selected is disabled, location updates will cease, and a
* provider availability update will be sent. As soon as the provider is enabled again, another
- * provider availability update will be sent and location updates will immediately resume.
+ * provider availability update will be sent and location updates will resume.
*
- * <p> When location callbacks are invoked, the system will hold a wakelock on your
+ * <p>When location callbacks are invoked, the system will hold a wakelock on your
* application's behalf for some period of time, but not indefinitely. If your application
* requires a long running wakelock within the location callback, you should acquire it
* yourself.
*
+ * <p>Spamming location requests is a drain on system resources, and the system has preventative
+ * measures in place to ensure that this behavior will never result in more locations than could
+ * be achieved with a single location request with an equivalent interval that is left in place
+ * the whole time. As part of this amelioration, applications that target Android S and above
+ * may receive cached or historical locations through their listener. These locations will never
+ * be older than the interval of the location request.
+ *
* <p>To unregister for location updates, use {@link #removeUpdates(LocationListener)}.
*
* @param provider a provider listed by {@link #getAllProviders()}
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 0521b10..c57794f 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -27,6 +27,8 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -45,6 +47,17 @@
public final class LocationRequest implements Parcelable {
/**
+ * For apps targeting Android S and above, all LocationRequest objects marked as low power will
+ * throw exceptions if the caller does not have the LOCATION_HARDWARE permission, instead of
+ * silently dropping the low power part of the request.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long LOW_POWER_EXCEPTIONS = 168936375L;
+
+ /**
* Represents a passive only request. Such a request will not trigger any active locations or
* power usage itself, but may receive locations generated in response to other requests.
*
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 24dacc4..3af2e17 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -472,7 +472,8 @@
/**
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(publicAlternatives = "Check equality of {@link #getSessionToken() tokens}"
+ + "instead.")
public boolean controlsSameSession(MediaController other) {
if (other == null) return false;
return mSessionBinder.asBinder() == other.getSessionBinder().asBinder();
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 4d0a01c..3cc1e42 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -21,6 +21,7 @@
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@
field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -12235,6 +12237,7 @@
field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+ field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12344,6 +12347,7 @@
field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+ field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
field public static final int PROTECTION_DANGEROUS = 1; // 0x1
field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 684968c..7a64d28 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -2097,6 +2097,7 @@
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -9580,16 +9581,12 @@
method public int getTimeoutSeconds();
method public boolean isEnabled();
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
- field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
- field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
- field public static final int ERROR_UNKNOWN = 1; // 0x1
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
field public static final int REASON_BUSY = 1; // 0x1
field public static final int REASON_NOT_REACHABLE = 3; // 0x3
field public static final int REASON_NO_REPLY = 2; // 0x2
field public static final int REASON_UNCONDITIONAL = 0; // 0x0
- field public static final int SUCCESS = 0; // 0x0
}
public final class CallQuality implements android.os.Parcelable {
@@ -10332,7 +10329,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -10431,6 +10428,10 @@
public static interface TelephonyManager.CallForwardingInfoCallback {
method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
index 1b12eb4..51d2b9d 100644
--- a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
+++ b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
@@ -14,9 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/system_bar_background_pill_color"/>
- <corners android:radius="30dp"/>
-</shape>
\ No newline at end of file
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/system_bar_background_pill_color"/>
+ <corners android:radius="30dp"/>
+ </shape>
+ </aapt:attr>
+ </item>
+ <item>
+ <aapt:attr name="android:drawable">
+ <ripple android:color="?android:attr/colorControlHighlight"/>
+ </aapt:attr>
+ </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
index f242db0..f5de2fd 100644
--- a/packages/CarSystemUI/res/values/styles.xml
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -49,5 +49,6 @@
<item name="android:padding">@dimen/system_bar_button_padding</item>
<item name="android:gravity">center</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="unselectedAlpha">0.56</item>
</style>
</resources>
\ No newline at end of file
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index e05bd3c..d3aa977 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -21,8 +21,6 @@
import static androidx.test.ext.truth.location.LocationSubject.assertThat;
-import static com.google.common.truth.Truth.assertThat;
-
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f5f58ef..a927997 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -326,6 +326,9 @@
<!-- Permission needed for CTS test - DisplayTest -->
<uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
+ <!-- Permission needed for CTS test - TimeManagerTest -->
+ <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4ba757f..2427d360 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2829,4 +2829,9 @@
<string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
<!-- Title for pairing item [CHAR LIMIT=60] -->
<string name="media_output_dialog_pairing_new">Pair new device</string>
+
+ <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
+ <string name="build_number_clip_data_label">Build number</string>
+ <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]-->
+ <string name="build_number_copy_toast">Build number copied to clipboard.</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f4c865e..4657b06 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -75,8 +75,10 @@
Key.HAS_SEEN_BUBBLES_EDUCATION,
Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
- Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
+ Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
+ Key.HAS_SEEN_PRIORITY_ONBOARDING
})
+ // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 1ebe648..c4a305e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -86,6 +86,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -121,6 +122,7 @@
private final TunerService mTunerService;
private DisplayManager.DisplayListener mDisplayListener;
private CameraAvailabilityListener mCameraListener;
+ private final UserTracker mUserTracker;
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -198,11 +200,13 @@
public ScreenDecorations(Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
- TunerService tunerService) {
+ TunerService tunerService,
+ UserTracker userTracker) {
super(context);
mMainHandler = handler;
mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
+ mUserTracker = userTracker;
}
@Override
@@ -306,7 +310,8 @@
// Watch color inversion and invert the overlay as needed.
if (mColorInversionSetting == null) {
mColorInversionSetting = new SecureSetting(mContext, mHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ mUserTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateColorInversion(value);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 658f46e..2f0fd99 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.os.IBinder
@@ -30,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.util.concurrent.atomic.AtomicBoolean
@@ -40,7 +40,8 @@
open class ControlsBindingControllerImpl @Inject constructor(
private val context: Context,
@Background private val backgroundExecutor: DelayableExecutor,
- private val lazyController: Lazy<ControlsController>
+ private val lazyController: Lazy<ControlsController>,
+ userTracker: UserTracker
) : ControlsBindingController {
companion object {
@@ -56,7 +57,7 @@
}
}
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId: Int
get() = currentUser.identifier
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 495872f..d3d24be 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.app.PendingIntent
import android.app.backup.BackupManager
import android.content.BroadcastReceiver
@@ -46,6 +45,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.globalactions.GlobalActionsDialog
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -56,14 +56,15 @@
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
- private val context: Context,
- @Background private val executor: DelayableExecutor,
- private val uiController: ControlsUiController,
- private val bindingController: ControlsBindingController,
- private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
- optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
- dumpManager: DumpManager
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val listingController: ControlsListingController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpManager: DumpManager,
+ userTracker: UserTracker
) : Dumpable, ControlsController {
companion object {
@@ -85,7 +86,7 @@
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId
get() = currentUser.identifier
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 0d4439f..2d76ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.management
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.ServiceInfo
@@ -29,6 +28,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
@@ -56,14 +56,16 @@
class ControlsListingControllerImpl @VisibleForTesting constructor(
private val context: Context,
@Background private val backgroundExecutor: Executor,
- private val serviceListingBuilder: (Context) -> ServiceListing
+ private val serviceListingBuilder: (Context) -> ServiceListing,
+ userTracker: UserTracker
) : ControlsListingController {
@Inject
- constructor(context: Context, executor: Executor): this(
+ constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
context,
executor,
- ::createServiceListing
+ ::createServiceListing,
+ userTracker
)
private var serviceListing = serviceListingBuilder(context)
@@ -78,7 +80,7 @@
private var availableServices = emptyList<ServiceInfo>()
private var userChangeInProgress = AtomicInteger(0)
- override var currentUserId = ActivityManager.getCurrentUser()
+ override var currentUserId = userTracker.userId
private set
private val serviceListingCallback = ServiceListing.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 0fbd73b..f56e6cd 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -16,25 +16,23 @@
package com.android.systemui.privacy
-import android.app.ActivityManager
import android.app.AppOpsManager
-import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.UserInfo
import android.os.UserHandle
-import android.os.UserManager
import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.Dumpable
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
@@ -48,9 +46,8 @@
private val appOpsController: AppOpsController,
@Main uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
- private val broadcastDispatcher: BroadcastDispatcher,
private val deviceConfigProxy: DeviceConfigProxy,
- private val userManager: UserManager,
+ private val userTracker: UserTracker,
dumpManager: DumpManager
) : Dumpable {
@@ -153,13 +150,16 @@
}
@VisibleForTesting
- internal var userSwitcherReceiver = Receiver()
- set(value) {
- unregisterReceiver()
- field = value
- if (listening) registerReceiver()
+ internal var userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ update(true)
}
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ update(true)
+ }
+ }
+
init {
deviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_PRIVACY,
@@ -168,20 +168,18 @@
dumpManager.registerDumpable(TAG, this)
}
- private fun unregisterReceiver() {
- broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
+ private fun unregisterListener() {
+ userTracker.removeCallback(userTrackerCallback)
}
private fun registerReceiver() {
- broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter,
- null /* handler */, UserHandle.ALL)
+ userTracker.addCallback(userTrackerCallback, bgExecutor)
}
private fun update(updateUsers: Boolean) {
bgExecutor.execute {
if (updateUsers) {
- val currentUser = ActivityManager.getCurrentUser()
- currentUserIds = userManager.getProfiles(currentUser).map { it.id }
+ currentUserIds = userTracker.userProfiles.map { it.id }
}
updateListAndNotifyChanges.run()
}
@@ -206,7 +204,7 @@
update(true)
} else {
appOpsController.removeCallback(OPS, cb)
- unregisterReceiver()
+ unregisterListener()
// Make sure that we remove all indicators and notify listeners if we are not
// listening anymore due to indicators being disabled
update(false)
@@ -275,14 +273,6 @@
fun onFlagMicCameraChanged(flag: Boolean) {}
}
- internal inner class Receiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intentFilter.hasAction(intent.action)) {
- update(true)
- }
- }
- }
-
private class NotifyChangesToCallback(
private val callback: Callback?,
private val list: List<PrivacyItem>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 6e4ab9a..84563a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -20,6 +20,8 @@
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -34,6 +36,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -57,6 +60,7 @@
import com.android.systemui.R.dimen;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -75,6 +79,7 @@
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final UserTracker mUserTracker;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
@@ -115,11 +120,12 @@
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ActivityStarter activityStarter, UserInfoController userInfoController,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
super(context, attrs);
mActivityStarter = activityStarter;
mUserInfoController = userInfoController;
mDeviceProvisionedController = deviceProvisionedController;
+ mUserTracker = userTracker;
}
@VisibleForTesting
@@ -127,7 +133,8 @@
this(context, attrs,
Dependency.get(ActivityStarter.class),
Dependency.get(UserInfoController.class),
- Dependency.get(DeviceProvisionedController.class));
+ Dependency.get(DeviceProvisionedController.class),
+ Dependency.get(UserTracker.class));
}
@Override
@@ -150,6 +157,19 @@
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
+ mBuildText.setOnLongClickListener(view -> {
+ CharSequence buildText = mBuildText.getText();
+ if (!TextUtils.isEmpty(buildText)) {
+ ClipboardManager service =
+ mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
+ String label = mContext.getString(R.string.build_number_clip_data_label);
+ service.setPrimaryClip(ClipData.newPlainText(label, buildText));
+ Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ return false;
+ });
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -176,6 +196,7 @@
mBuildText.setSelected(true);
mShouldShowBuildText = true;
} else {
+ mBuildText.setText(null);
mShouldShowBuildText = false;
mBuildText.setSelected(false);
}
@@ -317,12 +338,14 @@
mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+ mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
private void updateVisibilities() {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
+ : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -376,15 +399,16 @@
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (TunerService.isTunerEnabled(mContext)) {
- TunerService.showResetRequest(mContext, () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
+ if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
+ TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
+ () -> {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity();
+ });
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, true);
+ TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
}
startSettingsActivity();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 290ab85..000fd1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -30,6 +30,7 @@
void openPanels();
Context getContext();
Context getUserContext();
+ int getUserId();
UiEventLogger getUiEventLogger();
Collection<QSTile> getTiles();
void addCallback(Callback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ae925d1..fdc0a60e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.PointF;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
@@ -57,6 +56,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -114,6 +114,7 @@
private final QSLogger mQSLogger;
protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
+ private final UserTracker mUserTracker;
@Nullable
protected QSSecurityFooter mSecurityFooter;
@@ -157,7 +158,8 @@
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -173,6 +175,7 @@
mDumpManager = dumpManager;
mBroadcastDispatcher = broadcastDispatcher;
mUiEventLogger = uiEventLogger;
+ mUserTracker = userTracker;
setOrientation(VERTICAL);
@@ -221,7 +224,7 @@
}
protected void addSecurityFooter() {
- mSecurityFooter = new QSSecurityFooter(this, mContext);
+ mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker);
}
protected void addViewsAboveTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index afc5be4e..0891972 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyEventLogger;
import android.content.Context;
@@ -45,6 +44,7 @@
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -61,8 +61,7 @@
private final SecurityController mSecurityController;
private final ActivityStarter mActivityStarter;
private final Handler mMainHandler;
-
- private final UserManager mUm;
+ private final UserTracker mUserTracker;
private AlertDialog mDialog;
private QSTileHost mHost;
@@ -73,7 +72,7 @@
private int mFooterTextId;
private int mFooterIconId;
- public QSSecurityFooter(QSPanel qsPanel, Context context) {
+ public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) {
mRootView = LayoutInflater.from(context)
.inflate(R.layout.quick_settings_footer, qsPanel, false);
mRootView.setOnClickListener(this);
@@ -85,7 +84,7 @@
mActivityStarter = Dependency.get(ActivityStarter.class);
mSecurityController = Dependency.get(SecurityController.class);
mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
- mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUserTracker = userTracker;
}
public void setHostEnvironment(QSTileHost host) {
@@ -138,7 +137,7 @@
private void handleRefreshState() {
final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
- final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser());
+ final UserInfo currentUser = mUserTracker.getUserInfo();
final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
&& currentUser.isDemo();
final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a63a56..0d0d012 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +48,7 @@
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -99,6 +99,7 @@
private int mCurrentUser;
private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
+ private UserTracker mUserTracker;
@Inject
public QSTileHost(Context context,
@@ -113,7 +114,8 @@
BroadcastDispatcher broadcastDispatcher,
Optional<StatusBar> statusBarOptional,
QSLogger qsLogger,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -125,12 +127,13 @@
mBroadcastDispatcher = broadcastDispatcher;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
- mServices = new TileServices(this, bgLooper, mBroadcastDispatcher);
+ mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker);
mStatusBarOptional = statusBarOptional;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
mDumpManager.registerDumpable(TAG, this);
+ mUserTracker = userTracker;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -230,6 +233,11 @@
}
@Override
+ public int getUserId() {
+ return mCurrentUser;
+ }
+
+ @Override
public TileServices getTileServices() {
return mServices;
}
@@ -248,9 +256,9 @@
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- int currentUser = ActivityManager.getCurrentUser();
+ int currentUser = mUserTracker.getUserId();
if (currentUser != mCurrentUser) {
- mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
+ mUserContext = mUserTracker.getUserContext();
if (mAutoTiles != null) {
mAutoTiles.changeUser(UserHandle.of(currentUser));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index affb7b9..ea036f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -38,6 +38,7 @@
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -70,9 +71,11 @@
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger,
+ userTracker);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
applyBottomMargin((View) mRegularTileLayout);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 8fec5a2..544249a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -20,7 +20,6 @@
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.annotation.ColorInt;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
@@ -76,6 +75,7 @@
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.qs.carrier.QSCarrierGroup;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -156,6 +156,7 @@
private RingerModeTracker mRingerModeTracker;
private DemoModeController mDemoModeController;
private DemoMode mDemoModeReceiver;
+ private UserTracker mUserTracker;
private boolean mAllIndicatorsEnabled;
private boolean mMicCameraIndicatorsEnabled;
@@ -212,7 +213,8 @@
StatusBarIconController statusBarIconController,
ActivityStarter activityStarter, PrivacyItemController privacyItemController,
CommandQueue commandQueue, RingerModeTracker ringerModeTracker,
- UiEventLogger uiEventLogger, DemoModeController demoModeController) {
+ UiEventLogger uiEventLogger, DemoModeController demoModeController,
+ UserTracker userTracker) {
super(context, attrs);
mAlarmController = nextAlarmController;
mZenController = zenModeController;
@@ -225,6 +227,7 @@
mRingerModeTracker = ringerModeTracker;
mUiEventLogger = uiEventLogger;
mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
}
@Override
@@ -723,7 +726,7 @@
return "";
}
String skeleton = android.text.format.DateFormat
- .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ .is24HourFormat(mContext, mUserTracker.getUserId()) ? "EHm" : "Ehma";
String pattern = android.text.format.DateFormat
.getBestDateTimePattern(Locale.getDefault(), skeleton);
return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 65d81505..3ee3e11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
@@ -37,10 +36,6 @@
protected abstract void handleValueChanged(int value, boolean observedChange);
- protected SecureSetting(Context context, Handler handler, String settingName) {
- this(context, handler, settingName, ActivityManager.getCurrentUser());
- }
-
public SecureSetting(Context context, Handler handler, String settingName, int userId) {
super(handler);
mContext = context;
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 bffeb3e..e049025 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -607,6 +607,12 @@
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final ViewHolder holder = parent.getChildViewHolder(child);
+ // Do not draw background for the holder that's currently being dragged
+ if (holder == mCurrentDrag) {
+ continue;
+ }
+ // Do not draw background for holders before the edit index (header and current
+ // tiles)
if (holder.getAdapterPosition() == 0 ||
holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
continue;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 73c6504..b795a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.customize;
import android.Manifest.permission;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +39,7 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -58,16 +58,18 @@
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final Context mContext;
+ private final UserTracker mUserTracker;
private TileStateListener mListener;
private boolean mFinished;
@Inject
- public TileQueryHelper(Context context,
+ public TileQueryHelper(Context context, UserTracker userTracker,
@Main Executor mainExecutor, @Background Executor bgExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
+ mUserTracker = userTracker;
}
public void setListener(TileStateListener listener) {
@@ -207,7 +209,7 @@
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+ new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
for (ResolveInfo info : services) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 19c7b6c..6e28cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -18,7 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -304,8 +303,7 @@
}
private Intent resolveIntent(Intent i) {
- ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
- ActivityManager.getCurrentUser());
+ ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser);
return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfa8fb6..7e76e57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs.external;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -26,7 +25,6 @@
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
-import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -36,6 +34,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
import java.util.Objects;
@@ -60,6 +59,7 @@
private final TileServices mServices;
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
+ private final UserTracker mUserTracker;
private boolean mBindRequested;
private boolean mBindAllowed;
private boolean mBound;
@@ -73,25 +73,26 @@
private boolean mStarted = false;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
- Tile tile, BroadcastDispatcher broadcastDispatcher) {
- this(tileServices, handler, new TileLifecycleManager(handler,
+ Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+ this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
- new UserHandle(ActivityManager.getCurrentUser()), broadcastDispatcher));
+ userTracker.getUserHandle(), broadcastDispatcher));
}
@VisibleForTesting
- TileServiceManager(TileServices tileServices, Handler handler,
+ TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
TileLifecycleManager tileLifecycleManager) {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
+ mUserTracker = userTracker;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
Context context = mServices.getContext();
- context.registerReceiverAsUser(mUninstallReceiver,
- new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
+ context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter,
+ null, mHandler);
}
boolean isLifecycleStarted() {
@@ -279,7 +280,7 @@
queryIntent.setPackage(pkgName);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- queryIntent, 0, ActivityManager.getCurrentUser());
+ queryIntent, 0, mUserTracker.getUserId());
for (ResolveInfo info : services) {
if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
&& Objects.equals(info.serviceInfo.name, component.getClassName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 2863d08..35cf2a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,6 +39,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -62,15 +63,18 @@
private final Handler mMainHandler;
private final QSTileHost mHost;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private int mMaxBound = DEFAULT_MAX_BOUND;
- public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher) {
+ public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mHost = host;
mContext = mHost.getContext();
mBroadcastDispatcher = broadcastDispatcher;
mHandler = new Handler(looper);
mMainHandler = new Handler(Looper.getMainLooper());
+ mUserTracker = userTracker;
mBroadcastDispatcher.registerReceiver(
mRequestListeningReceiver,
new IntentFilter(TileService.ACTION_REQUEST_LISTENING),
@@ -104,7 +108,7 @@
protected TileServiceManager onCreateTileService(ComponentName component, Tile tile,
BroadcastDispatcher broadcastDispatcher) {
return new TileServiceManager(this, mHandler, component, tile,
- broadcastDispatcher);
+ broadcastDispatcher, mUserTracker);
}
public void freeService(CustomTile tile, TileServiceManager service) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index dfd7e2c..5a81676 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,7 +31,6 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -521,9 +520,9 @@
protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
- userRestriction, ActivityManager.getCurrentUser());
+ userRestriction, mHost.getUserId());
if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- userRestriction, ActivityManager.getCurrentUser())) {
+ userRestriction, mHost.getUserId())) {
state.disabledByPolicy = true;
mEnforcedAdmin = admin;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 347ef45..98782f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
@@ -61,13 +62,14 @@
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
- QSLogger qsLogger
+ QSLogger qsLogger,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mSetting = new SecureSetting(mContext, mainHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ec8b143..2076cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,7 +19,6 @@
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
-import android.app.ActivityManager;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -203,7 +202,7 @@
break;
default:
Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- ActivityManager.getCurrentUser(), true).id;
+ mHost.getUserId(), true).id;
mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 26d408f..c7a8fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -40,6 +40,11 @@
val userHandle: UserHandle
/**
+ * [UserInfo] for current user
+ */
+ val userInfo: UserInfo
+
+ /**
* List of profiles associated with the current user.
*/
val userProfiles: List<UserInfo>
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 4cc0eee..049685f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -82,6 +82,12 @@
override val userContentResolver: ContentResolver
get() = userContext.contentResolver
+ override val userInfo: UserInfo
+ get() {
+ val user = userId
+ return userProfiles.first { it.id == user }
+ }
+
/**
* Returns a [List<UserInfo>] of all profiles associated with the current user.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 37ae791..0184fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -58,12 +58,14 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.tracing.ProtoTracer;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* This class takes the functions from IStatusBar that come in on
@@ -159,6 +161,7 @@
*/
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private ProtoTracer mProtoTracer;
+ private final @Nullable CommandRegistry mRegistry;
/**
* These methods are called back on the main thread.
@@ -368,11 +371,12 @@
}
public CommandQueue(Context context) {
- this(context, null);
+ this(context, null, null);
}
- public CommandQueue(Context context, ProtoTracer protoTracer) {
+ public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
mProtoTracer = protoTracer;
+ mRegistry = registry;
context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
// We always have default display.
setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -1013,6 +1017,34 @@
}
}
+ @Override
+ public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) {
+ final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
+ final PrintWriter pw = new PrintWriter(fos);
+ // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+ // to just throw this work onto the handler just like the other messages
+ Thread thr = new Thread("Sysui.passThroughShellCommand") {
+ public void run() {
+ try {
+ if (mRegistry == null) {
+ return;
+ }
+
+ // Registry blocks this thread until finished
+ mRegistry.onShellCommand(pw, args);
+ } finally {
+ pw.flush();
+ try {
+ // Close the file descriptor so the TransferPipe finishes its thread
+ pfd.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+ thr.start();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
new file mode 100644
index 0000000..ce0a08c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.content.Context
+
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.io.PrintWriter
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+
+import javax.inject.Inject
+
+/**
+ * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and
+ * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service
+ * like so:
+ *
+ * `adb shell cmd statusbar <command>`
+ *
+ * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and
+ * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue)
+ */
+@SysUISingleton
+class CommandRegistry @Inject constructor(
+ val context: Context,
+ @Main val mainExecutor: Executor
+) {
+ // To keep the command line parser hermetic, create a new one for every shell command
+ private val commandMap = mutableMapOf<String, CommandWrapper>()
+ private var initialized = false
+
+ /**
+ * Register a [Command] for a given name. The name here is the top-level namespace for
+ * the registered command. A command could look like this for instance:
+ *
+ * `adb shell cmd statusbar notifications list`
+ *
+ * Where `notifications` is the command that signifies which receiver to send the remaining args
+ * to.
+ *
+ * @param command String name of the command to register. Currently does not support aliases
+ * @param receiverFactory Creates an instance of the receiver on every command
+ * @param executor Pass an executor to offload your `receive` to another thread
+ */
+ @Synchronized
+ fun registerCommand(
+ name: String,
+ commandFactory: () -> Command,
+ executor: Executor
+ ) {
+ if (commandMap[name] != null) {
+ throw IllegalStateException("A command is already registered for ($name)")
+ }
+ commandMap[name] = CommandWrapper(commandFactory, executor)
+ }
+
+ /**
+ * Register a [Command] for a given name, to be executed on the main thread.
+ */
+ @Synchronized
+ fun registerCommand(name: String, commandFactory: () -> Command) {
+ registerCommand(name, commandFactory, mainExecutor)
+ }
+
+ /** Unregister a receiver */
+ @Synchronized
+ fun unregisterCommand(command: String) {
+ commandMap.remove(command)
+ }
+
+ private fun initializeCommands() {
+ initialized = true
+ // TODO: Might want a dedicated place for commands without a home. Currently
+ // this is here because Prefs.java is just an interface
+ registerCommand("prefs") { PrefsCommand(context) }
+ }
+
+ /**
+ * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished.
+ */
+ fun onShellCommand(pw: PrintWriter, args: Array<String>) {
+ if (!initialized) initializeCommands()
+
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val commandName = args[0]
+ val wrapper = commandMap[commandName]
+
+ if (wrapper == null) {
+ help(pw)
+ return
+ }
+
+ // Create a new instance of the command
+ val command = wrapper.commandFactory()
+
+ // Wrap the receive command in a task so that we can wait for its completion
+ val task = FutureTask<Unit> {
+ command.execute(pw, args.drop(1))
+ }
+
+ wrapper.executor.execute {
+ task.run()
+ }
+
+ // Wait for the future to complete
+ task.get()
+ }
+
+ private fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar <command>")
+ pw.println(" known commands:")
+ for (k in commandMap.keys) {
+ pw.println(" $k")
+ }
+ }
+}
+
+private const val TAG = "CommandRegistry"
+
+interface Command {
+ fun execute(pw: PrintWriter, args: List<String>)
+ fun help(pw: PrintWriter)
+}
+
+// Wrap commands in an executor package
+private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor)
+
+// Commands can go here for now, but they should move outside
+
+private class PrefsCommand(val context: Context) : Command {
+ override fun help(pw: PrintWriter) {
+ pw.println("usage: prefs <command> [args]")
+ pw.println("Available commands:")
+ pw.println(" list-prefs")
+ pw.println(" set-pref <pref name> <value>")
+ }
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val topLevel = args[0]
+
+ when (topLevel) {
+ "list-prefs" -> listPrefs(pw)
+ "set-pref" -> setPref(pw, args.drop(1))
+ else -> help(pw)
+ }
+ }
+
+ private fun listPrefs(pw: PrintWriter) {
+ pw.println("Available keys:")
+ for (field in Prefs.Key::class.java.declaredFields) {
+ pw.print(" ")
+ pw.println(field.get(Prefs.Key::class.java))
+ }
+ }
+
+ /**
+ * Sets a preference from [Prefs]
+ */
+ private fun setPref(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ pw.println("invalid arguments: $args")
+ return
+ }
+ val pref = args[0]
+
+ when (pref) {
+ Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> {
+ val value = Integer.parseInt(args[1])
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0)
+ }
+ else -> {
+ pw.println("Cannot set pref ($pref)")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 969cd90..c4e0429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -192,8 +193,11 @@
*/
@Provides
@SysUISingleton
- static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
- return new CommandQueue(context, protoTracer);
+ static CommandQueue provideCommandQueue(
+ Context context,
+ ProtoTracer protoTracer,
+ CommandRegistry registry) {
+ return new CommandQueue(context, protoTracer, registry);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index efd6767..76c5baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -169,7 +169,8 @@
String setting = split[0];
String spec = split[1];
// Populate all the settings. As they may not have been added in other users
- AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
+ AutoAddSetting s = new AutoAddSetting(
+ mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec);
mAutoAddSettingList.add(s);
} else {
Log.w(TAG, "Malformed item in array: " + tile);
@@ -319,8 +320,14 @@
private class AutoAddSetting extends SecureSetting {
private final String mSpec;
- AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) {
- super(context, handler, setting);
+ AutoAddSetting(
+ Context context,
+ Handler handler,
+ String setting,
+ int userId,
+ String tileSpec
+ ) {
+ super(context, handler, setting, userId);
mSpec = tileSpec;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 0fdc80b..8e8a33f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -20,7 +20,6 @@
import static com.android.settingslib.Utils.updateLocationEnabled;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +42,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -60,6 +60,7 @@
private final Context mContext;
private final AppOpsController mAppOpsController;
private final BootCompleteCache mBootCompleteCache;
+ private final UserTracker mUserTracker;
private final H mHandler;
@@ -68,11 +69,13 @@
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
@Main Looper mainLooper, @Background Handler backgroundHandler,
- BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) {
+ BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
+ UserTracker userTracker) {
mContext = context;
mAppOpsController = appOpsController;
mBootCompleteCache = bootCompleteCache;
mHandler = new H(mainLooper);
+ mUserTracker = userTracker;
// Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
@@ -113,7 +116,7 @@
public boolean setLocationEnabled(boolean enabled) {
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mUserTracker.getUserId();
if (isUserLocationRestricted(currentUserId)) {
return false;
}
@@ -134,7 +137,7 @@
LocationManager locationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser(
- UserHandle.of(ActivityManager.getCurrentUser()));
+ mUserTracker.getUserHandle());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 0070dcf..4c724ae 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.tuner;
+import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -22,6 +23,7 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
@@ -122,7 +124,8 @@
getActivity().finish();
return true;
case MENU_REMOVE:
- TunerService.showResetRequest(getContext(), new Runnable() {
+ UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
+ TunerService.showResetRequest(getContext(), user, new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 338e178..70bba26 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -14,7 +14,6 @@
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -51,25 +50,24 @@
void onTuningChanged(String key, String newValue);
}
- private static Context userContext(Context context) {
+ private static Context userContext(Context context, UserHandle user) {
try {
- return context.createPackageContextAsUser(context.getPackageName(), 0,
- new UserHandle(ActivityManager.getCurrentUser()));
+ return context.createPackageContextAsUser(context.getPackageName(), 0, user);
} catch (NameNotFoundException e) {
return context;
}
}
- public static final void setTunerEnabled(Context context, boolean enabled) {
- userContext(context).getPackageManager().setComponentEnabledSetting(
+ public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
+ userContext(context, user).getPackageManager().setComponentEnabledSetting(
new ComponentName(context, TunerActivity.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
- public static final boolean isTunerEnabled(Context context) {
- return userContext(context).getPackageManager().getComponentEnabledSetting(
+ public static final boolean isTunerEnabled(Context context, UserHandle user) {
+ return userContext(context, user).getPackageManager().getComponentEnabledSetting(
new ComponentName(context, TunerActivity.class))
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
@@ -83,7 +81,8 @@
}
}
- public static final void showResetRequest(final Context context, final Runnable onDisabled) {
+ public static final void showResetRequest(final Context context, UserHandle user,
+ final Runnable onDisabled) {
SystemUIDialog dialog = new SystemUIDialog(context);
dialog.setShowForAllUsers(true);
dialog.setMessage(R.string.remove_from_settings_prompt);
@@ -91,20 +90,20 @@
(OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // Tell the tuner (in main SysUI process) to clear all its settings.
- context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
- // Disable access to tuner.
- TunerService.setTunerEnabled(context, false);
- // Make them sit through the warning dialog again.
- Settings.Secure.putInt(context.getContentResolver(),
- TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
- if (onDisabled != null) {
- onDisabled.run();
- }
- }
- });
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Tell the tuner (in main SysUI process) to clear all its settings.
+ context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+ // Disable access to tuner.
+ TunerService.setTunerEnabled(context, user, false);
+ // Make them sit through the warning dialog again.
+ Settings.Secure.putInt(context.getContentResolver(),
+ TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+ if (onDisabled != null) {
+ onDisabled.run();
+ }
+ }
+ });
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index d9727a7..22f03e07 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,13 +15,13 @@
*/
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -37,7 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.util.leak.LeakDetector;
@@ -81,7 +81,8 @@
private ContentResolver mContentResolver;
private int mCurrentUser;
- private CurrentUserTracker mUserTracker;
+ private UserTracker.Callback mCurrentUserTracker;
+ private UserTracker mUserTracker;
/**
*/
@@ -91,11 +92,13 @@
@Main Handler mainHandler,
LeakDetector leakDetector,
DemoModeController demoModeController,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mLeakDetector = leakDetector;
mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
@@ -104,21 +107,22 @@
}
}
- mCurrentUser = ActivityManager.getCurrentUser();
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
+ mCurrentUser = mUserTracker.getUserId();
+ mCurrentUserTracker = new UserTracker.Callback() {
@Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
+ public void onUserChanged(int newUser, Context userContext) {
+ mCurrentUser = newUser;
reloadAll();
reregisterAll();
}
};
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mCurrentUserTracker,
+ new HandlerExecutor(mainHandler));
}
@Override
public void destroy() {
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mCurrentUserTracker);
}
private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) {
@@ -137,7 +141,7 @@
}
}
if (oldVersion < 2) {
- setTunerEnabled(mContext, false);
+ setTunerEnabled(mContext, mUserTracker.getUserHandle(), false);
}
// 3 Removed because of a revert.
if (oldVersion < 4) {
@@ -272,7 +276,7 @@
@Override
public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
int flags, int userId) {
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mUserTracker.getUserId()) {
for (Uri u : uris) {
reloadSetting(u);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index bb3b1b4..970d500 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,8 +32,10 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.common.AnimationThread;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
@@ -132,8 +134,10 @@
@SysUISingleton
@Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue) {
- ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue);
+ static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
+ @Main Handler handler, TransactionPool transactionPool) {
+ ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool,
+ new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
organizer.registerOrganizer();
return organizer;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index d107f64..ab805f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -62,6 +62,7 @@
import com.android.systemui.R.dimen;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import org.junit.Before;
@@ -88,6 +89,8 @@
private TunerService mTunerService;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private UserTracker mUserTracker;
@Before
public void setup() {
@@ -109,7 +112,7 @@
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler,
- mBroadcastDispatcher, mTunerService) {
+ mBroadcastDispatcher, mTunerService, mUserTracker) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index bb003ee..0a81c38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -27,6 +27,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -61,6 +62,8 @@
@Mock
private lateinit var mockControlsController: ControlsController
+ @Mock(stubOnly = true)
+ private lateinit var mockUserTracker: UserTracker
@Captor
private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
@@ -82,9 +85,10 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
providers.clear()
+ `when`(mockUserTracker.userHandle).thenReturn(user)
controller = TestableControlsBindingControllerImpl(
- mContext, executor, Lazy { mockControlsController })
+ mContext, executor, Lazy { mockControlsController }, mockUserTracker)
}
@After
@@ -364,8 +368,9 @@
class TestableControlsBindingControllerImpl(
context: Context,
executor: DelayableExecutor,
- lazyController: Lazy<ControlsController>
-) : ControlsBindingControllerImpl(context, executor, lazyController) {
+ lazyController: Lazy<ControlsController>,
+ userTracker: UserTracker
+) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) {
companion object {
val providers = mutableListOf<ControlsProviderLifecycleManager>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 45262c7..f6c836a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
@@ -82,6 +83,8 @@
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
private lateinit var listingController: ControlsListingController
+ @Mock(stubOnly = true)
+ private lateinit var userTracker: UserTracker
@Captor
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -143,6 +146,8 @@
Settings.Secure.putIntForUser(mContext.contentResolver,
ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser)
+ `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
+
delayableExecutor = FakeExecutor(FakeSystemClock())
val wrapper = object : ContextWrapper(mContext) {
@@ -162,7 +167,8 @@
listingController,
broadcastDispatcher,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java)
+ mock(DumpManager::class.java),
+ userTracker
)
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
@@ -217,7 +223,8 @@
listingController,
broadcastDispatcher,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java)
+ mock(DumpManager::class.java),
+ userTracker
)
assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 841b255..db41d8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.applications.ServiceListing
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
@@ -66,6 +67,8 @@
private lateinit var serviceInfo: ServiceInfo
@Mock
private lateinit var serviceInfo2: ServiceInfo
+ @Mock(stubOnly = true)
+ private lateinit var userTracker: UserTracker
private var componentName = ComponentName("pkg1", "class1")
private var componentName2 = ComponentName("pkg2", "class2")
@@ -86,6 +89,7 @@
`when`(serviceInfo.componentName).thenReturn(componentName)
`when`(serviceInfo2.componentName).thenReturn(componentName2)
+ `when`(userTracker.userId).thenReturn(user)
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -93,7 +97,7 @@
}
}
- controller = ControlsListingControllerImpl(wrapper, executor, { mockSL })
+ controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
}
@@ -121,7 +125,7 @@
`when`(mockServiceListing.reload()).then {
callback?.onServicesReloaded(listOf(serviceInfo))
}
- ControlsListingControllerImpl(mContext, exec, { mockServiceListing })
+ ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index 1f10d01..cb17829 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -16,15 +16,14 @@
package com.android.systemui.privacy
-import android.os.UserManager
import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,11 +61,9 @@
@Mock
private lateinit var callback: PrivacyItemController.Callback
@Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var userTracker: UserTracker
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
@@ -77,9 +74,8 @@
appOpsController,
executor,
executor,
- broadcastDispatcher,
deviceConfigProxy,
- userManager,
+ userTracker,
dumpManager
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 0a079b1..16a1105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -18,10 +18,8 @@
import android.app.ActivityManager
import android.app.AppOpsManager
-import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
-import android.os.UserManager
import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -30,8 +28,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
@@ -52,6 +50,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -84,9 +83,7 @@
@Mock
private lateinit var callback: PrivacyItemController.Callback
@Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
+ private lateinit var userTracker: UserTracker
@Mock
private lateinit var dumpManager: DumpManager
@Captor
@@ -103,9 +100,8 @@
appOpsController,
executor,
executor,
- broadcastDispatcher,
deviceConfigProxy,
- userManager,
+ userTracker,
dumpManager
)
}
@@ -119,11 +115,7 @@
// Listen to everything by default
changeAll(true)
- doReturn(listOf(object : UserInfo() {
- init {
- id = CURRENT_USER_ID
- }
- })).`when`(userManager).getProfiles(anyInt())
+ `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0)))
privacyItemController = PrivacyItemController()
}
@@ -163,37 +155,26 @@
}
@Test
- fun testRegisterReceiver_allUsers() {
+ fun testRegisterCallback() {
privacyItemController.addCallback(callback)
executor.runAllReady()
- verify(broadcastDispatcher, atLeastOnce()).registerReceiver(
- eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL))
- verify(broadcastDispatcher, never())
- .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver))
+ verify(userTracker, atLeastOnce()).addCallback(
+ eq(privacyItemController.userTrackerCallback), any())
+ verify(userTracker, never()).removeCallback(eq(privacyItemController.userTrackerCallback))
}
@Test
- fun testReceiver_ACTION_USER_FOREGROUND() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_USER_SWITCHED))
+ fun testCallback_userChanged() {
+ privacyItemController.userTrackerCallback.onUserChanged(0, mContext)
executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
+ verify(userTracker).userProfiles
}
@Test
- fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE))
+ fun testReceiver_profilesChanged() {
+ privacyItemController.userTrackerCallback.onProfilesChanged(emptyList())
executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
- }
-
- @Test
- fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE))
- executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
+ verify(userTracker).userProfiles
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
index d4688d7..99f2d80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
@@ -14,30 +14,40 @@
package com.android.systemui.qs;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.R.id;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -47,19 +57,46 @@
private QSFooterImpl mFooter;
private ActivityStarter mActivityStarter;
private DeviceProvisionedController mDeviceProvisionedController;
+ private UserInfoController mUserInfoController;
+ private UserTracker mUserTracker;
+ @Mock
+ private ClipboardManager mClipboardManager;
@Before
public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
mDeviceProvisionedController = mDependency.injectMockDependency(
DeviceProvisionedController.class);
+ mUserInfoController = mDependency.injectMockDependency(UserInfoController.class);
+ mUserTracker = mDependency.injectMockDependency(UserTracker.class);
+
+ mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
+
+ when(mUserTracker.getUserContext()).thenReturn(mContext);
+
TestableLooper.get(this).runWithLooper(
() -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate(
R.layout.qs_footer_impl, null));
}
@Test
+ public void testBuildTextCopy() {
+ TextView buildTextView = mFooter.requireViewById(R.id.build);
+ CharSequence buildText = "TEST";
+ buildTextView.setText(buildText);
+ buildTextView.setLongClickable(true);
+
+ buildTextView.performLongClick();
+
+ ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class);
+ verify(mClipboardManager).setPrimaryClip(captor.capture());
+ assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(buildText);
+ }
+
+ @Test
@Ignore("failing")
public void testSettings_UserNotSetup() {
View settingsButton = mFooter.findViewById(id.settings_button);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index b46c6ef..1c2d0840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -107,7 +108,7 @@
mock(PluginManager.class), mock(TunerService.class),
() -> mock(AutoTileManager.class), mock(DumpManager.class),
mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
- mock(QSLogger.class), mock(UiEventLogger.class));
+ mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class));
qs.setHost(host);
qs.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index cb3a048..4b7a268 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -38,7 +38,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -49,7 +48,7 @@
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.util.animation.DisappearParameters;
import com.android.systemui.util.animation.UniqueObjectHostView;
@@ -93,11 +92,9 @@
@Mock
private MediaHost mMediaHost;
@Mock
- private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
private ActivityStarter mActivityStarter;
- @Mock
- private NotificationEntryManager mEntryManager;
+ @Mock(stubOnly = true)
+ private UserTracker mUserTracker;
private UiEventLoggerFake mUiEventLogger;
@Before
@@ -117,7 +114,7 @@
mTestableLooper.runWithLooper(() -> {
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
- mQSLogger, mMediaHost, mUiEventLogger);
+ mQSLogger, mMediaHost, mUiEventLogger, mUserTracker);
mQsPanel.onFinishInflate();
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 417b19f..fd1866b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -17,7 +17,6 @@
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -25,7 +24,6 @@
import android.content.Context;
import android.content.pm.UserInfo;
-import android.os.UserManager;
import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -42,6 +40,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController;
import org.junit.Before;
@@ -73,20 +72,20 @@
private TestableImageView mFooterIcon;
private QSSecurityFooter mFooter;
private SecurityController mSecurityController = mock(SecurityController.class);
- private UserManager mUserManager;
+ private UserTracker mUserTracker;
@Before
public void setUp() {
mDependency.injectTestDependency(SecurityController.class, mSecurityController);
mDependency.injectTestDependency(Dependency.BG_LOOPER,
TestableLooper.get(this).getLooper());
+ mUserTracker = mock(UserTracker.class);
+ when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build());
- mUserManager = Mockito.mock(UserManager.class);
- mContext.addMockSystemService(Context.USER_SERVICE, mUserManager);
- mFooter = new QSSecurityFooter(null, mContext);
+ mFooter = new QSSecurityFooter(null, mContext, mUserTracker);
mRootView = (ViewGroup) mFooter.getView();
mFooterText = mRootView.findViewById(R.id.footer_text);
mFooterIcon = mRootView.findViewById(R.id.footer_icon);
@@ -141,7 +140,7 @@
when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
final UserInfo mockUserInfo = Mockito.mock(UserInfo.class);
when(mockUserInfo.isDemo()).thenReturn(true);
- when(mUserManager.getUserInfo(anyInt())).thenReturn(mockUserInfo);
+ when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
mFooter.refreshState();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c8e1a74..452ff4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -55,6 +55,7 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -110,6 +111,8 @@
private CustomTile mCustomTile;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserTracker mUserTracker;
private Handler mHandler;
private TestableLooper mLooper;
@@ -122,7 +125,7 @@
mHandler = new Handler(mLooper.getLooper());
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
- mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger);
+ mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker);
setUpTileFactory();
Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
@@ -301,10 +304,10 @@
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger, UserTracker userTracker) {
super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
tunerService, autoTiles, dumpManager, broadcastDispatcher,
- Optional.of(statusBar), qsLogger, uiEventLogger);
+ Optional.of(statusBar), qsLogger, uiEventLogger, userTracker);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 2ef7c65..a2ffb84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -55,6 +55,7 @@
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -103,6 +104,8 @@
private QSTileHost mQSTileHost;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private UserTracker mUserTracker;
@Captor
private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
@@ -133,7 +136,7 @@
FakeSystemClock clock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
- mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor);
+ mTileQueryHelper = new TileQueryHelper(mContext, mUserTracker, mMainExecutor, mBgExecutor);
mTileQueryHelper.setListener(mListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 683e8f4..6a9d9fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -22,11 +22,13 @@
import android.content.ComponentName;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.After;
import org.junit.Before;
@@ -44,6 +46,7 @@
private HandlerThread mThread;
private Handler mHandler;
private TileServiceManager mTileServiceManager;
+ private UserTracker mUserTracker;
@Before
public void setUp() throws Exception {
@@ -51,13 +54,18 @@
mThread.start();
mHandler = Handler.createAsync(mThread.getLooper());
mTileServices = Mockito.mock(TileServices.class);
+ mUserTracker = Mockito.mock(UserTracker.class);
+ Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+
Mockito.when(mTileServices.getContext()).thenReturn(mContext);
mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
ComponentName componentName = new ComponentName(mContext,
TileServiceManagerTest.class);
Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
- mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
+ mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+ mTileLifecycle);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 53ed4cf..2a3bc31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -92,6 +93,8 @@
private QSLogger mQSLogger;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserTracker mUserTracker;
@Before
public void setUp() throws Exception {
@@ -110,8 +113,10 @@
mock(BroadcastDispatcher.class),
Optional.of(mStatusBar),
mQSLogger,
- mUiEventLogger);
- mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher);
+ mUiEventLogger,
+ mUserTracker);
+ mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
+ mUserTracker);
}
@After
@@ -186,8 +191,8 @@
private class TestTileServices extends TileServices {
TestTileServices(QSTileHost host, Looper looper,
- BroadcastDispatcher broadcastDispatcher) {
- super(host, looper, broadcastDispatcher);
+ BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+ super(host, looper, broadcastDispatcher, userTracker);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
new file mode 100644
index 0000000..16eb1d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+
+import com.android.systemui.SysuiTestCase
+
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.Executor
+
+private fun <T> anyObject(): T {
+ return Mockito.anyObject<T>()
+}
+
+private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class CommandRegistryTest : SysuiTestCase() {
+ lateinit var registry: CommandRegistry
+ val inLineExecutor = object : Executor {
+ override fun execute(command: Runnable) {
+ command.run()
+ }
+ }
+
+ val writer: PrintWriter = PrintWriter(StringWriter())
+
+ @Before
+ fun setup() {
+ registry = CommandRegistry(context, inLineExecutor)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun testRegisterCommand_throwsWhenAlreadyRegistered() {
+ registry.registerCommand(COMMAND) { FakeCommand() }
+ // Should throw when registering the same command twice
+ registry.registerCommand(COMMAND) { FakeCommand() }
+ }
+
+ @Test
+ fun testOnShellCommand() {
+ var fakeCommand = mock(Command::class.java)
+ registry.registerCommand(COMMAND) { fakeCommand }
+ registry.onShellCommand(writer, arrayOf(COMMAND))
+ verify(fakeCommand).execute(anyObject(), anyList())
+ }
+
+ @Test
+ fun testArgsPassedToShellCommand() {
+ var fakeCommand = mock(Command::class.java)
+ registry.registerCommand(COMMAND) { fakeCommand }
+ registry.onShellCommand(writer, arrayOf(COMMAND, "arg1", "arg2", "arg3"))
+ verify(fakeCommand).execute(anyObject(), safeEq(listOf("arg1", "arg2", "arg3")))
+ }
+
+ class FakeCommand() : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ }
+
+ override fun help(pw: PrintWriter) {
+ }
+ }
+}
+
+private const val COMMAND = "test_command"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 0c2361a..be836d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -20,11 +20,13 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Intent;
import android.location.LocationManager;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -35,6 +37,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
import org.junit.Before;
@@ -52,10 +55,13 @@
private TestableLooper mTestableLooper;
@Mock private AppOpsController mAppOpsController;
+ @Mock private UserTracker mUserTracker;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
mTestableLooper = TestableLooper.get(this);
mLocationController = spy(new LocationControllerImpl(mContext,
@@ -63,7 +69,8 @@
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
mock(BroadcastDispatcher.class),
- mock(BootCompleteCache.class)));
+ mock(BootCompleteCache.class),
+ mUserTracker));
mTestableLooper.processAllMessages();
}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 33b9d00..da5f25b 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -28,6 +28,7 @@
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
import android.net.netlink.NetlinkSocket;
+import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.SharedLog;
import android.net.util.SocketUtils;
@@ -41,11 +42,12 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.NoSuchElementException;
@@ -66,11 +68,12 @@
private static final String NO_IPV4_ADDRESS = "";
private static final String NO_IPV4_GATEWAY = "";
// Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
- private static final int NF_NETLINK_CONNTRACK_NEW = 1;
- private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
- private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+ public static final int NF_NETLINK_CONNTRACK_NEW = 1;
+ public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+ public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
// Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h
public static final short NFNL_SUBSYS_CTNETLINK = 1;
+ public static final short IPCTNL_MSG_CT_NEW = 0;
public static final short IPCTNL_MSG_CT_GET = 1;
private final long NETLINK_MESSAGE_TIMEOUT_MS = 500;
@@ -237,7 +240,7 @@
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
(short) (NLM_F_REQUEST | NLM_F_DUMP));
final NativeHandle h2 = mDeps.createConntrackSocket(
@@ -267,16 +270,23 @@
}
@VisibleForTesting
- public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) {
- final int length = StructNlMsgHdr.STRUCT_SIZE;
+ public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
final byte[] msg = new byte[length];
- final StructNlMsgHdr nlh = new StructNlMsgHdr();
final ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlh = new StructNlMsgHdr();
nlh.nlmsg_len = length;
nlh.nlmsg_type = type;
nlh.nlmsg_flags = flags;
- nlh.nlmsg_seq = 1;
+ nlh.nlmsg_seq = 0;
nlh.pack(byteBuffer);
+
+ // Header needs to be added to buffer since a generic netlink request is being sent.
+ final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET);
+ nfh.pack(byteBuffer);
+
try {
NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
NETLINK_MESSAGE_TIMEOUT_MS);
diff --git a/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
new file mode 100644
index 0000000..57c28fc
--- /dev/null
+++ b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.StructNlMsgHdr;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.system.Os;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackSocketTest {
+ private static final long TIMEOUT = 500;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private final SharedLog mLog = new SharedLog("privileged-test");
+
+ private OffloadHardwareInterface mOffloadHw;
+ private OffloadHardwareInterface.Dependencies mDeps;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ mDeps = new OffloadHardwareInterface.Dependencies(mLog);
+ mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
+ }
+
+ @Test
+ public void testIpv4ConntrackSocket() throws Exception {
+ // Set up server and connect.
+ final InetSocketAddress anyAddress = new InetSocketAddress(
+ InetAddress.getByName("127.0.0.1"), 0);
+ final ServerSocket serverSocket = new ServerSocket();
+ serverSocket.bind(anyAddress);
+ final SocketAddress theAddress = serverSocket.getLocalSocketAddress();
+
+ // Make a connection to the server.
+ final Socket socket = new Socket();
+ socket.connect(theAddress);
+ final Socket acceptedSocket = serverSocket.accept();
+
+ final NativeHandle handle = mDeps.createConntrackSocket(
+ NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+ mOffloadHw.sendIpv4NfGenMsg(handle,
+ (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ (short) (NLM_F_REQUEST | NLM_F_DUMP));
+
+ boolean foundConntrackEntry = false;
+ ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE);
+ buffer.order(ByteOrder.nativeOrder());
+
+ try {
+ while (Os.read(handle.getFileDescriptor(), buffer) > 0) {
+ buffer.flip();
+
+ // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr
+ // so we can confirm that the conntrack added is for the TCP connection above.
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer);
+ assertNotNull(nlmsghdr);
+
+ // As long as 1 conntrack entry is found test case will pass, even if it's not
+ // the from the TCP connection above.
+ if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) {
+ foundConntrackEntry = true;
+ break;
+ }
+ }
+ } finally {
+ socket.close();
+ serverSocket.close();
+ }
+ assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message",
+ foundConntrackEntry);
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index c543fad..38b19dd 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -17,8 +17,9 @@
package com.android.networkstack.tethering;
import static android.net.util.TetheringUtils.uint16;
-import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -35,14 +36,15 @@
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.NativeHandle;
import android.os.test.TestLooper;
import android.system.ErrnoException;
-import android.system.OsConstants;
import android.system.Os;
+import android.system.OsConstants;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,8 +57,8 @@
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
-import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
@RunWith(AndroidJUnit4.class)
@@ -218,7 +220,7 @@
}
@Test
- public void testNetlinkMessage() throws Exception {
+ public void testSendIpv4NfGenMsg() throws Exception {
FileDescriptor writeSocket = new FileDescriptor();
FileDescriptor readSocket = new FileDescriptor();
try {
@@ -229,17 +231,25 @@
}
when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket);
- mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS);
+ mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS);
- ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE);
+ ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen.
+ buffer.order(ByteOrder.nativeOrder());
+
int read = Os.read(readSocket, buffer);
+ final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+ assertEquals(expectedLen, read);
buffer.flip();
- assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt());
+ assertEquals(expectedLen, buffer.getInt());
assertEquals(TEST_TYPE, buffer.getShort());
assertEquals(TEST_FLAGS, buffer.getShort());
- assertEquals(1 /* seq */, buffer.getInt());
+ assertEquals(0 /* seq */, buffer.getInt());
assertEquals(0 /* pid */, buffer.getInt());
+ assertEquals(AF_INET, buffer.get()); // nfgen_family
+ assertEquals(0 /* error */, buffer.get()); // version
+ assertEquals(0 /* error */, buffer.getShort()); // res_id
+ assertEquals(expectedLen, buffer.position());
}
private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
index 6842dde..d719a97 100644
--- a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
+++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
@@ -21,7 +21,6 @@
android:versionName="1.0">
<overlay
android:targetPackage="android"
- android:targetName="IconShapeCustomization"
android:category="android.theme.customization.adaptive_icon_shape"
android:priority="1"/>
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 0e7fc93..4eb1d9a 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -40,7 +40,7 @@
* <p>This information is persisted to the filesystem so that it is not lost in the event of a
* reboot.
*/
-public final class DataChangedJournal {
+public class DataChangedJournal {
private static final String TAG = "DataChangedJournal";
private static final String FILE_NAME_PREFIX = "journal";
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb60573..6c08826 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -712,8 +712,6 @@
Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
// Pre-created user was started right after creation so services could properly
// intialize it; it should be stopped right away as it's not really a "real" user.
- // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
- // callback on SystemService instead.
stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 668713f..c1777b8 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -584,29 +584,6 @@
// process is not in foreground.
return MODE_IGNORED;
}
- } else if (mode == MODE_ALLOWED) {
- switch (op) {
- case OP_CAMERA:
- if (mActivityManagerInternal != null
- && mActivityManagerInternal.isPendingTopUid(uid)) {
- return MODE_ALLOWED;
- } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
- case OP_RECORD_AUDIO:
- if (mActivityManagerInternal != null
- && mActivityManagerInternal.isPendingTopUid(uid)) {
- return MODE_ALLOWED;
- } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
- default:
- return MODE_ALLOWED;
- }
}
return mode;
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 72734c4..0329c3c 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -23,10 +23,10 @@
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -36,6 +36,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.Intent;
import android.location.Criteria;
@@ -82,12 +83,12 @@
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssManagerService;
+import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
@@ -97,6 +98,7 @@
import com.android.server.location.util.LocationUsageLogger;
import com.android.server.location.util.ScreenInteractiveHelper;
import com.android.server.location.util.SettingsHelper;
+import com.android.server.location.util.SystemAlarmHelper;
import com.android.server.location.util.SystemAppForegroundHelper;
import com.android.server.location.util.SystemAppOpsHelper;
import com.android.server.location.util.SystemLocationPermissionsHelper;
@@ -569,7 +571,7 @@
new IllegalArgumentException());
}
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -591,7 +593,7 @@
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -600,8 +602,7 @@
manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
}
- private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
- @PermissionLevel int permissionLevel) {
+ private LocationRequest validateLocationRequest(LocationRequest request) {
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
mContext.enforceCallingOrSelfPermission(
@@ -620,26 +621,20 @@
}
LocationRequest.Builder sanitized = new LocationRequest.Builder(request);
- if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
- sanitized.setLowPower(false);
- }
- if (permissionLevel < PERMISSION_FINE) {
- switch (request.getQuality()) {
- case LocationRequest.ACCURACY_FINE:
- sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
- break;
- case LocationRequest.POWER_HIGH:
- sanitized.setQuality(LocationRequest.POWER_LOW);
- break;
- }
- if (request.getIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
- sanitized.setIntervalMillis(FASTEST_COARSE_INTERVAL_MS);
+ if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
+ if (request.isLowPower()) {
+ mContext.enforceCallingOrSelfPermission(
+ permission.LOCATION_HARDWARE,
+ "low power request requires " + permission.LOCATION_HARDWARE);
}
- if (request.getMinUpdateIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
- sanitized.clearMinUpdateIntervalMillis();
+ } else {
+ if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
+ != PERMISSION_GRANTED) {
+ sanitized.setLowPower(false);
}
}
+
if (request.getWorkSource() != null) {
if (request.getWorkSource().isEmpty()) {
sanitized.setWorkSource(null);
@@ -716,7 +711,7 @@
// clients in the system process must have an attribution tag set
Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ request = validateLocationRequest(request);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -735,7 +730,7 @@
// use fine permission level to avoid creating unnecessary coarse locations
Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL,
- PERMISSION_FINE, false);
+ PERMISSION_FINE, false, Long.MAX_VALUE);
if (location == null) {
return null;
}
@@ -1237,6 +1232,7 @@
private static class SystemInjector implements Injector {
private final UserInfoHelper mUserInfoHelper;
+ private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
private final SystemSettingsHelper mSettingsHelper;
@@ -1249,6 +1245,7 @@
SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mUserInfoHelper = userInfoHelper;
+ mAlarmHelper = new SystemAlarmHelper(context);
mAppOpsHelper = new SystemAppOpsHelper(context);
mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
mAppOpsHelper);
@@ -1276,6 +1273,11 @@
}
@Override
+ public AlarmHelper getAlarmHelper() {
+ return mAlarmHelper;
+ }
+
+ @Override
public AppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;
}
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 138301a..cd8bf4a 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -16,13 +16,14 @@
package com.android.server.location;
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.compat.CompatChanges.isChangeEnabled;
+import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.KEY_LOCATION_CHANGED;
import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.location.LocationRequest.PASSIVE_INTERVAL;
import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
@@ -36,11 +37,11 @@
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static java.lang.Math.max;
import static java.lang.Math.min;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
import android.annotation.Nullable;
-import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -88,6 +89,7 @@
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.util.AppOpsHelper;
@@ -118,12 +120,12 @@
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
- // fastest interval at which clients may receive coarse locations
- public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
-
private static final String WAKELOCK_TAG = "*location*";
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
+ // fastest interval at which clients may receive coarse locations
+ private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;
+
// max interval to be considered "high power" request
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
@@ -133,8 +135,15 @@
// max timeout allowed for getting the current location
private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
- // max jitter allowed for fastest interval evaluation
- private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
+ // max jitter allowed for min update interval as a percentage of the interval
+ private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;
+
+ // max absolute jitter allowed for min update interval evaluation
+ private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 5 * 1000;
+
+ // minimum amount of request delay in order to respect the delay, below this value the request
+ // will just be scheduled immediately
+ private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;
protected interface LocationTransport {
@@ -221,6 +230,7 @@
/**
* Must be implemented to return the location this operation intends to deliver.
*/
+ @Nullable
Location getLocation();
}
@@ -312,7 +322,7 @@
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
- return null;
+ return onProviderListenerActive();
}
@Override
@@ -325,6 +335,22 @@
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
+ return onProviderListenerInactive();
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onActive()}.
+ */
+ @GuardedBy("mLock")
+ protected LocationListenerOperation onProviderListenerActive() {
+ return null;
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onInactive()} ()}.
+ */
+ @GuardedBy("mLock")
+ protected LocationListenerOperation onProviderListenerInactive() {
return null;
}
@@ -333,6 +359,14 @@
return mProviderLocationRequest;
}
+ @GuardedBy("mLock")
+ final void initializeLastLocation(@Nullable Location location) {
+ if (mLastLocation == null) {
+ mLastLocation = location;
+ }
+ }
+
+ @GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
return mLastLocation;
}
@@ -465,9 +499,27 @@
}
private LocationRequest calculateProviderLocationRequest() {
- LocationRequest.Builder builder = new LocationRequest.Builder(super.getRequest());
+ LocationRequest baseRequest = super.getRequest();
+ LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
- if (super.getRequest().isLocationSettingsIgnored()) {
+ if (mPermissionLevel < PERMISSION_FINE) {
+ switch (baseRequest.getQuality()) {
+ case LocationRequest.ACCURACY_FINE:
+ builder.setQuality(LocationRequest.ACCURACY_BLOCK);
+ break;
+ case LocationRequest.POWER_HIGH:
+ builder.setQuality(LocationRequest.POWER_LOW);
+ break;
+ }
+ if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
+ }
+ if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ builder.clearMinUpdateIntervalMillis();
+ }
+ }
+
+ if (baseRequest.isLocationSettingsIgnored()) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
@@ -476,10 +528,10 @@
}
}
- if (!super.getRequest().isLocationSettingsIgnored() && !isThrottlingExempt()) {
+ if (!baseRequest.isLocationSettingsIgnored() && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
- builder.setIntervalMillis(Math.max(super.getRequest().getIntervalMillis(),
+ builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -534,7 +586,7 @@
}
protected abstract class LocationRegistration extends Registration implements
- AlarmManager.OnAlarmListener, ProviderEnabledListener {
+ OnAlarmListener, ProviderEnabledListener {
private final PowerManager.WakeLock mWakeLock;
@@ -561,17 +613,15 @@
@GuardedBy("mLock")
@Override
protected final void onProviderListenerRegister() {
- mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
- SystemClock.elapsedRealtime());
+ long registerTimeMs = SystemClock.elapsedRealtime();
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
- if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
- remove();
+ if (mExpirationRealtimeMs <= registerTimeMs) {
+ onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
- 0, this, FgThread.getHandler(), getWorkSource());
+ mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+ getWorkSource());
}
// start listening for provider enabled/disabled events
@@ -594,9 +644,7 @@
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.cancel(this);
+ mAlarmHelper.cancel(this);
}
onLocationListenerUnregister();
@@ -614,6 +662,39 @@
@GuardedBy("mLock")
protected void onLocationListenerUnregister() {}
+ @GuardedBy("mLock")
+ @Override
+ protected final LocationListenerOperation onProviderListenerActive() {
+ // a new registration may not get a location immediately, the provider request may be
+ // delayed. therefore we deliver a historical location if available. since delivering an
+ // older location could be considered a breaking change for some applications, we only
+ // do so for apps targeting S+.
+ if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) {
+ long maxLocationAgeMs = getRequest().getIntervalMillis();
+ Location lastDeliveredLocation = getLastDeliveredLocation();
+ if (lastDeliveredLocation != null) {
+ // ensure that location is fresher than the last delivered location
+ maxLocationAgeMs = min(maxLocationAgeMs,
+ lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1);
+ }
+
+ // requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense
+ // to deliver historical locations to clients with a last location older than that
+ if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) {
+ Location lastLocation = getLastLocationUnsafe(
+ getIdentity().getUserId(),
+ PERMISSION_FINE, // acceptLocationChange() handles coarsening this
+ getRequest().isLocationSettingsIgnored(),
+ maxLocationAgeMs);
+ if (lastLocation != null) {
+ return acceptLocationChange(lastLocation);
+ }
+ }
+ }
+
+ return null;
+ }
+
@Override
public void onAlarm() {
if (D) {
@@ -624,6 +705,8 @@
synchronized (mLock) {
remove();
+ // no need to remove alarm after it's fired
+ mExpirationRealtimeMs = Long.MAX_VALUE;
}
}
@@ -658,11 +741,11 @@
Location lastDeliveredLocation = getLastDeliveredLocation();
if (lastDeliveredLocation != null) {
// check fastest interval
- long deltaMs = NANOSECONDS.toMillis(
- location.getElapsedRealtimeNanos()
- - lastDeliveredLocation.getElapsedRealtimeNanos());
- if (deltaMs < getRequest().getMinUpdateIntervalMillis()
- - MAX_FASTEST_INTERVAL_JITTER_MS) {
+ long deltaMs = location.getElapsedRealtimeMillis()
+ - lastDeliveredLocation.getElapsedRealtimeMillis();
+ long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE
+ * getRequest().getIntervalMillis()), MAX_FASTEST_INTERVAL_JITTER_MS);
+ if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
return null;
}
@@ -871,7 +954,7 @@
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
- IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
+ IBinder.DeathRecipient, ProviderEnabledListener, OnAlarmListener {
private volatile LocationTransport mTransport;
@@ -902,15 +985,15 @@
remove();
}
- mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
- SystemClock.elapsedRealtime());
+ long registerTimeMs = SystemClock.elapsedRealtime();
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
- if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
- 0, this, FgThread.getHandler(), getWorkSource());
+ if (mExpirationRealtimeMs <= registerTimeMs) {
+ onAlarm();
+ } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+ getWorkSource());
}
// if this request is ignoring location settings, then we don't want to immediately fail
@@ -935,9 +1018,7 @@
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.cancel(this);
+ mAlarmHelper.cancel(this);
}
((IBinder) getKey()).unlinkToDeath(this, 0);
@@ -953,6 +1034,8 @@
synchronized (mLock) {
deliverLocation(null);
+ // no need to remove alarm after it's fired
+ mExpirationRealtimeMs = Long.MAX_VALUE;
}
}
@@ -964,6 +1047,12 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
+ // check expiration time - alarm is not guaranteed to go off at the right time,
+ // especially for short intervals
+ if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
+ fineLocation = null;
+ }
+
// lastly - note app ops
Location location;
if (fineLocation == null) {
@@ -1077,6 +1166,7 @@
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserInfoHelper;
+ protected final AlarmHelper mAlarmHelper;
protected final AppOpsHelper mAppOpsHelper;
protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
@@ -1120,6 +1210,9 @@
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
+ @GuardedBy("mLock")
+ @Nullable private OnAlarmListener mDelayedRegister;
+
LocationProviderManager(Context context, Injector injector, String name,
@Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
@@ -1135,6 +1228,7 @@
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
mUserInfoHelper = injector.getUserInfoHelper();
+ mAlarmHelper = injector.getAlarmHelper();
mAppOpsHelper = injector.getAppOpsHelper();
mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
@@ -1344,7 +1438,7 @@
}
Location location = getLastLocationUnsafe(identity.getUserId(), permissionLevel,
- ignoreLocationSettings);
+ ignoreLocationSettings, Long.MAX_VALUE);
// we don't note op here because we don't know what the client intends to do with the
// location, the client is responsible for noting if necessary
@@ -1364,13 +1458,14 @@
*/
@Nullable
public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings) {
+ boolean ignoreLocationSettings, long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
+ // find the most recent location across all users
Location lastLocation = null;
final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
- ignoreLocationSettings);
+ ignoreLocationSettings, maximumAgeMs);
if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
> lastLocation.getElapsedRealtimeNanos())) {
lastLocation = next;
@@ -1381,18 +1476,30 @@
Preconditions.checkArgument(userId >= 0);
+ Location location;
synchronized (mLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
- return null;
+ location = null;
+ } else {
+ location = lastLocation.get(permissionLevel, ignoreLocationSettings);
}
- return lastLocation.get(permissionLevel, ignoreLocationSettings);
}
+
+ if (location == null) {
+ return null;
+ }
+
+ if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) {
+ return null;
+ }
+
+ return location;
}
public void injectLastLocation(Location location, int userId) {
synchronized (mLock) {
- if (getLastLocationUnsafe(userId, PERMISSION_FINE, false) == null) {
+ if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
}
}
@@ -1455,22 +1562,14 @@
return null;
}
- Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(),
- permissionLevel, request.isLocationSettingsIgnored());
+ Location lastLocation = getLastLocationUnsafe(
+ callerIdentity.getUserId(),
+ permissionLevel,
+ request.isLocationSettingsIgnored(),
+ MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
- long locationAgeMs = NANOSECONDS.toMillis(
- SystemClock.elapsedRealtimeNanos()
- - lastLocation.getElapsedRealtimeNanos());
- if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
- registration.deliverLocation(lastLocation);
- return null;
- }
-
- if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
- && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
- registration.deliverLocation(null);
- return null;
- }
+ registration.deliverLocation(lastLocation);
+ return null;
}
// if last location isn't good enough then we add a location request
@@ -1629,6 +1728,16 @@
@GuardedBy("mLock")
@Override
+ protected void onRegistrationReplaced(Object key, Registration oldRegistration,
+ Registration newRegistration) {
+ // by saving the last delivered location state we are able to potentially delay the
+ // resulting provider request longer and save additional power
+ newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
+ super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+ }
+
+ @GuardedBy("mLock")
+ @Override
protected void onRegistrationRemoved(Object key, Registration registration) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
@@ -1652,21 +1761,61 @@
@GuardedBy("mLock")
@Override
- protected boolean registerWithService(ProviderRequest mergedRequest,
+ protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- mProvider.setRequest(mergedRequest);
- return true;
+ return reregisterWithService(EMPTY_REQUEST, request, registrations);
}
@GuardedBy("mLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
- return registerWithService(newRequest, registrations);
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
+ // calculate how long the new request should be delayed before sending it off to the
+ // provider, under the assumption that once we send the request off, the provider will
+ // immediately attempt to deliver a new location satisfying that request.
+ long delayMs;
+ if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) {
+ delayMs = 0;
+ } else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
+ // if the interval has increased, tell the provider immediately, so it can save power
+ // (even though technically this could burn extra power in the short term by producing
+ // an extra location - the provider itself is free to detect an increasing interval and
+ // delay its own location)
+ delayMs = 0;
+ } else {
+ delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations);
+ }
+
+ // the delay should never exceed the new interval
+ Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis());
+
+ if (delayMs < MIN_REQUEST_DELAY_MS) {
+ mProvider.setRequest(newRequest);
+ } else {
+ mDelayedRegister = new OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ synchronized (mLock) {
+ if (mDelayedRegister == this) {
+ mProvider.setRequest(newRequest);
+ mDelayedRegister = null;
+ }
+ }
+ }
+ };
+ mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, newRequest.getWorkSource());
+ }
+
+ return true;
}
@GuardedBy("mLock")
@@ -1733,42 +1882,40 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
-
long intervalMs = Long.MAX_VALUE;
boolean locationSettingsIgnored = false;
boolean lowPower = true;
ArrayList<LocationRequest> locationRequests = new ArrayList<>(registrations.size());
- for (Registration registration : registrations) {
- LocationRequest locationRequest = registration.getRequest();
- // passive requests do not contribute to the provider
- if (locationRequest.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) {
+ for (Registration registration : registrations) {
+ LocationRequest request = registration.getRequest();
+
+ // passive requests do not contribute to the provider request
+ if (request.getIntervalMillis() == PASSIVE_INTERVAL) {
continue;
}
- providerRegistrations.add(registration);
- intervalMs = min(locationRequest.getIntervalMillis(), intervalMs);
- locationSettingsIgnored |= locationRequest.isLocationSettingsIgnored();
- lowPower &= locationRequest.isLowPower();
- locationRequests.add(locationRequest);
+ intervalMs = min(request.getIntervalMillis(), intervalMs);
+ locationSettingsIgnored |= request.isLocationSettingsIgnored();
+ lowPower &= request.isLowPower();
+ locationRequests.add(request);
}
// calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
// interval slightly higher that the minimum interval, and spread the blame across all
// contributing registrations under that threshold (since worksource does not allow us to
// represent differing power blame ratios).
- WorkSource workSource = new WorkSource();
long thresholdIntervalMs = (intervalMs + 1000) * 3 / 2;
- if (thresholdIntervalMs < 0) {
- // handle overflow by setting to one below the passive interval
- thresholdIntervalMs = Long.MAX_VALUE - 1;
+ if (thresholdIntervalMs < 0 || thresholdIntervalMs >= PASSIVE_INTERVAL) {
+ // check for and handle overflow by setting to one below the passive interval so passive
+ // requests are automatically skipped
+ thresholdIntervalMs = PASSIVE_INTERVAL - 1;
}
- final int providerRegistrationsSize = providerRegistrations.size();
- for (int i = 0; i < providerRegistrationsSize; i++) {
- Registration registration = providerRegistrations.get(i);
+
+ WorkSource workSource = new WorkSource();
+ for (Registration registration : registrations) {
if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) {
- workSource.add(providerRegistrations.get(i).getWorkSource());
+ workSource.add(registration.getWorkSource());
}
}
@@ -1781,6 +1928,47 @@
.build();
}
+ @GuardedBy("mLock")
+ protected long calculateRequestDelayMillis(long newIntervalMs,
+ Collection<Registration> registrations) {
+ // calculate the minimum delay across all registrations, ensuring that it is not more than
+ // the requested interval
+ long delayMs = newIntervalMs;
+ for (Registration registration : registrations) {
+ if (delayMs == 0) {
+ break;
+ }
+
+ LocationRequest locationRequest = registration.getRequest();
+ Location last = registration.getLastDeliveredLocation();
+
+ if (last == null && !locationRequest.isLocationSettingsIgnored()) {
+ // if this request has never gotten any location and it's not ignoring location
+ // settings, then we pretend that this request has gotten the last applicable cached
+ // location for our calculations instead. this prevents spammy add/remove behavior
+ last = getLastLocationUnsafe(
+ registration.getIdentity().getUserId(),
+ PERMISSION_FINE,
+ false,
+ locationRequest.getIntervalMillis());
+ }
+
+ long registrationDelayMs;
+ if (last == null) {
+ // if this request has never gotten any location then there's no delay
+ registrationDelayMs = 0;
+ } else {
+ // otherwise the delay is the amount of time until the next location is expected
+ registrationDelayMs = max(0,
+ locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis());
+ }
+
+ delayMs = min(delayMs, registrationDelayMs);
+ }
+
+ return delayMs;
+ }
+
private void onUserChanged(int userId, int change) {
synchronized (mLock) {
switch (change) {
@@ -2068,7 +2256,7 @@
ipw.increaseIndent();
}
ipw.print("last location=");
- ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
+ ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE));
ipw.print("enabled=");
ipw.println(isEnabled(userId));
if (userIds.length != 1) {
@@ -2126,24 +2314,37 @@
}
}
- public void set(Location location, Location coarseLocation) {
- mFineLocation = location;
+ public void set(Location fineLocation, Location coarseLocation) {
+ mFineLocation = calculateNextFine(mFineLocation, fineLocation);
mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
}
- public void setBypass(Location location, Location coarseLocation) {
- mFineBypassLocation = location;
+ public void setBypass(Location fineLocation, Location coarseLocation) {
+ mFineBypassLocation = calculateNextFine(mFineBypassLocation, fineLocation);
mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
}
+ private Location calculateNextFine(@Nullable Location oldFine, Location newFine) {
+ if (oldFine == null) {
+ return newFine;
+ }
+
+ // update last fine interval only if more recent
+ if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) {
+ return newFine;
+ } else {
+ return oldFine;
+ }
+ }
+
private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
if (oldCoarse == null) {
return newCoarse;
}
+
// update last coarse interval only if enough time has passed
- long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
- - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
- if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
+ if (newCoarse.getElapsedRealtimeNanos() - MIN_COARSE_INTERVAL_MS
+ > oldCoarse.getElapsedRealtimeNanos()) {
return newCoarse;
} else {
return oldCoarse;
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
index afeb644..2870d41 100644
--- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -20,14 +20,12 @@
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
-import android.location.LocationRequest;
import android.os.Binder;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
import com.android.server.location.util.Injector;
-import java.util.ArrayList;
import java.util.Collection;
class PassiveLocationProviderManager extends LocationProviderManager {
@@ -65,17 +63,20 @@
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
- ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
- .setIntervalMillis(0);
-
- ArrayList<LocationRequest> requests = new ArrayList<>(registrations.size());
+ boolean locationSettingsIgnored = false;
for (Registration registration : registrations) {
- requests.add(registration.getRequest());
- if (registration.getRequest().isLocationSettingsIgnored()) {
- providerRequest.setLocationSettingsIgnored(true);
- }
+ locationSettingsIgnored |= registration.getRequest().isLocationSettingsIgnored();
}
- return providerRequest.setLocationRequests(requests).build();
+ return new ProviderRequest.Builder()
+ .setIntervalMillis(0)
+ .setLocationSettingsIgnored(locationSettingsIgnored)
+ .build();
+ }
+
+ @Override
+ protected long calculateRequestDelayMillis(long newIntervalMs,
+ Collection<Registration> registrations) {
+ return 0;
}
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 87d668a..0cda57c 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -57,6 +57,8 @@
* <li>{@link #onRegister()}</li>
* <li>{@link ListenerRegistration#onRegister(Object)}</li>
* <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
+ * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
+ * invoked if this registration is replacing a prior registration)</li>
* <li>{@link #onActive()}</li>
* <li>{@link ListenerRegistration#onActive()}</li>
* <li>{@link ListenerRegistration#onInactive()}</li>
@@ -183,6 +185,17 @@
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
+ * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
+ * registration is replacing an old registration. The old registration will have already been
+ * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
+ * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+ */
+ protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+ @NonNull TRegistration newRegistration) {
+ onRegistrationAdded(key, newRegistration);
+ }
+
+ /**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
@@ -227,9 +240,10 @@
boolean wasEmpty = mRegistrations.isEmpty();
+ TRegistration oldRegistration = null;
int index = mRegistrations.indexOfKey(key);
if (index >= 0) {
- removeRegistration(index, false);
+ oldRegistration = removeRegistration(index, false);
mRegistrations.setValueAt(index, registration);
} else {
mRegistrations.put(key, registration);
@@ -239,7 +253,11 @@
onRegister();
}
registration.onRegister(key);
- onRegistrationAdded(key, registration);
+ if (oldRegistration == null) {
+ onRegistrationAdded(key, registration);
+ } else {
+ onRegistrationReplaced(key, oldRegistration, registration);
+ }
onRegistrationActiveChanged(registration);
}
}
@@ -320,7 +338,7 @@
}
@GuardedBy("mRegistrations")
- private void removeRegistration(int index, boolean removeEntry) {
+ private TRegistration removeRegistration(int index, boolean removeEntry) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@@ -347,6 +365,8 @@
}
}
}
+
+ return registration;
}
/**
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index e28d73e..5dc4318 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -18,7 +18,9 @@
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.NonNull;
@@ -78,16 +80,19 @@
synchronized (mSharedLock) {
ProviderState currentState = mCurrentState.get();
switch (currentState.stateEnum) {
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN:
+ case PROVIDER_STATE_ENABLED_CERTAIN: {
// Losing a remote provider is treated as becoming uncertain.
String msg = "handleProviderLost reason=" + reason
+ ", mProviderName=" + mProviderName
+ ", currentState=" + currentState;
debugLog(msg);
- // This is an unusual PROVIDER_STATE_ENABLED state because event == null
+ // This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because
+ // event == null
ProviderState newState = currentState.newState(
- PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
- msg);
+ PROVIDER_STATE_ENABLED_UNCERTAIN, null,
+ currentState.currentUserConfiguration, msg);
setCurrentState(newState, true);
break;
}
@@ -118,7 +123,9 @@
synchronized (mSharedLock) {
ProviderState currentState = mCurrentState.get();
switch (currentState.stateEnum) {
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
debugLog("handleOnProviderBound mProviderName=" + mProviderName
+ ", currentState=" + currentState + ": Provider is enabled.");
break;
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index bedaeda..179ce75 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -24,7 +24,9 @@
import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.NonNull;
@@ -38,6 +40,7 @@
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import java.time.Duration;
+import java.util.List;
import java.util.Objects;
/**
@@ -49,8 +52,7 @@
*/
class ControllerImpl extends LocationTimeZoneProviderController {
- @NonNull private final LocationTimeZoneProvider mProvider;
- @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
+ @NonNull private final LocationTimeZoneProvider mPrimaryProvider;
@GuardedBy("mSharedLock")
// Non-null after initialize()
@@ -65,12 +67,9 @@
private Callback mCallback;
/**
- * Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is
- * one.
+ * Used for scheduling uncertainty timeouts, i.e after the provider has reported uncertainty.
*/
- @GuardedBy("mSharedLock")
- @Nullable
- private GeolocationTimeZoneSuggestion mPendingSuggestion;
+ @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
/** Contains the last suggestion actually made, if there is one. */
@GuardedBy("mSharedLock")
@@ -78,10 +77,10 @@
private GeolocationTimeZoneSuggestion mLastSuggestion;
ControllerImpl(@NonNull ThreadingDomain threadingDomain,
- @NonNull LocationTimeZoneProvider provider) {
+ @NonNull LocationTimeZoneProvider primaryProvider) {
super(threadingDomain);
- mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue();
- mProvider = Objects.requireNonNull(provider);
+ mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
+ mPrimaryProvider = Objects.requireNonNull(primaryProvider);
}
@Override
@@ -94,8 +93,12 @@
mCallback = Objects.requireNonNull(callback);
mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
- mProvider.initialize(ControllerImpl.this::onProviderStateChange);
- enableOrDisableProvider(mCurrentUserConfiguration);
+ LocationTimeZoneProvider.ProviderListener providerListener =
+ ControllerImpl.this::onProviderStateChange;
+ mPrimaryProvider.initialize(providerListener);
+
+ alterProviderEnabledStateIfRequired(
+ null /* oldConfiguration */, mCurrentUserConfiguration);
}
}
@@ -115,92 +118,148 @@
// If the user changed, disable the provider if needed. It may be re-enabled for
// the new user below if their settings allow.
debugLog("User changed. old=" + oldConfig.getUserId()
- + ", new=" + newConfig.getUserId());
- debugLog("Disabling LocationTimeZoneProviders as needed");
- if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) {
- mProvider.disable();
- }
- }
+ + ", new=" + newConfig.getUserId() + ": Disabling provider");
+ disableProvider();
- enableOrDisableProvider(newConfig);
+ alterProviderEnabledStateIfRequired(null /* oldConfiguration */, newConfig);
+ } else {
+ alterProviderEnabledStateIfRequired(oldConfig, newConfig);
+ }
}
}
}
+ @Override
+ boolean isUncertaintyTimeoutSet() {
+ return mUncertaintyTimeoutQueue.hasQueued();
+ }
+
+ @Override
+ long getUncertaintyTimeoutDelayMillis() {
+ return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
+ }
+
@GuardedBy("mSharedLock")
- private void enableOrDisableProvider(@NonNull ConfigurationInternal configuration) {
- ProviderState providerState = mProvider.getCurrentState();
- boolean geoDetectionEnabled = configuration.getGeoDetectionEnabledBehavior();
- boolean providerWasEnabled = providerState.stateEnum == PROVIDER_STATE_ENABLED;
- if (geoDetectionEnabled) {
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_DISABLED: {
- debugLog("Enabling " + mProvider);
- mProvider.enable(
- configuration, mEnvironment.getProviderInitializationTimeout());
- break;
- }
- case PROVIDER_STATE_ENABLED: {
- debugLog("No need to enable " + mProvider + ": already enabled");
- break;
- }
- case PROVIDER_STATE_PERM_FAILED: {
- debugLog("Unable to enable " + mProvider + ": it is perm failed");
- break;
- }
- default:
- warnLog("Unknown provider state: " + mProvider);
- break;
+ private void disableProvider() {
+ disableProviderIfEnabled(mPrimaryProvider);
+
+ // By definition, if the provider is disabled, the controller is uncertain.
+ cancelUncertaintyTimeout();
+ }
+
+ @GuardedBy("mSharedLock")
+ private void disableProviderIfEnabled(LocationTimeZoneProvider provider) {
+ if (provider.getCurrentState().isEnabled()) {
+ disableProvider(provider);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void disableProvider(LocationTimeZoneProvider provider) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("No need to disable " + provider + ": already disabled");
+ break;
}
- } else {
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_DISABLED: {
- debugLog("No need to disable " + mProvider + ": already enabled");
- break;
- }
- case PROVIDER_STATE_ENABLED: {
- debugLog("Disabling " + mProvider);
- mProvider.disable();
- break;
- }
- case PROVIDER_STATE_PERM_FAILED: {
- debugLog("Unable to disable " + mProvider + ": it is perm failed");
- break;
- }
- default: {
- warnLog("Unknown provider state: " + mProvider);
- break;
- }
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+ debugLog("Disabling " + provider);
+ provider.disable();
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("Unable to disable " + provider + ": it is perm failed");
+ break;
+ }
+ default: {
+ warnLog("Unknown provider state: " + provider);
+ break;
}
}
+ }
- boolean isProviderEnabled =
- mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED;
+ /**
+ * Sets the provider into the correct enabled/disabled state for the {@code newConfiguration}
+ * and, if there is a provider state change, makes any suggestions required to inform the
+ * downstream time zone detection code.
+ *
+ * <p>This is a utility method that exists to avoid duplicated logic for the various cases when
+ * provider enabled / disabled state may need to be set or changed, e.g. during initialization
+ * or when a new configuration has been received.
+ */
+ @GuardedBy("mSharedLock")
+ private void alterProviderEnabledStateIfRequired(
+ @Nullable ConfigurationInternal oldConfiguration,
+ @NonNull ConfigurationInternal newConfiguration) {
- if (isProviderEnabled) {
- if (!providerWasEnabled) {
- // When a provider has first been enabled, we allow it some time for it to
- // initialize before sending its first event.
- Duration initializationTimeout = mEnvironment.getProviderInitializationTimeout()
- .plus(mEnvironment.getProviderInitializationTimeoutFuzz());
- // This sets up an empty suggestion to trigger if no explicit "certain" or
- // "uncertain" suggestion preempts it within initializationTimeout. If, for some
- // reason, the provider does not produce any events then this scheduled suggestion
- // will ensure the controller makes at least an "uncertain" suggestion.
- suggestDelayed(createEmptySuggestion("No event received from provider in"
- + " initializationTimeout=" + initializationTimeout),
- initializationTimeout);
+ // Provider enabled / disabled states only need to be changed if geoDetectionEnabled has
+ // changed.
+ boolean oldGeoDetectionEnabled = oldConfiguration != null
+ && oldConfiguration.getGeoDetectionEnabledBehavior();
+ boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
+ if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
+ return;
+ }
+
+ if (newGeoDetectionEnabled) {
+ // Try to enable the primary provider.
+ tryEnableProvider(mPrimaryProvider, newConfiguration);
+
+ ProviderState newPrimaryState = mPrimaryProvider.getCurrentState();
+ if (!newPrimaryState.isEnabled()) {
+ // If the provider is perm failed then the controller is immediately considered
+ // uncertain.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Provider is failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
}
} else {
- // Clear any queued suggestions.
- clearDelayedSuggestion();
+ disableProvider();
- // If the provider is now not enabled, and a previous "certain" suggestion has been
- // made, then a new "uncertain" suggestion must be made to indicate the provider no
- // longer has an opinion and will not be sending updates.
+ // There can be an uncertainty timeout set if the controller most recently received
+ // an uncertain event. This is a no-op if there isn't a timeout set.
+ cancelUncertaintyTimeout();
+
+ // If a previous "certain" suggestion has been made, then a new "uncertain"
+ // suggestion must now be made to indicate the controller {does not / no longer has}
+ // an opinion and will not be sending further updates (until at least the config
+ // changes again and providers are re-enabled).
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
- suggestImmediate(createEmptySuggestion(
- "Provider disabled, clearing previous suggestion"));
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Provider is disabled:"
+ + " primary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
+ }
+ }
+
+ private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider,
+ @NonNull ConfigurationInternal configuration) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("Enabling " + provider);
+ provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(),
+ mEnvironment.getProviderInitializationTimeoutFuzz());
+ break;
+ }
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+ debugLog("No need to enable " + provider + ": already enabled");
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("Unable to enable " + provider + ": it is perm failed");
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Unknown provider state:"
+ + " provider=" + provider
+ + ", state=" + providerState.stateEnum);
}
}
}
@@ -217,7 +276,9 @@
+ " providerState=" + providerState);
break;
}
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
// Entering enabled does not trigger an event, so this only happens if an event
// is received while the provider is enabled.
debugLog("onProviderStateChange: Received notification of an event while"
@@ -228,10 +289,7 @@
case PROVIDER_STATE_PERM_FAILED: {
debugLog("Received notification of permanent failure for"
+ " provider=" + providerState);
- GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
- "provider=" + providerState.provider
- + " permanently failed: " + providerState);
- suggestImmediate(suggestion);
+ providerFailedProcessEvent();
break;
}
default: {
@@ -242,24 +300,46 @@
}
private void assertProviderKnown(LocationTimeZoneProvider provider) {
- if (provider != mProvider) {
+ if (provider != mPrimaryProvider) {
throw new IllegalArgumentException("Unknown provider: " + provider);
}
}
/**
- * Called when a provider has changed state but just moved from a PROVIDER_STATE_ENABLED state
- * to another PROVIDER_STATE_ENABLED state, usually as a result of a new {@link
- * LocationTimeZoneEvent} being received. There are some cases where event can be null.
+ * Called when the provider has reported that it has failed permanently.
*/
+ @GuardedBy("mSharedLock")
+ private void providerFailedProcessEvent() {
+ // If the provider is newly perm failed then the controller is uncertain by
+ // definition.
+ cancelUncertaintyTimeout();
+
+ // If the provider is now failed, then we must send a suggestion informing the time
+ // zone detector that there are no further updates coming in future.
+
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "The provider is permanently failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
+
+ /**
+ * Called when a provider has changed state but just moved from one enabled state to another
+ * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received.
+ * However, there are rare cases where the event can be null.
+ */
+ @GuardedBy("mSharedLock")
private void providerEnabledProcessEvent(@NonNull ProviderState providerState) {
+ LocationTimeZoneProvider provider = providerState.provider;
LocationTimeZoneEvent event = providerState.event;
if (event == null) {
// Implicit uncertainty, i.e. where the provider is enabled, but a problem has been
// detected without having received an event. For example, if the process has detected
- // the loss of a binder-based provider. This is treated like explicit uncertainty, i.e.
- // where the provider has explicitly told this process it is uncertain.
- scheduleUncertainSuggestionIfNeeded(null);
+ // the loss of a binder-based provider, or initialization took too long. This is treated
+ // the same as explicit uncertainty, i.e. where the provider has explicitly told this
+ // process it is uncertain.
+ handleProviderUncertainty(provider, "provider=" + provider
+ + ", implicit uncertainty, event=null");
return;
}
@@ -279,22 +359,19 @@
switch (event.getEventType()) {
case EVENT_TYPE_PERMANENT_FAILURE: {
- // This shouldn't happen. Providers cannot be enabled and have this event.
+ // This shouldn't happen. A provider cannot be enabled and have this event.
warnLog("Provider=" + providerState
+ " is enabled, but event suggests it shouldn't be");
break;
}
case EVENT_TYPE_UNCERTAIN: {
- scheduleUncertainSuggestionIfNeeded(event);
+ handleProviderUncertainty(provider, "provider=" + provider
+ + ", explicit uncertainty. event=" + event);
break;
}
case EVENT_TYPE_SUCCESS: {
- GeolocationTimeZoneSuggestion suggestion =
- new GeolocationTimeZoneSuggestion(event.getTimeZoneIds());
- suggestion.addDebugInfo("Event received provider=" + mProvider.getName()
- + ", event=" + event);
- // Rely on the receiver to dedupe events. It is better to over-communicate.
- suggestImmediate(suggestion);
+ handleProviderCertainty(provider, event.getTimeZoneIds(),
+ "Event received provider=" + provider.getName() + ", event=" + event);
break;
}
default: {
@@ -304,30 +381,19 @@
}
}
- /**
- * Indicates a provider has become uncertain with the event (if any) received that indicates
- * that.
- *
- * <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
- * this enables the most flexibility for the controller to enable other providers when there are
- * multiple ones available. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion.
- *
- * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
- * made later if nothing else preempts it. It can be preempted if the provider becomes certain
- * (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
- * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
- * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
- * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
- * reset each time).
- */
- private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
- if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
- GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
- "provider=" + mProvider + " became uncertain, event=" + event);
- // Only send the empty suggestion after the uncertainty delay.
- suggestDelayed(suggestion, mEnvironment.getUncertaintyDelay());
- }
+ @GuardedBy("mSharedLock")
+ private void handleProviderCertainty(
+ @NonNull LocationTimeZoneProvider provider,
+ @Nullable List<String> timeZoneIds,
+ @NonNull String reason) {
+ // By definition, the controller is now certain.
+ cancelUncertaintyTimeout();
+
+ GeolocationTimeZoneSuggestion suggestion =
+ new GeolocationTimeZoneSuggestion(timeZoneIds);
+ suggestion.addDebugInfo(reason);
+ // Rely on the receiver to dedupe events. It is better to over-communicate.
+ makeSuggestion(suggestion);
}
@Override
@@ -342,66 +408,74 @@
ipw.println("providerInitializationTimeoutFuzz="
+ mEnvironment.getProviderInitializationTimeoutFuzz());
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
- ipw.println("mPendingSuggestion=" + mPendingSuggestion);
ipw.println("mLastSuggestion=" + mLastSuggestion);
- ipw.println("Provider:");
+ ipw.println("Primary Provider:");
ipw.increaseIndent(); // level 2
- mProvider.dump(ipw, args);
+ mPrimaryProvider.dump(ipw, args);
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
}
}
- /** Sends an immediate suggestion, cancelling any pending suggestion. */
+ /** Sends an immediate suggestion, updating mLastSuggestion. */
@GuardedBy("mSharedLock")
- private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) {
- debugLog("suggestImmediate: Executing suggestion=" + suggestion);
- mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion));
- mPendingSuggestion = null;
+ private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ debugLog("makeSuggestion: suggestion=" + suggestion);
+ mCallback.suggest(suggestion);
mLastSuggestion = suggestion;
}
- /** Clears any pending suggestion. */
+ /** Clears the uncertainty timeout. */
@GuardedBy("mSharedLock")
- private void clearDelayedSuggestion() {
- mDelayedSuggestionQueue.cancel();
- mPendingSuggestion = null;
+ private void cancelUncertaintyTimeout() {
+ mUncertaintyTimeoutQueue.cancel();
}
-
/**
- * Schedules a delayed suggestion. There can only be one delayed suggestion at a time.
- * If there is a pending scheduled suggestion equal to the one passed, it will not be replaced.
- * Replacing a previous delayed suggestion has the effect of cancelling the timeout associated
- * with that previous suggestion.
+ * Indicates a provider has become uncertain with the event (if any) received that indicates
+ * that.
+ *
+ * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
+ * this enables the most flexibility for the controller to enable other providers when there are
+ * multiple ones available. The controller is therefore responsible for deciding when to make a
+ * "uncertain" suggestion.
+ *
+ * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
+ * made later if nothing else preempts it. It can be preempted if the provider becomes certain
+ * (or does anything else that calls {@link #makeSuggestion(GeolocationTimeZoneSuggestion)})
+ * within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * "uncertain" event to be cancelled. If the provider repeatedly sends uncertainty events within
+ * the uncertainty delay period, those events are effectively ignored (i.e. the timer is not
+ * reset each time).
*/
@GuardedBy("mSharedLock")
- private void suggestDelayed(@NonNull GeolocationTimeZoneSuggestion suggestion,
- @NonNull Duration delay) {
- Objects.requireNonNull(suggestion);
- Objects.requireNonNull(delay);
+ void handleProviderUncertainty(@NonNull LocationTimeZoneProvider provider, String reason) {
+ Objects.requireNonNull(provider);
- if (Objects.equals(mPendingSuggestion, suggestion)) {
- // Do not reset the timer.
- debugLog("suggestDelayed: Suggestion=" + suggestion + " is equal to existing."
- + " Not scheduled.");
- return;
+ // Start the uncertainty timeout if needed.
+ if (!mUncertaintyTimeoutQueue.hasQueued()) {
+ debugLog("Starting uncertainty timeout: reason=" + reason);
+
+ Duration delay = mEnvironment.getUncertaintyDelay();
+ mUncertaintyTimeoutQueue.runDelayed(
+ this::onProviderUncertaintyTimeout, delay.toMillis());
}
-
- debugLog("suggestDelayed: Scheduling suggestion=" + suggestion);
- mPendingSuggestion = suggestion;
-
- mDelayedSuggestionQueue.runDelayed(() -> {
- debugLog("suggestDelayed: Executing suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mPendingSuggestion = null;
- mLastSuggestion = suggestion;
- }, delay.toMillis());
}
- private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) {
+ private void onProviderUncertaintyTimeout() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Uncertainty timeout triggered:"
+ + " primary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
+ }
+
+ private static GeolocationTimeZoneSuggestion createUncertainSuggestion(String reason) {
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
suggestion.addDebugInfo(reason);
return suggestion;
@@ -412,17 +486,22 @@
* If the provider name does not match a known provider, then the event is logged and discarded.
*/
void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
- if (!Objects.equals(mProvider.getName(), event.getProviderName())) {
+ String targetProviderName = event.getProviderName();
+ LocationTimeZoneProvider targetProvider;
+ if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) {
+ targetProvider = mPrimaryProvider;
+ } else {
warnLog("Unable to process simulated binder provider event,"
+ " unknown providerName in event=" + event);
return;
}
- if (!(mProvider instanceof BinderLocationTimeZoneProvider)) {
+ if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) {
warnLog("Unable to process simulated binder provider event,"
- + " provider is not a " + BinderLocationTimeZoneProvider.class
+ + " provider=" + targetProvider
+ + " is not a " + BinderLocationTimeZoneProvider.class
+ ", event=" + event);
return;
}
- ((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event);
+ ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event);
}
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 238f999..c9a211d 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -160,7 +160,8 @@
// Called on an arbitrary thread during initialization.
synchronized (mSharedLock) {
LocationTimeZoneProvider primary = createPrimaryProvider();
- mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary);
+ mLocationTimeZoneDetectorController =
+ new ControllerImpl(mThreadingDomain, primary);
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
mThreadingDomain, mLocationTimeZoneDetectorController);
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index abfa580..4b0b5a2 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -22,7 +22,9 @@
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.IntDef;
@@ -33,6 +35,9 @@
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
@@ -73,8 +78,9 @@
*/
static class ProviderState {
- @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED,
- PROVIDER_STATE_PERM_FAILED })
+ @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING,
+ PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN,
+ PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED })
@interface ProviderStateEnum {}
/**
@@ -83,22 +89,33 @@
static final int PROVIDER_STATE_UNKNOWN = 0;
/**
- * The provider is currently enabled.
+ * The provider is enabled and has not reported its first event.
*/
- static final int PROVIDER_STATE_ENABLED = 1;
+ static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1;
/**
- * The provider is currently disabled.
+ * The provider is enabled and most recently reported a "success" event.
+ */
+ static final int PROVIDER_STATE_ENABLED_CERTAIN = 2;
+
+ /**
+ * The provider is enabled and most recently reported an "uncertain" event.
+ */
+ static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3;
+
+ /**
+ * The provider is disabled.
+ *
* This is the state after {@link #initialize} is called.
*/
- static final int PROVIDER_STATE_DISABLED = 2;
+ static final int PROVIDER_STATE_DISABLED = 4;
/**
* The provider has failed and cannot be re-enabled.
*
* Providers may enter this state after a provider is enabled.
*/
- static final int PROVIDER_STATE_PERM_FAILED = 3;
+ static final int PROVIDER_STATE_PERM_FAILED = 5;
/** The {@link LocationTimeZoneProvider} the state is for. */
public final @NonNull LocationTimeZoneProvider provider;
@@ -108,14 +125,15 @@
/**
* The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum}
- * is {@link #PROVIDER_STATE_ENABLED}, but it can be {@code null} then too if no event has
+ * is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link
+ * #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has
* yet been received.
*/
@Nullable public final LocationTimeZoneEvent event;
/**
* The user configuration associated with the current state. Only and always present when
- * {@link #stateEnum} is {@link #PROVIDER_STATE_ENABLED}.
+ * {@link #stateEnum} is one of the enabled states.
*/
@Nullable public final ConfigurationInternal currentUserConfiguration;
@@ -133,7 +151,8 @@
private ProviderState(@NonNull LocationTimeZoneProvider provider,
- @ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event,
+ @ProviderStateEnum int stateEnum,
+ @Nullable LocationTimeZoneEvent event,
@Nullable ConfigurationInternal currentUserConfiguration,
@Nullable String debugInfo) {
this.provider = Objects.requireNonNull(provider);
@@ -172,7 +191,9 @@
break;
}
case PROVIDER_STATE_DISABLED:
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
// These can go to each other or PROVIDER_STATE_PERM_FAILED.
break;
}
@@ -200,7 +221,9 @@
}
break;
}
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
if (currentUserConfig == null) {
throw new IllegalArgumentException(
"Enabled state: currentUserConfig must not be null");
@@ -223,6 +246,13 @@
return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo);
}
+ /** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */
+ boolean isEnabled() {
+ return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING
+ || stateEnum == PROVIDER_STATE_ENABLED_CERTAIN
+ || stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN;
+ }
+
@Override
public String toString() {
return "State{"
@@ -257,8 +287,12 @@
switch (state) {
case PROVIDER_STATE_DISABLED:
return "Disabled (" + PROVIDER_STATE_DISABLED + ")";
- case PROVIDER_STATE_ENABLED:
- return "Enabled (" + PROVIDER_STATE_ENABLED + ")";
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")";
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")";
+ case PROVIDER_STATE_ENABLED_UNCERTAIN:
+ return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")";
case PROVIDER_STATE_PERM_FAILED:
return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")";
case PROVIDER_STATE_UNKNOWN:
@@ -279,6 +313,11 @@
final ReferenceWithHistory<ProviderState> mCurrentState =
new ReferenceWithHistory<>(10);
+ /**
+ * Used for scheduling initialization timeouts, i.e. for providers that have just been enabled.
+ */
+ @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue;
+
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
@@ -286,6 +325,7 @@
LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
}
@@ -303,7 +343,8 @@
mProviderListener = Objects.requireNonNull(providerListener);
ProviderState currentState = ProviderState.createStartingState(this);
ProviderState newState = currentState.newState(
- PROVIDER_STATE_DISABLED, null, null, "initialize() called");
+ PROVIDER_STATE_DISABLED, null, null,
+ "initialize() called");
setCurrentState(newState, false);
onInitialize();
@@ -370,20 +411,41 @@
* called using the handler thread from the {@link ThreadingDomain}.
*/
final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
- @NonNull Duration initializationTimeout) {
+ @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
assertCurrentState(PROVIDER_STATE_DISABLED);
- ProviderState currentState = getCurrentState();
+ ProviderState currentState = mCurrentState.get();
ProviderState newState = currentState.newState(
- PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
+ PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */,
+ currentUserConfiguration, "enable() called");
setCurrentState(newState, false);
+
+ Duration delay = initializationTimeout.plus(initializationTimeoutFuzz);
+ mInitializationTimeoutQueue.runDelayed(
+ this::handleInitializationTimeout, delay.toMillis());
+
onEnable(initializationTimeout);
}
}
+ private void handleInitializationTimeout() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ ProviderState currentState = mCurrentState.get();
+ if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) {
+ // On initialization timeout the provider becomes uncertain.
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */,
+ currentState.currentUserConfiguration, "initialization timeout");
+ setCurrentState(newState, true);
+ }
+ }
+ }
+
/**
* Implemented by subclasses to do work during {@link #enable}.
*/
@@ -391,20 +453,24 @@
/**
* Disables the provider. It is an error* to call this method except when the {@link
- * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_ENABLED}. This method must be
+ * #getCurrentState()} is one of the enabled states. This method must be
* called using the handler thread from the {@link ThreadingDomain}.
*/
final void disable() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
- assertCurrentState(PROVIDER_STATE_ENABLED);
+ assertIsEnabled();
- ProviderState currentState = getCurrentState();
- ProviderState newState =
- currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called");
+ ProviderState currentState = mCurrentState.get();
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_DISABLED, null, null, "disable() called");
setCurrentState(newState, false);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
+
onDisable();
}
}
@@ -424,7 +490,7 @@
debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName
+ ", locationTimeZoneEvent=" + locationTimeZoneEvent);
- ProviderState currentState = getCurrentState();
+ ProviderState currentState = mCurrentState.get();
int eventType = locationTimeZoneEvent.getEventType();
switch (currentState.stateEnum) {
case PROVIDER_STATE_PERM_FAILED: {
@@ -445,6 +511,9 @@
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
return;
}
case EVENT_TYPE_SUCCESS:
@@ -464,7 +533,9 @@
}
}
}
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
switch (eventType) {
case EVENT_TYPE_PERMANENT_FAILURE: {
String msg = "handleLocationTimeZoneEvent:"
@@ -475,14 +546,27 @@
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
+
return;
}
case EVENT_TYPE_UNCERTAIN:
case EVENT_TYPE_SUCCESS: {
- ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED,
+ @ProviderStateEnum int providerStateEnum;
+ if (eventType == EVENT_TYPE_UNCERTAIN) {
+ providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN;
+ } else {
+ providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN;
+ }
+ ProviderState newState = currentState.newState(providerStateEnum,
locationTimeZoneEvent, currentState.currentUserConfiguration,
"handleLocationTimeZoneEvent() when enabled");
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
return;
}
default: {
@@ -503,11 +587,34 @@
*/
abstract void logWarn(String msg);
- private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) {
- ProviderState currentState = getCurrentState();
+ @GuardedBy("mSharedLock")
+ private void assertIsEnabled() {
+ ProviderState currentState = mCurrentState.get();
+ if (!currentState.isEnabled()) {
+ throw new IllegalStateException("Required an enabled state, but was " + currentState);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void assertCurrentState(@ProviderStateEnum int requiredState) {
+ ProviderState currentState = mCurrentState.get();
if (currentState.stateEnum != requiredState) {
throw new IllegalStateException(
"Required stateEnum=" + requiredState + ", but was " + currentState);
}
}
+
+ @VisibleForTesting
+ boolean isInitializationTimeoutSet() {
+ synchronized (mSharedLock) {
+ return mInitializationTimeoutQueue.hasQueued();
+ }
+ }
+
+ @VisibleForTesting
+ Duration getInitializationTimeoutDelay() {
+ synchronized (mSharedLock) {
+ return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
index 88f0f00..ace066e 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.os.Handler;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
@@ -85,6 +86,12 @@
*/
abstract void onConfigChanged();
+ @VisibleForTesting
+ abstract boolean isUncertaintyTimeoutSet();
+
+ @VisibleForTesting
+ abstract long getUncertaintyTimeoutDelayMillis();
+
/**
* Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
* service. It can easily be faked for tests.
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
index 9b9c823..d55d3ed 100644
--- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
+++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
@@ -83,21 +83,14 @@
}
/**
- * A class that allows up to one {@link Runnable} to be queued on the handler, i.e. calling any
- * of the methods will cancel the execution of any previously queued / delayed runnable. All
+ * A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link
+ * #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All
* methods must be called from the {@link ThreadingDomain}'s thread.
*/
final class SingleRunnableQueue {
- /**
- * Runs the supplied {@link Runnable} synchronously on the threading domain's thread,
- * cancelling any queued but not-yet-executed {@link Runnable} previously added by this.
- * This method must be called from the threading domain's thread.
- */
- void runSynchronously(Runnable r) {
- cancel();
- r.run();
- }
+ private boolean mIsQueued;
+ private long mDelayMillis;
/**
* Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
@@ -106,15 +99,48 @@
*/
void runDelayed(Runnable r, long delayMillis) {
cancel();
- ThreadingDomain.this.postDelayed(r, this, delayMillis);
+ mIsQueued = true;
+ mDelayMillis = delayMillis;
+ ThreadingDomain.this.postDelayed(() -> {
+ mIsQueued = false;
+ mDelayMillis = -2;
+ r.run();
+ }, this, delayMillis);
+ }
+
+ /**
+ * Returns {@code true} if there is an item current queued. This method must be called from
+ * the threading domain's thread.
+ */
+ boolean hasQueued() {
+ assertCurrentThread();
+ return mIsQueued;
+ }
+
+ /**
+ * Returns the delay in milliseconds for the currently queued item. Throws {@link
+ * IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}.
+ * This method must be called from the threading domain's thread.
+ */
+ long getQueuedDelayMillis() {
+ assertCurrentThread();
+ if (!mIsQueued) {
+ throw new IllegalStateException("No item queued");
+ }
+ return mDelayMillis;
}
/**
* Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
+ * This method must be called from the threading domain's thread.
*/
public void cancel() {
assertCurrentThread();
- removeQueuedRunnables(this);
+ if (mIsQueued) {
+ removeQueuedRunnables(this);
+ }
+ mIsQueued = false;
+ mDelayMillis = -1;
}
}
}
diff --git a/services/core/java/com/android/server/location/util/AlarmHelper.java b/services/core/java/com/android/server/location/util/AlarmHelper.java
new file mode 100644
index 0000000..46dbdbf
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/AlarmHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helps manage alarms.
+ */
+public abstract class AlarmHelper {
+
+ /**
+ * Sets a wakeup alarm that will fire after the given delay.
+ */
+ public final void setDelayedAlarm(long delayMs, OnAlarmListener listener,
+ WorkSource workSource) {
+ // helps ensure that we're not wasting system resources by setting alarms in the past/now
+ Preconditions.checkArgument(delayMs > 0);
+ Preconditions.checkArgument(workSource != null);
+ setDelayedAlarmInternal(delayMs, listener, workSource);
+ }
+
+ protected abstract void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+ WorkSource workSource);
+
+ /**
+ * Cancels an alarm.
+ */
+ public abstract void cancel(OnAlarmListener listener);
+}
diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java
index 379b303..d9c73ba 100644
--- a/services/core/java/com/android/server/location/util/Injector.java
+++ b/services/core/java/com/android/server/location/util/Injector.java
@@ -28,6 +28,9 @@
/** Returns a UserInfoHelper. */
UserInfoHelper getUserInfoHelper();
+ /** Returns an AlarmHelper. */
+ AlarmHelper getAlarmHelper();
+
/** Returns an AppOpsHelper. */
AppOpsHelper getAppOpsHelper();
diff --git a/services/core/java/com/android/server/location/util/SystemAlarmHelper.java b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
new file mode 100644
index 0000000..81849794
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.util;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.os.WorkSource;
+
+import com.android.server.FgThread;
+
+import java.util.Objects;
+
+/**
+ * Provides helpers for alarms.
+ */
+public class SystemAlarmHelper extends AlarmHelper {
+
+ private final Context mContext;
+
+ public SystemAlarmHelper(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void setDelayedAlarmInternal(long delayMs, AlarmManager.OnAlarmListener listener,
+ WorkSource workSource) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs,
+ WINDOW_EXACT, 0, listener, FgThread.getHandler(), workSource);
+ }
+
+ @Override
+ public void cancel(AlarmManager.OnAlarmListener listener) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.cancel(listener);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1ae1681..67f218e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -25740,7 +25740,7 @@
// This API is exposed temporarily to only the contacts provider. (b/158688602)
final int callingUid = Binder.getCallingUid();
ProviderInfo contactsProvider = resolveContentProviderInternal(
- ContactsContract.AUTHORITY, 0, UserHandle.USER_SYSTEM);
+ ContactsContract.AUTHORITY, 0, UserHandle.getUserId(callingUid));
if (contactsProvider == null || contactsProvider.applicationInfo == null
|| !UserHandle.isSameApp(contactsProvider.applicationInfo.uid, callingUid)) {
throw new SecurityException(callingUid + " is not allow to call grantImplicitAccess");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6b29129..0a8c8f6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1729,6 +1729,7 @@
}
public void makeInitialized(@UserIdInt int userId) {
+ if (DBG) Slog.d(LOG_TAG, "makeInitialized(" + userId + ")");
checkManageUsersPermission("makeInitialized");
boolean scheduleWriteUser = false;
UserData userData;
@@ -3553,8 +3554,7 @@
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
// intialize it.
- // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
- // callback on SystemService instead.
+ // NOTE: user will be stopped on UserController.finishUserUnlockedCompleted().
Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString());
final IActivityManager am = ActivityManager.getService();
try {
@@ -4965,6 +4965,9 @@
UserData userData = getUserDataNoChecks(userId);
if (userData != null) {
writeUserLP(userData);
+ } else {
+ Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId
+ + ", it was probably removed before handler could handle it");
}
}
}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 524f9ac..eb06bf9 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -36,13 +36,14 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.util.ArrayUtils;
import com.android.server.pm.DumpState;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSettingBase;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import libcore.util.EmptyArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -95,7 +96,8 @@
int uid;
/** Additional GIDs given to apps granted this permission */
- private int[] gids;
+ @NonNull
+ private int[] gids = EmptyArray.INT;
/**
* Flag indicating that {@link #gids} should be adjusted based on the
@@ -132,7 +134,7 @@
public int getUid() {
return uid;
}
- public void setGids(int[] gids, boolean perUser) {
+ public void setGids(@NonNull int[] gids, boolean perUser) {
this.gids = gids;
this.perUser = perUser;
}
@@ -141,18 +143,20 @@
}
public boolean hasGids() {
- return !ArrayUtils.isEmpty(gids);
+ return gids.length != 0;
}
+ @NonNull
public int[] computeGids(int userId) {
if (perUser) {
final int[] userGids = new int[gids.length];
for (int i = 0; i < gids.length; i++) {
- userGids[i] = UserHandle.getUid(userId, gids[i]);
+ final int gid = gids[i];
+ userGids[i] = UserHandle.getUid(userId, gid);
}
return userGids;
} else {
- return gids;
+ return gids.length != 0 ? gids.clone() : gids;
}
}
@@ -206,6 +210,11 @@
return perm != null && (perm.getFlags() & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0;
}
+ public boolean isInstallerExemptIgnored() {
+ return perm != null
+ && (perm.getFlags() & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+ }
+
public boolean isSignature() {
return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
PermissionInfo.PROTECTION_SIGNATURE;
@@ -286,7 +295,8 @@
pendingPermissionInfo.packageName = newPackageName;
}
uid = 0;
- setGids(null, false);
+ gids = EmptyArray.INT;
+ perUser = false;
}
public boolean addToTree(@ProtectionLevel int protectionLevel,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 474ce7c..b293ba6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,12 +24,14 @@
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -53,7 +55,6 @@
import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -152,6 +153,8 @@
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.SoftRestrictedPermissionPolicy;
+import libcore.util.EmptyArray;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -245,6 +248,7 @@
private final SparseArray<ArraySet<String>> mSystemPermissions;
/** Built-in group IDs given to all packages. Read from system configuration files. */
+ @NonNull
private final int[] mGlobalGids;
private final HandlerThread mHandlerThread;
@@ -785,6 +789,10 @@
throw new IllegalArgumentException("Unknown permission: " + permName);
}
+ if (bp.isInstallerExemptIgnored()) {
+ flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+ }
+
final UidPermissionState uidState = getUidState(pkg, userId);
if (uidState == null) {
Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
@@ -1096,7 +1104,8 @@
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
Preconditions.checkArgumentNonNegative(userId, null);
if (UserHandle.getCallingUserId() != userId) {
@@ -1120,16 +1129,16 @@
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
- && !isCallerPrivileged) {
- throw new SecurityException("Querying system whitelist requires "
+ if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) {
+ throw new SecurityException("Querying system or role allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
- throw new SecurityException("Querying upgrade or installer whitelist"
+ throw new SecurityException("Querying upgrade or installer allowlist"
+ " requires being installer on record or "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -1153,6 +1162,9 @@
if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
}
+ if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) {
+ queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ }
ArrayList<String> whitelistedPermissions = null;
@@ -1245,7 +1257,8 @@
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
Preconditions.checkArgument(Integer.bitCount(flags) == 1);
Preconditions.checkArgumentNonNegative(userId, null);
@@ -1271,15 +1284,16 @@
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+ if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0
&& !isCallerPrivileged) {
- throw new SecurityException("Modifying system whitelist requires "
+ throw new SecurityException("Modifying system or role allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
- throw new SecurityException("Modifying upgrade whitelist requires"
+ throw new SecurityException("Modifying upgrade allowlist requires"
+ " being installer on record or "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -1501,7 +1515,7 @@
// normal runtime permissions. For now they apply to all users.
// TODO(zhanghai): We are breaking the behavior above by making all permission state
// per-user. It isn't documented behavior and relatively rarely used anyway.
- if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
+ if (uidState.grantPermission(bp)) {
if (callback != null) {
callback.onInstallPermissionGranted();
}
@@ -1519,18 +1533,14 @@
return;
}
- final int result = uidState.grantPermission(bp);
- switch (result) {
- case PERMISSION_OPERATION_FAILURE: {
- return;
- }
+ if (!uidState.grantPermission(bp)) {
+ return;
+ }
- case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
- if (callback != null) {
- callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
- }
+ if (bp.hasGids()) {
+ if (callback != null) {
+ callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
}
- break;
}
if (bp.isRuntime()) {
@@ -1650,7 +1660,7 @@
// normal runtime permissions. For now they apply to all users.
// TODO(zhanghai): We are breaking the behavior above by making all permission state
// per-user. It isn't documented behavior and relatively rarely used anyway.
- if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
+ if (uidState.revokePermission(bp)) {
if (callback != null) {
mDefaultPermissionCallback.onInstallPermissionRevoked();
}
@@ -1658,12 +1668,7 @@
return;
}
- // Permission is already revoked, no need to do anything.
- if (!uidState.isPermissionGranted(permName)) {
- return;
- }
-
- if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) {
+ if (!uidState.revokePermission(bp)) {
return;
}
@@ -2077,6 +2082,15 @@
return false;
}
+ BasePermission permission = getPermission(permName);
+ if (permission == null) {
+ return false;
+ }
+ if (permission.isHardRestricted()
+ && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+ return false;
+ }
+
final long token = Binder.clearCallingIdentity();
try {
if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
@@ -2504,11 +2518,11 @@
}
}
- @Nullable
+ @NonNull
private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
BasePermission permission = mSettings.getPermission(permissionName);
if (permission == null) {
- return null;
+ return EmptyArray.INT;
}
return permission.computeGids(userId);
}
@@ -2629,8 +2643,6 @@
}
}
- uidState.setGlobalGids(mGlobalGids);
-
ArraySet<String> newImplicitPermissions = new ArraySet<>();
final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
@@ -2765,7 +2777,7 @@
switch (grant) {
case GRANT_INSTALL: {
// Grant an install permission.
- if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
+ if (uidState.grantPermission(bp)) {
changedInstallPermission = true;
}
} break;
@@ -2797,8 +2809,7 @@
if (permissionPolicyInitialized && hardRestricted) {
if (!restrictionExempt) {
if (origPermState != null && origPermState.isGranted()
- && uidState.revokePermission(
- bp) != PERMISSION_OPERATION_FAILURE) {
+ && uidState.revokePermission(bp)) {
wasChanged = true;
}
if (!restrictionApplied) {
@@ -2830,8 +2841,7 @@
|| (!hardRestricted || restrictionExempt)) {
if ((origPermState != null && origPermState.isGranted())
|| upgradedActivityRecognitionPermission != null) {
- if (uidState.grantPermission(bp)
- == PERMISSION_OPERATION_FAILURE) {
+ if (!uidState.grantPermission(bp)) {
wasChanged = true;
}
}
@@ -2850,8 +2860,7 @@
}
if (!uidState.isPermissionGranted(bp.name)
- && uidState.grantPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
+ && uidState.grantPermission(bp)) {
wasChanged = true;
}
@@ -2899,13 +2908,11 @@
} break;
}
} else {
- if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
- // Also drop the permission flags.
- uidState.updatePermissionFlags(bp,
- MASK_PERMISSION_FLAGS_ALL, 0);
- changedInstallPermission = true;
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Un-granting permission " + perm
+ if (DEBUG_PERMISSIONS) {
+ boolean wasGranted = uidState.isPermissionGranted(bp.name);
+ if (wasGranted || bp.isAppOp()) {
+ Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
+ + " permission " + perm
+ " from package " + friendlyName
+ " (protectionLevel=" + bp.getProtectionLevel()
+ " flags=0x"
@@ -2913,20 +2920,9 @@
ps))
+ ")");
}
- } else if (bp.isAppOp()) {
- // Don't print warning for app op permissions, since it is fine for them
- // not to be granted, there is a UI for the user to decide.
- if (DEBUG_PERMISSIONS
- && (packageOfInterest == null
- || packageOfInterest.equals(pkg.getPackageName()))) {
- Slog.i(TAG, "Not granting permission " + perm
- + " to package " + friendlyName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x"
- + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
- ps))
- + ")");
- }
+ }
+ if (uidState.removePermissionState(bp.name)) {
+ changedInstallPermission = true;
}
}
}
@@ -3005,8 +3001,7 @@
if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
&& supportsRuntimePermissions) {
- int revokeResult = ps.revokePermission(bp);
- if (revokeResult != PERMISSION_OPERATION_FAILURE) {
+ if (ps.revokePermission(bp)) {
if (DEBUG_PERMISSIONS) {
Slog.i(TAG, "Revoking runtime permission "
+ permission + " for " + pkgName
@@ -3730,6 +3725,15 @@
}
}
break;
+ case FLAG_PERMISSION_ALLOWLIST_ROLE: {
+ mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ if (permissions != null && permissions.contains(permissionName)) {
+ newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ } else {
+ newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ }
+ }
+ break;
}
}
@@ -3865,14 +3869,9 @@
}
}
- // The package is gone - no need to keep flags for applying policy.
- uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
-
- // Try to revoke as a runtime permission which is per user.
- // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are
- // we only killing the uid when gids changed, instead of any permission change?
- if (uidState.revokePermission(bp)
- == UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
+ // permission change?
+ if (uidState.removePermissionState(bp.name) && bp.hasGids()) {
affectedUserId = userId;
}
}
@@ -3905,17 +3904,14 @@
boolean runtimePermissionChanged = false;
// Prune permissions
- final List<com.android.server.pm.permission.PermissionState> permissionStates =
- uidState.getPermissionStates();
+ final List<PermissionState> permissionStates = uidState.getPermissionStates();
final int permissionStatesSize = permissionStates.size();
for (int i = permissionStatesSize - 1; i >= 0; i--) {
PermissionState permissionState = permissionStates.get(i);
if (!usedPermissions.contains(permissionState.getName())) {
BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
if (bp != null) {
- uidState.revokePermission(bp);
- uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0);
- if (permissionState.isRuntime()) {
+ if (uidState.removePermissionState(bp.name) && permissionState.isRuntime()) {
runtimePermissionChanged = true;
}
}
@@ -4178,11 +4174,7 @@
+ p.getPackageName() + " and user " + userId);
return;
}
- if (uidState.getPermissionState(bp.getName()) != null) {
- uidState.revokePermission(bp);
- uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
- 0);
- }
+ uidState.removePermissionState(bp.name);
}
});
}
@@ -4741,7 +4733,7 @@
Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID " + userId);
return EMPTY_INT_ARRAY;
}
- return uidState.computeGids(userId);
+ return uidState.computeGids(mGlobalGids, userId);
}
private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal {
@@ -4804,7 +4796,7 @@
@UserIdInt int userId) {
return PermissionManagerService.this.getGrantedPermissions(packageName, userId);
}
- @Nullable
+ @NonNull
@Override
public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
return PermissionManagerService.this.getPermissionGids(permissionName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java
index 38264c8..59b204f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionState.java
@@ -17,7 +17,6 @@
package com.android.server.pm.permission;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import com.android.internal.annotations.GuardedBy;
@@ -62,7 +61,7 @@
return mPermission.getName();
}
- @Nullable
+ @NonNull
public int[] computeGids(@UserIdInt int userId) {
return mPermission.computeGids(userId);
}
diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
index 06a7f8d..c73e2f3 100644
--- a/services/core/java/com/android/server/pm/permission/UidPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
@@ -22,35 +22,19 @@
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Permission state for a UID.
- * <p>
- * This class is also responsible for keeping track of the Linux GIDs per
- * user for a package or a shared user. The GIDs are computed as a set of
- * the GIDs for all granted permissions' GIDs on a per user basis.
*/
public final class UidPermissionState {
- /** The permission operation failed. */
- public static final int PERMISSION_OPERATION_FAILURE = -1;
-
- /** The permission operation succeeded and no gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS = 0;
-
- /** The permission operation succeeded and gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
-
- private static final int[] NO_GIDS = {};
-
@NonNull
private final Object mLock = new Object();
@@ -60,11 +44,6 @@
@Nullable
private ArrayMap<String, PermissionState> mPermissions;
- private boolean mPermissionReviewRequired;
-
- @NonNull
- private int[] mGlobalGids = NO_GIDS;
-
public UidPermissionState() {}
public UidPermissionState(@NonNull UidPermissionState other) {
@@ -80,12 +59,6 @@
mPermissions.put(name, new PermissionState(permissionState));
}
}
-
- mPermissionReviewRequired = other.mPermissionReviewRequired;
-
- if (other.mGlobalGids != NO_GIDS) {
- mGlobalGids = other.mGlobalGids.clone();
- }
}
}
@@ -96,8 +69,6 @@
synchronized (mLock) {
mMissing = false;
mPermissions = null;
- mPermissionReviewRequired = false;
- mGlobalGids = NO_GIDS;
invalidateCache();
}
}
@@ -156,8 +127,8 @@
/**
* Gets the state for a permission or null if none.
*
- * @param name the permission name.
- * @return the permission state.
+ * @param name the permission name
+ * @return the permission state
*/
@Nullable
public PermissionState getPermissionState(@NonNull String name) {
@@ -169,6 +140,22 @@
}
}
+ @NonNull
+ private PermissionState getOrCreatePermissionState(@NonNull BasePermission permission) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ final String name = permission.getName();
+ PermissionState permissionState = mPermissions.get(name);
+ if (permissionState == null) {
+ permissionState = new PermissionState(permission);
+ mPermissions.put(name, permissionState);
+ }
+ return permissionState;
+ }
+ }
+
/**
* Get all permission states.
*
@@ -186,19 +173,44 @@
/**
* Put a permission state.
+ *
+ * @param permission the permission
+ * @param granted whether the permission is granted
+ * @param flags the permission flags
*/
- public void putPermissionState(@NonNull BasePermission permission, boolean isGranted,
- int flags) {
+ public void putPermissionState(@NonNull BasePermission permission, boolean granted, int flags) {
synchronized (mLock) {
- ensureNoPermissionState(permission.name);
- PermissionState permissionState = ensurePermissionState(permission);
- if (isGranted) {
+ final String name = permission.getName();
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ } else {
+ mPermissions.remove(name);
+ }
+ final PermissionState permissionState = new PermissionState(permission);
+ if (granted) {
permissionState.grant();
}
permissionState.updateFlags(flags, flags);
- if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- mPermissionReviewRequired = true;
+ mPermissions.put(name, permissionState);
+ }
+ }
+
+ /**
+ * Remove a permission state.
+ *
+ * @param name the permission name
+ * @return whether the permission state changed
+ */
+ public boolean removePermissionState(@NonNull String name) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
}
+ boolean changed = mPermissions.remove(name) != null;
+ if (changed && mPermissions.isEmpty()) {
+ mPermissions = null;
+ }
+ return changed;
}
}
@@ -209,13 +221,8 @@
* @return whether the permission is granted
*/
public boolean isPermissionGranted(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- PermissionState permissionState = mPermissions.get(name);
- return permissionState != null && permissionState.isGranted();
- }
+ final PermissionState permissionState = getPermissionState(name);
+ return permissionState != null && permissionState.isGranted();
}
/**
@@ -246,61 +253,37 @@
/**
* Grant a permission.
*
- * @param permission the permission to grantt
- * @return the operation result, which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
+ * @param permission the permission to grant
+ * @return whether the permission grant state changed
*/
- public int grantPermission(@NonNull BasePermission permission) {
- if (isPermissionGranted(permission.getName())) {
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- PermissionState permissionState = ensurePermissionState(permission);
-
- if (!permissionState.grant()) {
- return PERMISSION_OPERATION_FAILURE;
- }
-
- return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
- : PERMISSION_OPERATION_SUCCESS;
+ public boolean grantPermission(@NonNull BasePermission permission) {
+ PermissionState permissionState = getOrCreatePermissionState(permission);
+ return permissionState.grant();
}
/**
* Revoke a permission.
*
* @param permission the permission to revoke
- * @return the operation result, which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
+ * @return whether the permission grant state changed
*/
- public int revokePermission(@NonNull BasePermission permission) {
+ public boolean revokePermission(@NonNull BasePermission permission) {
final String name = permission.getName();
- if (!isPermissionGranted(name)) {
- return PERMISSION_OPERATION_SUCCESS;
+ final PermissionState permissionState = getPermissionState(name);
+ if (permissionState == null) {
+ return false;
}
-
- PermissionState permissionState;
- synchronized (mLock) {
- permissionState = mPermissions.get(name);
+ final boolean changed = permissionState.revoke();
+ if (changed && permissionState.isDefault()) {
+ removePermissionState(name);
}
-
- if (!permissionState.revoke()) {
- return PERMISSION_OPERATION_FAILURE;
- }
-
- if (permissionState.isDefault()) {
- ensureNoPermissionState(name);
- }
-
- return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
- : PERMISSION_OPERATION_SUCCESS;
+ return changed;
}
/**
* Get the flags for a permission.
*
- * @param name the permission name.
+ * @param name the permission name
* @return the permission flags
*/
public int getPermissionFlags(@NonNull String name) {
@@ -324,76 +307,36 @@
if (flagMask == 0) {
return false;
}
-
- synchronized (mLock) {
- final PermissionState permissionState = ensurePermissionState(permission);
- final int oldFlags = permissionState.getFlags();
-
- final boolean updated = permissionState.updateFlags(flagMask, flagValues);
- if (updated) {
- final int newFlags = permissionState.getFlags();
- if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- mPermissionReviewRequired = true;
- } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
- if (mPermissionReviewRequired && !hasPermissionRequiringReview()) {
- mPermissionReviewRequired = false;
- }
- }
- }
- return updated;
+ final PermissionState permissionState = getOrCreatePermissionState(permission);
+ final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+ if (changed && permissionState.isDefault()) {
+ removePermissionState(permission.name);
}
+ return changed;
}
public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) {
+ if (flagMask == 0) {
+ return false;
+ }
synchronized (mLock) {
if (mPermissions == null) {
return false;
}
- boolean changed = false;
- final int permissionsSize = mPermissions.size();
- for (int i = 0; i < permissionsSize; i++) {
+ boolean anyChanged = false;
+ for (int i = mPermissions.size() - 1; i >= 0; i--) {
final PermissionState permissionState = mPermissions.valueAt(i);
- changed |= permissionState.updateFlags(flagMask, flagValues);
+ final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+ if (changed && permissionState.isDefault()) {
+ mPermissions.removeAt(i);
+ }
+ anyChanged |= changed;
}
- return changed;
- }
- }
-
- @NonNull
- private PermissionState ensurePermissionState(@NonNull BasePermission permission) {
- final String name = permission.getName();
- synchronized (mLock) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
- }
- PermissionState permissionState = mPermissions.get(name);
- if (permissionState == null) {
- permissionState = new PermissionState(permission);
- mPermissions.put(name, permissionState);
- }
- return permissionState;
- }
- }
-
- private void ensureNoPermissionState(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return;
- }
- mPermissions.remove(name);
- if (mPermissions.isEmpty()) {
- mPermissions = null;
- }
+ return anyChanged;
}
}
public boolean isPermissionReviewRequired() {
- return mPermissionReviewRequired;
- }
-
- private boolean hasPermissionRequiringReview() {
synchronized (mLock) {
final int permissionsSize = mPermissions.size();
for (int i = 0; i < permissionsSize; i++) {
@@ -407,81 +350,31 @@
}
/**
- * Gets the global gids, applicable to all users.
- */
- @NonNull
- public int[] getGlobalGids() {
- return mGlobalGids;
- }
-
- /**
- * Sets the global gids, applicable to all users.
- *
- * @param globalGids The global gids.
- */
- public void setGlobalGids(@NonNull int[] globalGids) {
- if (!ArrayUtils.isEmpty(globalGids)) {
- mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
- } else {
- mGlobalGids = NO_GIDS;
- }
- }
-
- /**
* Compute the Linux GIDs from the permissions granted to a user.
*
* @param userId the user ID
* @return the GIDs for the user
*/
@NonNull
- public int[] computeGids(@UserIdInt int userId) {
- int[] gids = mGlobalGids;
-
+ public int[] computeGids(@NonNull int[] globalGids, @UserIdInt int userId) {
synchronized (mLock) {
- if (mPermissions != null) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionState permissionState = mPermissions.valueAt(i);
- if (!permissionState.isGranted()) {
- continue;
- }
- final int[] permGids = permissionState.computeGids(userId);
- if (permGids != NO_GIDS) {
- gids = appendInts(gids, permGids);
- }
+ IntArray gids = IntArray.wrap(globalGids);
+ if (mPermissions == null) {
+ return gids.toArray();
+ }
+ final int permissionsSize = mPermissions.size();
+ for (int i = 0; i < permissionsSize; i++) {
+ PermissionState permissionState = mPermissions.valueAt(i);
+ if (!permissionState.isGranted()) {
+ continue;
+ }
+ final int[] permissionGids = permissionState.computeGids(userId);
+ if (permissionGids.length != 0) {
+ gids.addAll(permissionGids);
}
}
+ return gids.toArray();
}
-
- return gids;
- }
-
- /**
- * Compute the Linux GIDs from the permissions granted to specified users.
- *
- * @param userIds the user IDs
- * @return the GIDs for the user
- */
- @NonNull
- public int[] computeGids(@NonNull int[] userIds) {
- int[] gids = mGlobalGids;
-
- for (final int userId : userIds) {
- final int[] userGids = computeGids(userId);
- gids = appendInts(gids, userGids);
- }
-
- return gids;
- }
-
- // TODO: fix this to use arraycopy and append all ints in one go
- private static int[] appendInts(int[] current, int[] added) {
- if (current != null && added != null) {
- for (int guid : added) {
- current = ArrayUtils.appendInt(current, guid);
- }
- }
- return current;
}
static void invalidateCache() {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 874b910..d1fd0ad 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,6 +56,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.os.TransferPipe;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -1510,6 +1511,23 @@
return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
}
+ /** @hide */
+ public void passThroughShellCommand(String[] args, FileDescriptor fd) {
+ enforceStatusBarOrShell();
+ if (mBar == null) return;
+
+ try (TransferPipe tp = new TransferPipe()) {
+ // Sending the command to the remote, which needs to execute async to avoid blocking
+ // See Binder#dumpAsync() for inspiration
+ tp.setBufferPrefix(" ");
+ mBar.passThroughShellCommand(args, tp.getWriteFd());
+ // Times out after 5s
+ tp.go(fd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Error sending command to IStatusBar", t);
+ }
+ }
+
// ================================================================================
// Can be called from any thread
// ================================================================================
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index a79c19f..6171822 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -46,7 +46,8 @@
@Override
public int onCommand(String cmd) {
if (cmd == null) {
- return handleDefaultCommands(cmd);
+ onHelp();
+ return 1;
}
try {
switch (cmd) {
@@ -74,8 +75,16 @@
return runSendDisableFlag();
case "tracing":
return runTracing();
+ // Handle everything that would be handled in `handleDefaultCommand()` explicitly,
+ // so the default can be to pass all args to StatusBar
+ case "-h":
+ case "help":
+ onHelp();
+ return 0;
+ case "dump":
+ return super.handleDefaultCommands(cmd);
default:
- return handleDefaultCommands(cmd);
+ return runPassArgsToStatusBar();
}
} catch (RemoteException e) {
final PrintWriter pw = getOutPrintWriter();
@@ -187,6 +196,11 @@
return 0;
}
+ private int runPassArgsToStatusBar() {
+ mInterface.passThroughShellCommand(getAllArgs(), getOutFileDescriptor());
+ return 0;
+ }
+
private int runTracing() {
switch (getNextArg()) {
case "start":
@@ -250,6 +264,12 @@
pw.println(" tracing (start | stop)");
pw.println(" Start or stop SystemUI tracing");
pw.println("");
+ pw.println(" NOTE: any command not listed here will be passed through to IStatusBar");
+ pw.println("");
+ pw.println(" Commands implemented in SystemUI:");
+ pw.flush();
+ // Sending null args to systemui will print help
+ mInterface.passThroughShellCommand(new String[] {}, getOutFileDescriptor());
}
/**
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 3406bd9..70ab48b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -84,6 +84,7 @@
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
new ContentObserver(handler) {
+ @Override
public void onChange(boolean selfChange) {
timeDetectorService.handleAutoTimeDetectionChanged();
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index fe0e82e..9c18aad 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -572,7 +572,7 @@
*/
@VisibleForTesting
@Nullable
- public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
+ public synchronized NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
return findLatestValidNetworkSuggestion();
}
@@ -590,7 +590,7 @@
*/
@VisibleForTesting
@Nullable
- public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() {
return mLastNetworkSuggestion.get();
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index b68c54f..9e76bc1 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -16,15 +16,16 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.os.UserHandle;
import java.util.Objects;
@@ -32,7 +33,7 @@
/**
* Holds all configuration values that affect time zone behavior and some associated logic, e.g.
* {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
- * #createCapabilities()}.
+ * #createCapabilitiesAndConfig()}.
*/
public final class ConfigurationInternal {
@@ -106,10 +107,15 @@
return false;
}
- /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
- public TimeZoneCapabilities createCapabilities() {
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
- .setConfiguration(asConfiguration());
+ /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
+ public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig() {
+ return new TimeZoneCapabilitiesAndConfig(asCapabilities(), asConfiguration());
+ }
+
+ @NonNull
+ private TimeZoneCapabilities asCapabilities() {
+ UserHandle userHandle = UserHandle.of(mUserId);
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
boolean allowConfigDateTime = isUserConfigAllowed();
@@ -125,7 +131,7 @@
} else {
configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
}
- builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+ builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
final int configureGeolocationDetectionEnabledCapability;
if (!deviceHasTimeZoneDetection) {
@@ -137,7 +143,8 @@
} else {
configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
}
- builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+ builder.setConfigureGeoDetectionEnabledCapability(
+ configureGeolocationDetectionEnabledCapability);
// The ability to make manual time zone suggestions can also be restricted by policy. With
// the current logic above, this could lead to a situation where a device hardware does not
@@ -151,14 +158,14 @@
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+ builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
return builder.build();
}
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
- public TimeZoneConfiguration asConfiguration() {
- return new TimeZoneConfiguration.Builder(mUserId)
+ private TimeZoneConfiguration asConfiguration() {
+ return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
.build();
@@ -171,10 +178,10 @@
*/
public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
Builder builder = new Builder(this);
- if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+ if (newConfiguration.hasIsAutoDetectionEnabled()) {
builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
}
- if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+ if (newConfiguration.hasIsGeoDetectionEnabled()) {
builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
}
return builder.build();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 6a12b7c..964dbec 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -23,7 +23,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneConfiguration;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -87,6 +87,7 @@
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
new ContentObserver(mHandler) {
+ @Override
public void onChange(boolean selfChange) {
handleConfigChangeOnHandlerThread();
}
@@ -97,6 +98,7 @@
Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
true,
new ContentObserver(mHandler) {
+ @Override
public void onChange(boolean selfChange) {
handleConfigChangeOnHandlerThread();
}
@@ -157,7 +159,7 @@
}
@Override
- public void storeConfiguration(TimeZoneConfiguration configuration) {
+ public void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration) {
Objects.requireNonNull(configuration);
// Avoid writing the auto detection enabled setting for devices that do not support auto
@@ -169,7 +171,6 @@
setAutoDetectionEnabled(autoDetectionEnabled);
if (mGeoDetectionFeatureEnabled) {
- final int userId = configuration.getUserId();
final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 73322a6..6e1f89b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,18 +18,19 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -41,7 +42,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Objects;
/**
@@ -109,10 +109,14 @@
@NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
- @GuardedBy("mConfigurationListeners")
+ /**
+ * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value
+ * is the listener itself.
+ */
+ @GuardedBy("mListeners")
@NonNull
- private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
- new ArrayList<>();
+ private final ArrayMap<IBinder, ITimeZoneDetectorListener> mListeners =
+ new ArrayMap<>();
private static TimeZoneDetectorService create(
@NonNull Context context, @NonNull Handler handler,
@@ -133,20 +137,22 @@
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
- // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+ // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
// the configuration changes for any reason.
mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
}
@Override
@NonNull
- public TimeZoneCapabilities getCapabilities() {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig() {
+ enforceManageTimeZoneDetectorPermission();
int userId = mCallerIdentityInjector.getCallingUserId();
long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
+ ConfigurationInternal configurationInternal =
+ mTimeZoneDetectorStrategy.getConfigurationInternal(userId);
+ return configurationInternal.createCapabilitiesAndConfig();
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -154,37 +160,34 @@
@Override
public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(configuration);
int callingUserId = mCallerIdentityInjector.getCallingUserId();
- if (callingUserId != configuration.getUserId()) {
- return false;
- }
-
long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
+ return mTimeZoneDetectorStrategy.updateConfiguration(callingUserId, configuration);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@Override
- public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public void addListener(@NonNull ITimeZoneDetectorListener listener) {
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(listener);
- synchronized (mConfigurationListeners) {
- if (mConfigurationListeners.contains(listener)) {
+ synchronized (mListeners) {
+ IBinder listenerBinder = listener.asBinder();
+ if (mListeners.containsKey(listenerBinder)) {
return;
}
try {
// Ensure the reference to the listener will be removed if the client process dies.
- listener.asBinder().linkToDeath(this, 0 /* flags */);
+ listenerBinder.linkToDeath(this, 0 /* flags */);
// Only add the listener if we can linkToDeath().
- mConfigurationListeners.add(listener);
+ mListeners.put(listenerBinder, listener);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
}
@@ -192,21 +195,22 @@
}
@Override
- public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public void removeListener(@NonNull ITimeZoneDetectorListener listener) {
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(listener);
- synchronized (mConfigurationListeners) {
+ synchronized (mListeners) {
+ IBinder listenerBinder = listener.asBinder();
boolean removedListener = false;
- if (mConfigurationListeners.remove(listener)) {
+ if (mListeners.remove(listenerBinder) != null) {
// Stop listening for the client process to die.
- listener.asBinder().unlinkToDeath(this, 0 /* flags */);
+ listenerBinder.unlinkToDeath(this, 0 /* flags */);
removedListener = true;
}
if (!removedListener) {
Slog.w(TAG, "Client asked to remove listener=" + listener
+ ", but no listeners were removed."
- + " mConfigurationListeners=" + mConfigurationListeners);
+ + " mListeners=" + mListeners);
}
}
}
@@ -218,19 +222,18 @@
}
/**
- * Called when one of the ITimeZoneConfigurationListener processes dies before calling
- * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}.
+ * Called when one of the ITimeZoneDetectorListener processes dies before calling
+ * {@link #removeListener(ITimeZoneDetectorListener)}.
*/
@Override
public void binderDied(IBinder who) {
- synchronized (mConfigurationListeners) {
+ synchronized (mListeners) {
boolean removedListener = false;
- final int listenerCount = mConfigurationListeners.size();
+ final int listenerCount = mListeners.size();
for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
- ITimeZoneConfigurationListener listener =
- mConfigurationListeners.get(listenerIndex);
- if (listener.asBinder().equals(who)) {
- mConfigurationListeners.remove(listenerIndex);
+ IBinder listenerBinder = mListeners.keyAt(listenerIndex);
+ if (listenerBinder.equals(who)) {
+ mListeners.removeAt(listenerIndex);
removedListener = true;
break;
}
@@ -238,7 +241,7 @@
if (!removedListener) {
Slog.w(TAG, "Notified of binder death for who=" + who
+ ", but did not remove any listeners."
- + " mConfigurationListeners=" + mConfigurationListeners);
+ + " mConfigurationListeners=" + mListeners);
}
}
}
@@ -247,11 +250,10 @@
// Configuration has changed, but each user may have a different view of the configuration.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
- synchronized (mConfigurationListeners) {
- final int listenerCount = mConfigurationListeners.size();
+ synchronized (mListeners) {
+ final int listenerCount = mListeners.size();
for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
- ITimeZoneConfigurationListener listener =
- mConfigurationListeners.get(listenerIndex);
+ ITimeZoneDetectorListener listener = mListeners.valueAt(listenerIndex);
try {
listener.onChange();
} catch (RemoteException e) {
@@ -302,11 +304,10 @@
ipw.flush();
}
- private void enforceManageTimeZoneDetectorConfigurationPermission() {
- // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
+ private void enforceManageTimeZoneDetectorPermission() {
mContext.enforceCallingPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS,
- "manage time and time zone configuration");
+ android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION,
+ "manage time and time zone detection");
}
private void enforceSuggestGeolocationTimeZonePermission() {
@@ -333,7 +334,7 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new TimeZoneDetectorShellCommand(this)).exec(
+ new TimeZoneDetectorShellCommand(this).exec(
this, in, out, err, args, callback, resultReceiver);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f944c56..0b1d6d7 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,9 +17,9 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
/**
@@ -96,7 +96,8 @@
* <p>This method returns {@code true} if the configuration was changed,
* {@code false} otherwise.
*/
- boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
+ boolean updateConfiguration(
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
/**
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 8a42b18..781668b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -15,20 +15,21 @@
*/
package com.android.server.timezonedetector;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.os.Handler;
import android.util.IndentingPrintWriter;
@@ -93,7 +94,7 @@
* All checks about user capabilities must be done by the caller and
* {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
*/
- void storeConfiguration(TimeZoneConfiguration newConfiguration);
+ void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration);
}
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -240,27 +241,26 @@
}
@Override
- public synchronized boolean updateConfiguration(
+ public synchronized boolean updateConfiguration(@UserIdInt int userId,
@NonNull TimeZoneConfiguration requestedConfiguration) {
Objects.requireNonNull(requestedConfiguration);
- int userId = requestedConfiguration.getUserId();
- TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ getConfigurationInternal(userId).createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
- // Create a new configuration builder, and copy across the mutable properties users are
- // able to modify. Other properties are therefore ignored.
final TimeZoneConfiguration newConfiguration =
- capabilities.applyUpdate(requestedConfiguration);
+ capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
if (newConfiguration == null) {
- // The changes could not be made due to
+ // The changes could not be made because the user's capabilities do not allow it.
return false;
}
// Store the configuration / notify as needed. This will cause the mCallback to invoke
// handleConfigChanged() asynchronously.
- mCallback.storeConfiguration(newConfiguration);
+ mCallback.storeConfiguration(userId, newConfiguration);
- TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
String logMsg = "Configuration changed:"
+ " oldConfiguration=" + oldConfiguration
+ ", newConfiguration=" + newConfiguration;
@@ -313,8 +313,10 @@
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
- TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
- if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ getConfigurationInternal(userId).createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ", capabilities=" + capabilities
+ ", timeZoneId=" + timeZoneId
@@ -605,7 +607,7 @@
ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
- ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
+ ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]");
ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
@@ -644,7 +646,7 @@
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
- public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+ public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
return mLatestGeoLocationSuggestion.get();
}
@@ -652,7 +654,7 @@
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@VisibleForTesting
- public static class QualifiedTelephonyTimeZoneSuggestion {
+ public static final class QualifiedTelephonyTimeZoneSuggestion {
@VisibleForTesting
public final TelephonyTimeZoneSuggestion suggestion;
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 39f6c59..f6acc64 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@
private static final String TAG = "UriGrantsManagerService";
// Maximum number of persisted Uri grants a package is allowed
private static final int MAX_PERSISTED_URI_GRANTS = 512;
- private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+ private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
private final Object mLock = new Object();
private final H mH;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f29ad24..2355ed3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1308,6 +1308,8 @@
return;
}
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
@@ -3234,6 +3236,8 @@
mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
waitingToShow = false;
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
// Defer removal of this activity when either a child is animating, or app transition is on
// going. App transition animation might be applied on the parent stack not on the activity,
// but the actual frame buffer is associated with the activity, so we have to keep the
@@ -3245,6 +3249,8 @@
} else if (getDisplayContent().mAppTransition.isTransitionSet()) {
getDisplayContent().mClosingApps.add(this);
delayed = true;
+ } else if (mAtmService.getTransitionController().inTransition()) {
+ delayed = true;
}
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
@@ -4076,6 +4082,11 @@
return mVisible;
}
+ @Override
+ boolean isVisibleRequested() {
+ return mVisibleRequested;
+ }
+
void setVisible(boolean visible) {
if (visible != mVisible) {
mVisible = visible;
@@ -4206,6 +4217,11 @@
transferStartingWindowFromHiddenAboveTokenIfNeeded();
}
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
+ if (!visible && mAtmService.getTransitionController().inTransition()) {
+ return;
+ }
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
// Note that we ignore display frozen since we want the opening / closing transition type
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 783f8e8..3ef383b 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1830,7 +1830,8 @@
for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord s = mStoppingActivities.get(i);
final boolean animating = s.isAnimating(TRANSITION | PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+ || mService.getTransitionController().inTransition(s);
if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
+ " animating=" + animating + " finishing=" + s.finishing);
if (!animating || mService.mShuttingDown) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0402140..c0e31f9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -958,6 +958,10 @@
return mLockTaskController;
}
+ TransitionController getTransitionController() {
+ return mWindowOrganizerController.getTransitionController();
+ }
+
/**
* Return the global configuration used by the process corresponding to the input pid. This is
* usually the global configuration with some overrides specific to that process.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f76108f..0e47ea8 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2214,6 +2214,10 @@
*/
boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent,
@TransitionFlags int flags, boolean forceOverride) {
+ if (mService.mAtmService.getTransitionController().adaptLegacyPrepare(
+ transit, flags, forceOverride)) {
+ return false;
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d "
+ "Callers=%s",
@@ -2255,7 +2259,7 @@
|| transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
}
- private static boolean isKeyguardTransit(int transit) {
+ static boolean isKeyguardTransit(int transit) {
return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE
|| transit == TRANSIT_KEYGUARD_UNOCCLUDE;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1153c46..22b446d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1465,6 +1465,15 @@
// intermediate orientation change, it is more stable to freeze the display.
return false;
}
+ if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
+ // If the activity is executing or has done the lifecycle callback, use normal
+ // rotation animation so the display info can be updated immediately (see
+ // updateDisplayAndOrientation). This prevents a compatibility issue such as
+ // calling setRequestedOrientation in Activity#onCreate and then get display info.
+ // If fixed rotation is applied, the display rotation will still be the old one,
+ // unless the client side gets the rotation again after the adjustments arrive.
+ return false;
+ }
} else if (r != topRunningActivity()) {
// If the transition has not started yet, the activity must be the top.
return false;
@@ -2273,6 +2282,11 @@
}
@Override
+ boolean isVisibleRequested() {
+ return isVisible();
+ }
+
+ @Override
void onAppTransitionDone() {
super.onAppTransitionDone();
mWmService.mWindowsChanged = true;
@@ -4432,6 +4446,7 @@
}
void executeAppTransition() {
+ mAtmService.getTransitionController().setReady();
if (mAppTransition.isTransitionSet()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a236699..22776e0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2372,6 +2372,7 @@
private void initializeChangeTransition(Rect startBounds) {
mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+ mAtmService.getTransitionController().collect(this);
mDisplayContent.mChangingContainers.add(this);
mSurfaceFreezer.freeze(getPendingTransaction(), startBounds);
@@ -4762,6 +4763,16 @@
}
@Override
+ boolean showSurfaceOnCreation() {
+ // Organized tasks handle their own surface visibility
+ final boolean willBeOrganized =
+ mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode())
+ && isRootTask();
+ return !mAtmService.getTransitionController().isShellTransitionsEnabled()
+ || !willBeOrganized;
+ }
+
+ @Override
protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
/**
* Avoid reparenting SurfaceControl of the organized tasks that are always on top, since
@@ -4781,7 +4792,9 @@
// hide it to allow the task organizer to show it when it is properly reparented. We
// skip this for tasks created by the organizer because they can synchronously update
// the leash before new children are added to the task.
- if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) {
+ if (!mAtmService.getTransitionController().isShellTransitionsEnabled()
+ && !mCreatedByOrganizer
+ && mTaskOrganizer != null && !prevHasBeenVisible) {
getSyncTransaction().hide(getSurfaceControl());
commitPendingTransaction();
}
@@ -6362,7 +6375,15 @@
transit = TRANSIT_TASK_OPEN;
}
}
- dc.prepareAppTransition(transit, keepCurTransition);
+ if (mAtmService.getTransitionController().isShellTransitionsEnabled()
+ // TODO(shell-transitions): eventually all transitions.
+ && transit == TRANSIT_TASK_OPEN) {
+ Transition transition =
+ mAtmService.getTransitionController().requestTransition(transit);
+ transition.collect(task);
+ } else {
+ dc.prepareAppTransition(transit, keepCurTransition);
+ }
mStackSupervisor.mNoAnimActivities.remove(r);
}
boolean doShow = true;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index e07c567..8201d10 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -354,7 +354,7 @@
: null;
}
- private boolean isSupportedWindowingMode(int winMode) {
+ boolean isSupportedWindowingMode(int winMode) {
return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
new file mode 100644
index 0000000..fc67cd2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a logical transition.
+ * @see TransitionController
+ */
+class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+ private static final String TAG = "Transition";
+
+ /** The transition has been created and is collecting, but hasn't formally started. */
+ private static final int STATE_COLLECTING = 0;
+
+ /**
+ * The transition has formally started. It is still collecting but will stop once all
+ * participants are ready to animate (finished drawing).
+ */
+ private static final int STATE_STARTED = 1;
+
+ /**
+ * This transition is currently playing its animation and can no longer collect or be changed.
+ */
+ private static final int STATE_PLAYING = 2;
+
+ final @WindowManager.TransitionType int mType;
+ private int mSyncId;
+ private @WindowManager.TransitionFlags int mFlags;
+ private final TransitionController mController;
+ final ArrayMap<WindowContainer, ChangeInfo> mParticipants = new ArrayMap<>();
+ private int mState = STATE_COLLECTING;
+ private boolean mReadyCalled = false;
+
+ Transition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags, TransitionController controller) {
+ mType = type;
+ mFlags = flags;
+ mController = controller;
+ mSyncId = mController.mSyncEngine.startSyncSet(this);
+ }
+
+ /**
+ * Formally starts the transition. Participants can be collected before this is started,
+ * but this won't consider itself ready until started -- even if all the participants have
+ * drawn.
+ */
+ void start() {
+ if (mState >= STATE_STARTED) {
+ Slog.w(TAG, "Transition already started: " + mSyncId);
+ }
+ mState = STATE_STARTED;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
+ mSyncId);
+ if (mReadyCalled) {
+ setReady();
+ }
+ }
+
+ /** Adds wc to set of WindowContainers participating in this transition. */
+ void collect(@NonNull WindowContainer wc) {
+ if (mSyncId < 0) return;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
+ mSyncId, wc);
+ // Add to sync set before checking contains because it may not have added it at other
+ // times (eg. if wc was previously invisible).
+ mController.mSyncEngine.addToSyncSet(mSyncId, wc);
+ if (mParticipants.containsKey(wc)) return;
+ mParticipants.put(wc, new ChangeInfo());
+ }
+
+ /**
+ * Call this when all known changes related to this transition have been applied. Until
+ * all participants have finished drawing, the transition can still collect participants.
+ *
+ * If this is called before the transition is started, it will be deferred until start.
+ */
+ void setReady() {
+ if (mSyncId < 0) return;
+ if (mState < STATE_STARTED) {
+ mReadyCalled = true;
+ return;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Finish collecting in transition %d", mSyncId);
+ mController.mSyncEngine.setReady(mSyncId);
+ mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal();
+ }
+
+ /** The transition has finished animating and is ready to finalize WM state */
+ void finishTransition() {
+ if (mState < STATE_PLAYING) {
+ throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
+ }
+ for (int i = 0; i < mParticipants.size(); ++i) {
+ final ActivityRecord ar = mParticipants.keyAt(i).asActivityRecord();
+ if (ar == null || ar.mVisibleRequested) {
+ continue;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit activity becoming invisible: %s", ar);
+ ar.commitVisibility(false /* visible */, false /* performLayout */);
+ }
+ }
+
+ @Override
+ public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) {
+ if (syncId != mSyncId) {
+ Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
+ return;
+ }
+ mState = STATE_PLAYING;
+ mController.moveToPlaying(this);
+ final TransitionInfo info = calculateTransitionInfo(mType, mParticipants);
+
+ SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
+ int displayId = Display.DEFAULT_DISPLAY;
+ for (WindowContainer container : windowContainersReady) {
+ container.mergeBlastSyncTransaction(mergedTransaction);
+ displayId = container.mDisplayContent.getDisplayId();
+ }
+
+ handleNonAppWindowsInTransition(displayId, mType, mFlags);
+
+ if (mController.getTransitionPlayer() != null) {
+ try {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Calling onTransitionReady: %s", info);
+ mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction);
+ } catch (RemoteException e) {
+ // If there's an exception when trying to send the mergedTransaction to the
+ // client, we should immediately apply it here so the transactions aren't lost.
+ mergedTransaction.apply();
+ }
+ } else {
+ mergedTransaction.apply();
+ }
+ mSyncId = -1;
+ }
+
+ private void handleNonAppWindowsInTransition(int displayId, int transit, int flags) {
+ final DisplayContent dc =
+ mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) {
+ return;
+ }
+ if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+ if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+ Animation anim = mController.mAtm.mWindowManager.mPolicy
+ .createKeyguardWallpaperExit(
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+ if (anim != null) {
+ anim.scaleCurrentDuration(
+ mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
+ dc.mWallpaperController.startWallpaperAnimation(anim);
+ }
+ }
+ }
+ if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+ dc.startKeyguardExitOnNonAppWindows(
+ transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+ mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(transit, 0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("TransitionRecord{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" id=" + mSyncId);
+ sb.append(" type=" + mType);
+ sb.append(" flags=" + mFlags);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private static boolean reportIfNotTop(WindowContainer wc) {
+ // Organized tasks need to be reported anyways because Core won't show() their surfaces
+ // and we can't rely on onTaskAppeared because it isn't in sync.
+ // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
+ return wc.isOrganized();
+ }
+
+ /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
+ private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
+ WindowContainer parent = child;
+ int depth = 0;
+ while (parent != null) {
+ if (parent == ancestor) {
+ return depth;
+ }
+ parent = parent.getParent();
+ ++depth;
+ }
+ return -1;
+ }
+
+ private static @TransitionInfo.TransitionMode int getModeFor(WindowContainer wc) {
+ if (wc.isVisibleRequested()) {
+ final Task t = wc.asTask();
+ if (t != null && t.getHasBeenVisible()) {
+ return TransitionInfo.TRANSIT_SHOW;
+ }
+ return TransitionInfo.TRANSIT_OPEN;
+ }
+ return TransitionInfo.TRANSIT_CLOSE;
+ }
+
+ /**
+ * Under some conditions (eg. all visible targets within a parent container are transitioning
+ * the same way) the transition can be "promoted" to the parent container. This means an
+ * animation can play just on the parent rather than all the individual children.
+ *
+ * @return {@code true} if transition in target can be promoted to its parent.
+ */
+ private static boolean canPromote(
+ WindowContainer target, ArraySet<WindowContainer> topTargets) {
+ final WindowContainer parent = target.getParent();
+ if (parent == null || !parent.canCreateRemoteAnimationTarget()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
+ parent == null ? "no parent" : ("parent can't be target " + parent));
+ return false;
+ }
+ @TransitionInfo.TransitionMode int mode = TransitionInfo.TRANSIT_NONE;
+ // Go through all siblings of this target to see if any of them would prevent
+ // the target from promoting.
+ siblingLoop:
+ for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer sibling = parent.getChildAt(i);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
+ sibling);
+ // Check if any topTargets are the sibling or within it
+ for (int j = topTargets.size() - 1; j >= 0; --j) {
+ final int depth = getChildDepth(topTargets.valueAt(j), sibling);
+ if (depth < 0) continue;
+ if (depth == 0) {
+ final int siblingMode = sibling.isVisibleRequested()
+ ? TransitionInfo.TRANSIT_OPEN : TransitionInfo.TRANSIT_CLOSE;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " sibling is a top target with mode %s",
+ TransitionInfo.modeToString(siblingMode));
+ if (mode == TransitionInfo.TRANSIT_NONE) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " no common mode yet, so set it");
+ mode = siblingMode;
+ } else if (mode != siblingMode) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: common mode mismatch. was %s",
+ TransitionInfo.modeToString(mode));
+ return false;
+ }
+ continue siblingLoop;
+ } else {
+ // Sibling subtree may not be promotable.
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: sibling contains top target %s",
+ topTargets.valueAt(j));
+ return false;
+ }
+ }
+ // No other animations are playing in this sibling
+ if (sibling.isVisibleRequested()) {
+ // Sibling is visible but not animating, so no promote.
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: sibling is visible but not part of transition");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
+ *
+ * @param topTargets set of just the top-most targets in the hierarchy of participants.
+ * @param targets all targets that will be sent to the player.
+ * @return {@code true} if something was promoted.
+ */
+ private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
+ ArrayMap<WindowContainer, ChangeInfo> targets) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---");
+ // Go through each target until we find one that can be promoted.
+ targetLoop:
+ for (WindowContainer targ : topTargets) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ);
+ if (!canPromote(targ, topTargets)) {
+ continue;
+ }
+ final WindowContainer parent = targ.getParent();
+ // No obstructions found to promotion, so promote
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " CAN PROMOTE: promoting to parent %s", parent);
+ final ChangeInfo parentInfo = new ChangeInfo();
+ targets.put(parent, parentInfo);
+ // Go through all children of newly-promoted container and remove them from
+ // the top-targets.
+ for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer child = parent.getChildAt(i);
+ int idx = targets.indexOfKey(child);
+ if (idx >= 0) {
+ if (reportIfNotTop(child)) {
+ targets.valueAt(idx).mParent = parent;
+ parentInfo.addChild(child);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " keep as target %s", child);
+ } else {
+ if (targets.valueAt(idx).mChildren != null) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " merging children in from %s: %s", child,
+ targets.valueAt(idx).mChildren);
+ parentInfo.addChildren(targets.valueAt(idx).mChildren);
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " remove from targets %s", child);
+ targets.removeAt(idx);
+ }
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " remove from topTargets %s", child);
+ topTargets.remove(child);
+ }
+ topTargets.add(parent);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
+ * animation targets to higher level in the window hierarchy if possible.
+ */
+ @VisibleForTesting
+ static TransitionInfo calculateTransitionInfo(
+ int type, Map<WindowContainer, ChangeInfo> participants) {
+ final TransitionInfo out = new TransitionInfo(type);
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Start calculating TransitionInfo based on participants: %s",
+ new ArraySet<>(participants.keySet()));
+
+ final ArraySet<WindowContainer> topTargets = new ArraySet<>();
+ // The final animation targets which cannot promote to higher level anymore.
+ final ArrayMap<WindowContainer, ChangeInfo> targets = new ArrayMap<>();
+
+ final ArrayList<WindowContainer> tmpList = new ArrayList<>();
+
+ // Build initial set of top-level participants by removing any participants that are
+ // children of other participants or are otherwise invalid.
+ for (Map.Entry<WindowContainer, ChangeInfo> entry : participants.entrySet()) {
+ final WindowContainer wc = entry.getKey();
+ // Don't include detached windows.
+ if (!wc.isAttached()) continue;
+
+ final ChangeInfo changeInfo = entry.getValue();
+ WindowContainer parent = wc.getParent();
+ WindowContainer topParent = null;
+ // Keep track of always-report parents in bottom-to-top order
+ tmpList.clear();
+ while (parent != null) {
+ if (participants.containsKey(parent)) {
+ topParent = parent;
+ } else if (reportIfNotTop(parent)) {
+ tmpList.add(parent);
+ }
+ parent = parent.getParent();
+ }
+ if (topParent != null) {
+ // Add always-report parents along the way
+ parent = topParent;
+ for (int i = tmpList.size() - 1; i >= 0; --i) {
+ if (!participants.containsKey(tmpList.get(i))) {
+ final ChangeInfo info = new ChangeInfo();
+ info.mParent = parent;
+ targets.put(tmpList.get(i), info);
+ }
+ parent = tmpList.get(i);
+ }
+ continue;
+ }
+ targets.put(wc, changeInfo);
+ topTargets.add(wc);
+ }
+
+ // Populate children lists
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ if (targets.valueAt(i).mParent != null) {
+ targets.get(targets.valueAt(i).mParent).addChild(targets.keyAt(i));
+ }
+ }
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Initial targets: %s", new ArraySet<>(targets.keySet()));
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets);
+
+ // Combine targets by repeatedly going through the topTargets to see if they can be
+ // promoted until there aren't any promotions possible.
+ while (tryPromote(topTargets, targets)) {
+ // Empty on purpose
+ }
+
+ // Convert all the resolved ChangeInfos into a TransactionInfo object.
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ final WindowContainer target = targets.keyAt(i);
+ final ChangeInfo info = targets.valueAt(i);
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ target.mRemoteToken.toWindowContainerToken(), target.getSurfaceControl());
+ if (info.mParent != null) {
+ change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+ }
+ change.setMode(getModeFor(target));
+ out.addChange(change);
+ }
+
+ return out;
+ }
+
+ static Transition fromBinder(IBinder binder) {
+ return (Transition) binder;
+ }
+
+ @VisibleForTesting
+ static class ChangeInfo {
+ WindowContainer mParent;
+ ArraySet<WindowContainer> mChildren;
+ // TODO(shell-transitions): other tracking like before state and bounds
+ void addChild(@NonNull WindowContainer wc) {
+ if (mChildren == null) {
+ mChildren = new ArraySet<>();
+ }
+ mChildren.add(wc);
+ }
+ void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
+ if (mChildren == null) {
+ mChildren = new ArraySet<>();
+ }
+ mChildren.addAll(wcs);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
new file mode 100644
index 0000000..d102c19
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
+import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles all the aspects of recording and synchronizing transitions.
+ */
+class TransitionController {
+ private static final String TAG = "TransitionController";
+
+ private static final int[] SUPPORTED_LEGACY_TRANSIT_TYPES = {TRANSIT_TASK_OPEN,
+ TRANSIT_TASK_CLOSE, TRANSIT_TASK_TO_FRONT, TRANSIT_TASK_TO_BACK,
+ TRANSIT_TASK_OPEN_BEHIND, TRANSIT_KEYGUARD_GOING_AWAY};
+ static {
+ Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES);
+ }
+
+ final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine();
+ private ITransitionPlayer mTransitionPlayer;
+ private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null;
+ final ActivityTaskManagerService mAtm;
+
+ /** Currently playing transitions. When finished, records are removed from this list. */
+ private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
+
+ /**
+ * The transition currently being constructed (collecting participants).
+ * TODO(shell-transitions): When simultaneous transitions are supported, merge this with
+ * mPlayingTransitions.
+ */
+ private Transition mCollectingTransition = null;
+
+ TransitionController(ActivityTaskManagerService atm) {
+ mAtm = atm;
+ }
+
+ /** @see #createTransition(int, int) */
+ @NonNull
+ Transition createTransition(int type) {
+ return createTransition(type, 0 /* flags */);
+ }
+
+ /**
+ * Creates a transition. It can immediately collect participants.
+ */
+ @NonNull
+ Transition createTransition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ if (mCollectingTransition != null) {
+ throw new IllegalStateException("Simultaneous transitions not supported yet.");
+ }
+ mCollectingTransition = new Transition(type, flags, this);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+ mCollectingTransition);
+ return mCollectingTransition;
+ }
+
+ void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ try {
+ if (mTransitionPlayer != null) {
+ mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
+ mTransitionPlayer = null;
+ }
+ player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
+ mTransitionPlayer = player;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to set transition player");
+ }
+ }
+
+ @Nullable ITransitionPlayer getTransitionPlayer() {
+ return mTransitionPlayer;
+ }
+
+ boolean isShellTransitionsEnabled() {
+ return mTransitionPlayer != null;
+ }
+
+ /** @return {@code true} if a transition is running */
+ boolean inTransition() {
+ // TODO(shell-transitions): eventually properly support multiple
+ return mCollectingTransition != null || !mPlayingTransitions.isEmpty();
+ }
+
+ /** @return {@code true} if wc is in a participant subtree */
+ boolean inTransition(@NonNull WindowContainer wc) {
+ if (mCollectingTransition != null && mCollectingTransition.mParticipants.containsKey(wc)) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mPlayingTransitions.get(i).mParticipants.containsKey(p)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates a transition and asks the TransitionPlayer (Shell) to start it.
+ * @return the created transition. Collection can start immediately.
+ */
+ @NonNull
+ Transition requestTransition(@WindowManager.TransitionType int type) {
+ return requestTransition(type, 0 /* flags */);
+ }
+
+ /** @see #requestTransition */
+ @NonNull
+ Transition requestTransition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ if (mTransitionPlayer == null) {
+ throw new IllegalStateException("Shell Transitions not enabled");
+ }
+ final Transition transition = createTransition(type, flags);
+ try {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Requesting StartTransition: %s", transition);
+ mTransitionPlayer.requestStartTransition(type, transition);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting transition", e);
+ transition.start();
+ }
+ return transition;
+ }
+
+ /**
+ * Temporary adapter that converts the legacy AppTransition's prepareAppTransition call into
+ * a Shell transition request. If shell transitions are enabled, this will take priority in
+ * handling transition types that it supports. All other transitions will be ignored and thus
+ * be handled by the legacy apptransition system. This allows both worlds to live in tandem
+ * during migration.
+ *
+ * @return {@code true} if the transition is handled.
+ */
+ boolean adaptLegacyPrepare(@WindowManager.TransitionType int transit,
+ @WindowManager.TransitionFlags int flags, boolean forceOverride) {
+ if (!isShellTransitionsEnabled()
+ || Arrays.binarySearch(SUPPORTED_LEGACY_TRANSIT_TYPES, transit) < 0) {
+ return false;
+ }
+ if (inTransition()) {
+ if (AppTransition.isKeyguardTransit(transit)) {
+ // TODO(shell-transitions): add to flags
+ } else if (forceOverride) {
+ // TODO(shell-transitions): sort out these flags
+ } else if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
+ // TODO(shell-transitions): record crashing
+ }
+ } else {
+ requestTransition(transit, flags);
+ }
+ return true;
+ }
+
+ /** @see Transition#collect */
+ void collect(@NonNull WindowContainer wc) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.collect(wc);
+ }
+
+ /** @see Transition#setReady */
+ void setReady() {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setReady();
+ }
+
+ /** @see Transition#finishTransition */
+ void finishTransition(@NonNull IBinder token) {
+ final Transition record = Transition.fromBinder(token);
+ if (record == null || !mPlayingTransitions.contains(record)) {
+ Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+ return;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
+ mPlayingTransitions.remove(record);
+ record.finishTransition();
+ }
+
+ void moveToPlaying(Transition transition) {
+ if (transition != mCollectingTransition) {
+ throw new IllegalStateException("Trying to move non-collecting transition to playing");
+ }
+ mCollectingTransition = null;
+ mPlayingTransitions.add(transition);
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2b93080..2ece30d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -417,7 +417,9 @@
void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build());
- getSyncTransaction().show(mSurfaceControl);
+ if (showSurfaceOnCreation()) {
+ getSyncTransaction().show(mSurfaceControl);
+ }
onSurfaceShown(getSyncTransaction());
updateSurfacePositionNonOrganized();
}
@@ -999,6 +1001,21 @@
}
/**
+ * Is this window's surface needed? This is almost like isVisible, except when participating
+ * in a transition, this will reflect the final visibility while isVisible won't change until
+ * the transition is finished.
+ */
+ boolean isVisibleRequested() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (child.isVisibleRequested()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Called when the visibility of a child is asked to change. This is before visibility actually
* changes (eg. a transition animation might play out first).
*/
@@ -2816,6 +2833,13 @@
return false;
}
+ /**
+ * @return {@code true} if this container's surface should be shown when it is created.
+ */
+ boolean showSurfaceOnCreation() {
+ return true;
+ }
+
static WindowContainer fromBinder(IBinder binder) {
return RemoteToken.fromBinder(binder).getContainer();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d434bf9..19cfcb2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -665,6 +665,8 @@
// Whether the system should use BLAST for ViewRootImpl
final boolean mUseBLAST;
+ // Whether to enable BLASTSyncEngine Transaction passing.
+ final boolean mUseBLASTSync = false;
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
@@ -2129,7 +2131,7 @@
win.finishSeamlessRotation(false /* timeout */);
}
- if (win.useBLASTSync()) {
+ if (mUseBLASTSync && win.useBLASTSync()) {
result |= RELAYOUT_RES_BLAST_SYNC;
}
@@ -2460,7 +2462,10 @@
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
+ if (mAtmService.getTransitionController().inTransition(win)) {
+ focusMayChange = true;
+ win.mAnimatingExit = true;
+ } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
focusMayChange = true;
win.mAnimatingExit = true;
} else if (win.isAnimating(TRANSITION | PARENTS)) {
@@ -5127,6 +5132,10 @@
return mUseBLAST;
}
+ public boolean useBLASTSync() {
+ return mUseBLASTSync;
+ }
+
@Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 999181d..f1641cd 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -26,6 +26,8 @@
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -40,6 +42,7 @@
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
import android.window.WindowContainerToken;
@@ -88,21 +91,85 @@
final TaskOrganizerController mTaskOrganizerController;
final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
+ final TransitionController mTransitionController;
+
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
mTaskOrganizerController = new TaskOrganizerController(mService);
mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
+ mTransitionController = new TransitionController(atm);
+ }
+
+ TransitionController getTransitionController() {
+ return mTransitionController;
}
@Override
public void applyTransaction(WindowContainerTransaction t) {
- applySyncTransaction(t, null /*callback*/);
+ applyTransaction(t, null /*callback*/, null /*transition*/);
}
@Override
public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) {
+ return applyTransaction(t, callback, null /*transition*/);
+ }
+
+ @Override
+ public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ @Nullable WindowContainerTransaction t) {
+ enforceStackPermission("startTransition()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ Transition transition = Transition.fromBinder(transitionToken);
+ if (transition == null) {
+ if (type < 0) {
+ throw new IllegalArgumentException("Can't create transition with no type");
+ }
+ transition = mTransitionController.createTransition(type);
+ }
+ transition.start();
+ if (t == null) {
+ t = new WindowContainerTransaction();
+ }
+ applyTransaction(t, null /*callback*/, transition);
+ return transition;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public int finishTransition(@NonNull IBinder transitionToken,
+ @Nullable WindowContainerTransaction t,
+ @Nullable IWindowContainerTransactionCallback callback) {
+ enforceStackPermission("finishTransition()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ int syncId = -1;
+ if (t != null) {
+ syncId = applyTransaction(t, callback, null /*transition*/);
+ }
+ getTransitionController().finishTransition(transitionToken);
+ return syncId;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * @param callback If non-null, this will be a sync-transaction.
+ * @param transition A transition to collect changes into.
+ * @return a BLAST sync-id if this is a non-transition, sync transaction.
+ */
+ private int applyTransaction(@NonNull WindowContainerTransaction t,
+ @Nullable IWindowContainerTransactionCallback callback,
+ @Nullable Transition transition) {
enforceStackPermission("applySyncTransaction()");
int syncId = -1;
if (t == null) {
@@ -152,6 +219,7 @@
}
int containerEffect = applyWindowContainerChange(wc, entry.getValue());
+ if (transition != null) transition.collect(wc);
effects |= containerEffect;
// Lifecycle changes will trigger ensureConfig for everything.
@@ -173,6 +241,12 @@
addToSyncSet(syncId, wc);
}
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+ if (transition != null) {
+ transition.collect(wc);
+ if (hop.isReparent() && hop.getNewParent() != null) {
+ transition.collect(WindowContainer.fromBinder(hop.getNewParent()));
+ }
+ }
}
// Queue-up bounds-change transactions for tasks which are now organized. Do
// this after hierarchy ops so we have the final organized state.
@@ -512,6 +586,19 @@
return true;
}
+ @Override
+ public void registerTransitionPlayer(ITransitionPlayer player) {
+ enforceStackPermission("registerTransitionPlayer()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mTransitionController.registerTransitionPlayer(player);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void enforceStackPermission(String func) {
mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ad28177..8f42b3f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1683,6 +1683,11 @@
|| mControllableInsetProvider.isClientVisible());
}
+ @Override
+ boolean isVisibleRequested() {
+ return isVisible();
+ }
+
/**
* Ensures that all the policy visibility bits are set.
* @return {@code true} if all flags about visiblity are set
@@ -1771,7 +1776,7 @@
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
- return ((!isParentWindowHidden() && atoken.mVisibleRequested)
+ return ((!isParentWindowHidden() && atoken.isVisible())
|| isAnimating(TRANSITION | PARENTS));
}
return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
@@ -5803,7 +5808,9 @@
}
mNotifyBlastOnSurfacePlacement = true;
- return mWinAnimator.finishDrawingLocked(null);
+ mWinAnimator.finishDrawingLocked(null);
+ // We always want to force a traversal after a finish draw for blast sync.
+ return true;
}
private void notifyBlastSyncTransaction() {
@@ -5814,6 +5821,15 @@
return;
}
+ final Task task = getTask();
+ if (task != null) {
+ final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction();
+ if (t != null) {
+ mBLASTSyncTransaction.merge(t);
+ }
+ task.setMainWindowSizeChangeTransaction(null);
+ }
+
// If localSyncId is >0 then we are syncing with children and will
// invoke transaction ready from our own #transactionReady callback
// we just need to signal our side of the sync (setReady). But if we
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 2ace23f..9f653d6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -636,6 +636,11 @@
}
private boolean shouldConsumeMainWindowSizeTransaction() {
+ // If we use BLASTSync we always consume the transaction when finishing
+ // the sync.
+ if (mService.useBLASTSync()) {
+ return false;
+ }
// We only consume the transaction when the client is calling relayout
// because this is the only time we know the frameNumber will be valid
// due to the client renderer being paused. Put otherwise, only when
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index a398961..182fe9a 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.mockingservicestests">
- <uses-sdk android:targetSdkVersion="30" />
+ <uses-sdk android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
index b4e8825..be258dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -16,8 +16,6 @@
package com.android.server.location;
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
@@ -41,7 +39,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.after;
@@ -55,8 +52,6 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
import android.location.ILocationCallback;
import android.location.ILocationListener;
@@ -66,14 +61,11 @@
import android.location.LocationRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Bundle;
-import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -127,8 +119,6 @@
@Mock
private Context mContext;
@Mock
- private AlarmManager mAlarmManager;
- @Mock
private PowerManager mPowerManager;
@Mock
private PowerManager.WakeLock mWakeLock;
@@ -151,7 +141,6 @@
LocalServices.addService(LocationManagerInternal.class, mInternal);
doReturn("android").when(mContext).getPackageName();
- doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
@@ -505,19 +494,8 @@
ILocationListener listener = createMockLocationListener();
LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(5000).build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
- long baseTimeMs = SystemClock.elapsedRealtime();
- ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
- ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
- OnAlarmListener.class);
- verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
- eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
- any(WorkSource.class));
-
- assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
- assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
- listenerCapture.getValue().onAlarm();
-
+ mInjector.getAlarmHelper().incrementAlarmTime(5000);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
@@ -684,13 +662,7 @@
LocationRequest locationRequest = new LocationRequest.Builder(0).build();
mManager.getCurrentLocation(locationRequest, IDENTITY, PERMISSION_FINE, listener);
- ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
- OnAlarmListener.class);
- verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
- eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
- any(WorkSource.class));
- listenerCapture.getValue().onAlarm();
-
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
verify(listener, times(1)).onLocation(isNull());
}
@@ -769,6 +741,40 @@
}
@Test
+ public void testProviderRequest_DelayedRequest() throws Exception {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000).build();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class));
+
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
+ assertThat(mProvider.getRequest().isActive()).isTrue();
+ assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(60000);
+ }
+
+ @Test
+ public void testProviderRequest_SpamRequesting() {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000).build();
+
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.unregisterLocationRequest(listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.unregisterLocationRequest(listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
public void testProviderRequest_BackgroundThrottle() {
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(5).build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
new file mode 100644
index 0000000..0e3e6ef
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class FakeAlarmHelper extends AlarmHelper {
+
+ private static class Alarm {
+ public long delayMs;
+ public final OnAlarmListener listener;
+
+ Alarm(long delayMs, OnAlarmListener listener) {
+ this.delayMs = delayMs;
+ this.listener = listener;
+ }
+ }
+
+ private final ArrayList<Alarm> mAlarms = new ArrayList<>();
+
+ @Override
+ public void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+ WorkSource workSource) {
+ mAlarms.add(new Alarm(delayMs, listener));
+ }
+
+ @Override
+ public void cancel(OnAlarmListener listener) {
+ mAlarms.removeIf(alarm -> alarm.listener == listener);
+ }
+
+ public void incrementAlarmTime(long incrementMs) {
+ Iterator<Alarm> it = mAlarms.iterator();
+ while (it.hasNext()) {
+ Alarm alarm = it.next();
+ alarm.delayMs -= incrementMs;
+ if (alarm.delayMs <= 0) {
+ it.remove();
+ alarm.listener.onAlarm();
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
index 1867be0..69f7376 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
@@ -21,6 +21,7 @@
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
+ private final FakeAlarmHelper mAlarmHelper;
private final FakeAppOpsHelper mAppOpsHelper;
private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
private final FakeSettingsHelper mSettingsHelper;
@@ -33,6 +34,7 @@
public TestInjector() {
mUserInfoHelper = new FakeUserInfoHelper();
+ mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
@@ -50,6 +52,11 @@
}
@Override
+ public FakeAlarmHelper getAlarmHelper() {
+ return mAlarmHelper;
+ }
+
+ @Override
public FakeAppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index 631b8d3..9ee9259 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -15,17 +15,22 @@
*/
package com.android.server.location.timezone;
+import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
import static com.android.server.location.timezone.TestSupport.USER1_ID;
import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -39,6 +44,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.IndentingPrintWriter;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import com.android.server.timezonedetector.TestState;
@@ -65,10 +71,12 @@
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/Paris"));
private static final LocationTimeZoneEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_UNCERTAIN, null);
+ private static final LocationTimeZoneEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
+ createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_PERMANENT_FAILURE, null);
private TestThreadingDomain mTestThreadingDomain;
private TestCallback mTestCallback;
- private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider;
+ private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
@Before
public void setUp() {
@@ -77,276 +85,446 @@
// will never get a chance to execute.
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
- mTestLocationTimeZoneProvider =
+ mTestPrimaryLocationTimeZoneProvider =
new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
}
@Test
public void initialState_enabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
+ .plus(testEnvironment.getProviderInitializationTimeoutFuzz());
+
+ // Initialize. After initialization the provider must be initialized and should be
+ // enabled.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertInitialized();
+ mTestPrimaryLocationTimeZoneProvider.assertInitialized();
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void initialState_disabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize. After initialization the provider must be initialized but should not be
+ // enabled.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertInitialized();
+ mTestPrimaryLocationTimeZoneProvider.assertInitialized();
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate time passing with no event being received.
+ // Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ // The primary should have reported uncertainty, which should trigger the controller to
+ // start the uncertainty timeout.
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Finally, the uncertainty timeout should cause the controller to make an uncertain
+ // suggestion.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- mTestThreadingDomain.assertQueueEmpty();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void enabled_uncertaintySuggestionCancelledIfEventReceived() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void enabled_eventReceivedBeforeInitializationTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void enabled_repeatedCertainty() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate time passing with no provider event being received from the primary.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_repeatedPrimaryCertainty() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made and ensure the primary is considered initialized.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate an uncertain event being received from the primary provider. This should not
+ // cause a suggestion to be made straight away, but the uncertainty timeout should be
+ // started.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
+ // suggestion should be made.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void enabled_briefUncertaintyTriggersNoSuggestion() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Uncertainty should cause a suggestion to (only) be queued.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
+ // timeout should be started.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(testEnvironment.getUncertaintyDelay());
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
- // And a third event should cause yet another suggestion and for the queued item to be
- // removed.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // And a success event from the primary provider should cause the controller to make another
+ // suggestion, the uncertainty timeout should be cancelled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void configChanges_enableAndDisable() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void configChanges_disableWithPreviousSuggestion() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void configChanges_enableAndDisableWithPreviousSuggestion() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Now signal a config change so that geo detection is enabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a success event being received from the primary provider.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate the user disabling the provider.
- testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
-
+ // Now signal a config change so that geo detection is disabled.
// Because there had been a previous suggestion, the controller should withdraw it
// immediately to let the downstream components know that the provider can no longer be sure
// of the time zone.
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
- mTestCallback.assertSuggestionMadeAndCommit(null);
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void configChanges_userSwitch_enabledToEnabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- // There should be a runnable scheduled to suggest uncertainty if no event is received.
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- Duration expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Have the provider suggest a time zone.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate the primary provider suggesting a time zone.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
// Receiving a "success" provider event should cause a suggestion to be made synchronously,
// and also clear the scheduled uncertainty suggestion.
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate the user change (but geo detection still enabled).
testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED);
// We expect the provider to end up in PROVIDER_STATE_ENABLED, but it should have been
// disabled when the user changed.
- // The controller should schedule a runnable to make a suggestion if the provider doesn't
- // send a success event.
- int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
- mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
- mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
- expectedTimeout = expectedProviderInitializationTimeout();
- mTestThreadingDomain.assertSingleDelayedQueueItem(expectedTimeout);
+ int[] expectedStateTransitions =
+ { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED_INITIALIZING };
+ mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void primaryPermFailure_disableAndEnable() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate no event being received, and time passing.
- mTestThreadingDomain.executeNext();
+ // Simulate a failure location event being received from the primary provider. This should
+ // cause an uncertain suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is disabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is enabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ private static void assertUncertaintyTimeoutSet(
+ LocationTimeZoneProviderController.Environment environment,
+ LocationTimeZoneProviderController controller) {
+ assertTrue(controller.isUncertaintyTimeoutSet());
+ assertEquals(environment.getUncertaintyDelay().toMillis(),
+ controller.getUncertaintyTimeoutDelayMillis());
}
private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId,
@@ -362,16 +540,15 @@
}
- private Duration expectedProviderInitializationTimeout() {
- return TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT
- .plus(TestEnvironment.PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
- }
-
private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
+ // These timeouts are set deliberately so that:
+ // (initialization timeout * 2) < uncertainty delay
+ //
+ // That makes the order of initialization timeout Vs uncertainty delay deterministic.
static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
- private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(3);
+ private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15);
private final LocationTimeZoneProviderController mController;
private ConfigurationInternal mConfigurationInternal;
@@ -491,38 +668,57 @@
assertTrue(mInitialized);
}
- void assertIsDisabled() {
- // Disabled providers don't hold config.
- assertConfig(null);
- assertIsEnabledAndCommit(false);
- }
-
- /**
- * Asserts the provider's config matches the expected, and the current state is set
- * accordingly. Commits the latest changes to the state.
- */
- void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
- assertConfig(expectedConfig);
-
- boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior();
- assertIsEnabledAndCommit(expectIsEnabled);
- }
-
- private void assertIsEnabledAndCommit(boolean enabled) {
- ProviderState currentState = mCurrentState.get();
- if (enabled) {
- assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
- } else {
- assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
- }
+ public void assertIsPermFailedAndCommit() {
+ // A failed provider doesn't hold config.
+ assertStateEnumAndConfig(PROVIDER_STATE_PERM_FAILED, null /* config */);
mTestProviderState.commitLatest();
}
- void assertConfig(@NonNull ConfigurationInternal expectedConfig) {
+ void assertIsDisabledAndCommit() {
+ // A disabled provider doesn't hold config.
+ assertStateEnumAndConfig(PROVIDER_STATE_DISABLED, null /* config */);
+ mTestProviderState.commitLatest();
+ }
+
+ /**
+ * Asserts the provider's state enum and config matches the expected.
+ * Commits the latest changes to the state.
+ */
+ void assertStateEnumAndConfigAndCommit(
+ @ProviderStateEnum int expectedStateEnum,
+ @Nullable ConfigurationInternal expectedConfig) {
+ assertStateEnumAndConfig(expectedStateEnum, expectedConfig);
+ mTestProviderState.commitLatest();
+ }
+
+ /**
+ * Asserts the provider's state enum and config matches the expected.
+ * Does not commit any state changes.
+ */
+ void assertStateEnumAndConfig(
+ @ProviderStateEnum int expectedStateEnum,
+ @Nullable ConfigurationInternal expectedConfig) {
+ ProviderState currentState = mCurrentState.get();
+ assertEquals(expectedStateEnum, currentState.stateEnum);
+
+ // If and only if the controller is initializing, the initialization timeout must be
+ // set.
+ assertEquals(expectedStateEnum == PROVIDER_STATE_ENABLED_INITIALIZING,
+ isInitializationTimeoutSet());
+
+ assertConfig(expectedConfig);
+ }
+
+ private void assertConfig(@Nullable ConfigurationInternal expectedConfig) {
ProviderState currentState = mCurrentState.get();
assertEquals(expectedConfig, currentState.currentUserConfiguration);
}
+ void assertInitializationTimeoutSet(Duration expectedTimeout) {
+ assertTrue(isInitializationTimeoutSet());
+ assertEquals(expectedTimeout, getInitializationTimeoutDelay());
+ }
+
void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) {
handleLocationTimeZoneEvent(event);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
index cbaf0f3..4d6775d 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
@@ -115,28 +115,7 @@
}
@Test
- public void singleRunnableHandler_runSynchronously() throws Exception {
- ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
- SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
-
- AtomicBoolean testPassed = new AtomicBoolean(false);
- // Calls to SingleRunnableQueue must be made on the handler thread it is associated with,
- // so this uses runWithScissors() to block until the lambda has completed.
- mTestHandler.runWithScissors(() -> {
- Thread testThread = Thread.currentThread();
- CountDownLatch latch = new CountDownLatch(1);
- singleRunnableQueue.runSynchronously(() -> {
- assertSame(Thread.currentThread(), testThread);
- latch.countDown();
- });
- assertTrue(awaitWithRuntimeException(latch, 60, TimeUnit.SECONDS));
- testPassed.set(true);
- }, TimeUnit.SECONDS.toMillis(60));
- assertTrue(testPassed.get());
- }
-
- @Test
- public void singleRunnableHandler_runDelayed() throws Exception {
+ public void singleRunnableQueue_runDelayed() throws Exception {
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
index 5542db0..5403a65 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -16,7 +16,7 @@
package com.android.server.location.timezone;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
@@ -76,15 +76,16 @@
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
- provider.enable(config, arbitraryInitializationTimeout);
+ Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ provider.enable(config, arbitraryInitializationTimeout, arbitraryInitializationTimeoutFuzz);
- // The StubbedProvider should enters enabled state, but immediately schedule a runnable to
- // switch to perm failure.
+ // The NullProvider should enter the enabled state, but have schedule an immediate runnable
+ // to switch to perm failure.
ProviderState currentState = provider.getCurrentState();
assertSame(provider, currentState.provider);
- assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
+ assertEquals(PROVIDER_STATE_ENABLED_INITIALIZING, currentState.stateEnum);
assertEquals(config, currentState.currentUserConfiguration);
- mTestThreadingDomain.assertSingleImmediateQueueItem();
+ mTestThreadingDomain.assertNextQueueItemIsImmediate();
// Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
// controller.
mTestController.assertProviderChangeNotTriggered();
@@ -116,6 +117,18 @@
// Not needed for provider testing.
}
+ @Override
+ boolean isUncertaintyTimeoutSet() {
+ // Not needed for provider testing.
+ return false;
+ }
+
+ @Override
+ long getUncertaintyTimeoutDelayMillis() {
+ // Not needed for provider testing.
+ return 0;
+ }
+
void onProviderStateChange(ProviderState providerState) {
this.mProviderState.set(providerState);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
index def919e..7359abd 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -16,13 +16,15 @@
package com.android.server.location.timezone;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.annotation.Nullable;
import java.time.Duration;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -33,6 +35,10 @@
class TestThreadingDomain extends ThreadingDomain {
static class QueuedRunnable {
+
+ static final Comparator<? super QueuedRunnable> COMPARATOR =
+ (o1, o2) -> (int) (o1.executionTimeMillis - o2.executionTimeMillis);
+
@NonNull public final Runnable runnable;
@Nullable public final Object token;
public final long executionTimeMillis;
@@ -55,7 +61,7 @@
}
private long mCurrentTimeMillis;
- private LinkedList<QueuedRunnable> mQueue = new LinkedList<>();
+ private ArrayList<QueuedRunnable> mQueue = new ArrayList<>();
TestThreadingDomain() {
// Pick an arbitrary time.
@@ -69,22 +75,23 @@
@Override
void post(Runnable r) {
- mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis));
+ postDelayed(r, null, 0);
}
@Override
void postDelayed(Runnable r, long delayMillis) {
- mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis));
+ postDelayed(r, null, delayMillis);
}
@Override
void postDelayed(Runnable r, Object token, long delayMillis) {
mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis));
+ mQueue.sort(QueuedRunnable.COMPARATOR);
}
@Override
void removeQueuedRunnables(Object token) {
- mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token));
+ mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token);
}
void assertSingleDelayedQueueItem(Duration expectedDelay) {
@@ -114,14 +121,14 @@
}
long getNextQueueItemDelayMillis() {
- assertQueueLength(1);
- return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis;
+ assertFalse(mQueue.isEmpty());
+ return mQueue.get(0).executionTimeMillis - mCurrentTimeMillis;
}
void executeNext() {
- assertQueueLength(1);
+ assertFalse(mQueue.isEmpty());
+ QueuedRunnable queued = mQueue.remove(0);
- QueuedRunnable queued = mQueue.removeFirst();
mCurrentTimeMillis = queued.executionTimeMillis;
queued.runnable.run();
}
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 d7ed96f..54b5bee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -16,22 +16,23 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import org.junit.Test;
/**
- * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
- * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilitiesAndConfig}.
*/
public class ConfigurationInternalTest {
@@ -59,13 +60,20 @@
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_APPLICABLE,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
@@ -77,13 +85,20 @@
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
@@ -106,13 +121,20 @@
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
@@ -124,13 +146,20 @@
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
@@ -153,13 +182,19 @@
assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
@@ -170,13 +205,19 @@
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 4ef2082..bad380a 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -22,10 +22,11 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
@@ -67,20 +68,25 @@
}
@Override
- public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
+ public boolean updateConfiguration(
+ @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) {
assertNotNull(mConfigurationInternal);
assertNotNull(requestedChanges);
// Simulate the real strategy's behavior: the new configuration will be updated to be the
// old configuration merged with the new if the user has the capability to up the settings.
// Then, if the configuration changed, the change listener is invoked.
- TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
- TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ mConfigurationInternal.createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration newConfiguration =
+ capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
- if (!newConfiguration.equals(capabilities.getConfiguration())) {
+ if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
// Note: Unlike the real strategy, the listeners is invoked synchronously.
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 27b04b6..cb27657 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -31,10 +31,10 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.HandlerThread;
@@ -91,85 +91,85 @@
}
@Test(expected = SecurityException.class)
- public void testGetCapabilities_withoutPermission() {
+ public void testGetCapabilitiesAndConfig_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
- mTimeZoneDetectorService.getCapabilities();
+ mTimeZoneDetectorService.getCapabilitiesAndConfig();
fail();
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
- public void testGetCapabilities() {
+ public void testGetCapabilitiesAndConfig() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
- assertEquals(configuration.createCapabilities(),
- mTimeZoneDetectorService.getCapabilities());
+ assertEquals(configuration.createCapabilitiesAndConfig(),
+ mTimeZoneDetectorService.getCapabilitiesAndConfig());
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
@Test(expected = SecurityException.class)
- public void testAddConfigurationListener_withoutPermission() {
+ public void testAddListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
try {
- mTimeZoneDetectorService.addConfigurationListener(mockListener);
+ mTimeZoneDetectorService.addListener(mockListener);
fail();
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test(expected = SecurityException.class)
- public void testRemoveConfigurationListener_withoutPermission() {
+ public void testRemoveListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
try {
- mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+ mTimeZoneDetectorService.removeListener(mockListener);
fail("Expected a SecurityException");
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
- public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
+ public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
when(mockListener.asBinder()).thenReturn(mockListenerBinder);
- mTimeZoneDetectorService.addConfigurationListener(mockListener);
+ mTimeZoneDetectorService.addListener(mockListener);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).linkToDeath(any(), anyInt());
@@ -186,7 +186,7 @@
mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -200,10 +200,10 @@
// Now remove the listener, change the config again, and verify the listener is not
// called.
- mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+ mTimeZoneDetectorService.removeListener(mockListener);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
@@ -219,7 +219,7 @@
mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener, never()).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -354,7 +354,7 @@
}
private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
- return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
}
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 1cdf193..296aa73 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -39,11 +39,11 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -186,26 +186,27 @@
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Set the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// Nothing should have happened: it was initialized in this state.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
@@ -216,20 +217,22 @@
Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Try to update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -240,13 +243,15 @@
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -389,7 +394,8 @@
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting on should cause the device setting to be set.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// When time zone detection is already enabled the suggestion (if it scores highly
// enough) should be set immediately.
@@ -406,7 +412,8 @@
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting should off should do nothing.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Assert internal service state.
@@ -588,18 +595,20 @@
// Toggling time zone detection should set the device time zone only if the current setting
// value is different from the most recent telephony suggestion.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ .simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Simulate a user turning auto detection off, a new suggestion being made while auto
// detection is off, and the user turning it on again.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
.verifyTimeZoneNotChanged();
// Latest suggestion should be used.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(newYorkSuggestion);
}
@@ -784,7 +793,7 @@
assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
// Turn off geo detection and verify the latest suggestion is cleared.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_GEO_DETECTION_DISABLED, true)
.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Assert internal service state.
@@ -824,19 +833,21 @@
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
// disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
// Changing the detection to enable geo detection won't cause the device tz setting to
// change because the geo suggestion is empty.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
.verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
@@ -898,7 +909,7 @@
private static TimeZoneConfiguration createConfig(
@Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
- TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+ TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder();
if (autoDetection != null) {
builder.setAutoDetectionEnabled(autoDetection);
}
@@ -957,9 +968,10 @@
}
@Override
- public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+ public void storeConfiguration(
+ @UserIdInt int userId, TimeZoneConfiguration newConfiguration) {
ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
- if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+ if (userId != oldConfiguration.getUserId()) {
fail("FakeCallback does not support multiple users");
}
@@ -1014,9 +1026,9 @@
* the return value.
*/
Script simulateUpdateConfiguration(
- TimeZoneConfiguration configuration, boolean expectedResult) {
+ int userId, TimeZoneConfiguration configuration, boolean expectedResult) {
assertEquals(expectedResult,
- mTimeZoneDetectorStrategy.updateConfiguration(configuration));
+ mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 2c64526..0c6d638 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -84,6 +84,7 @@
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -703,6 +704,7 @@
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
}
+ @Ignore
@Test
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 633d216..4a90466 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1349,6 +1349,27 @@
}
@Test
+ public void testNoFixedRotationOnResumedScheduledApp() {
+ unblockDisplayRotation(mDisplayContent);
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ app.setVisible(false);
+ app.setState(Task.ActivityState.RESUMED, "test");
+ mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
+ false /* alwaysKeepCurrent */);
+ mDisplayContent.mOpeningApps.add(app);
+ final int newOrientation = getRotatedOrientation(mDisplayContent);
+ app.setRequestedOrientation(newOrientation);
+
+ // The condition should reject using fixed rotation because the resumed client in real case
+ // might get display info immediately. And the fixed rotation adjustments haven't arrived
+ // client side so the info may be inconsistent with the requested orientation.
+ verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
+ eq(true) /* checkOpening */);
+ assertFalse(app.isFixedRotationTransforming());
+ assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
+ }
+
+ @Test
public void testRecentsNotRotatingWithFixedRotation() {
unblockDisplayRotation(mDisplayContent);
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
new file mode 100644
index 0000000..ce22205
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.window.ITaskOrganizer;
+import android.window.TransitionInfo;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:TransitionRecordTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TransitionTests extends WindowTestsBase {
+
+ @Test
+ public void testCreateInfo_NewTask() {
+ final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ newTask.setHasBeenVisible(true);
+ oldTask.setHasBeenVisible(false);
+ final ActivityRecord closing = createActivityRecordInTask(oldTask);
+ final ActivityRecord opening = createActivityRecordInTask(newTask);
+ closing.setVisible(true);
+ closing.mVisibleRequested = false;
+ opening.setVisible(false);
+ opening.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check basic both tasks participating
+ participants.put(oldTask, new Transition.ChangeInfo());
+ participants.put(newTask, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+
+ // Check that children are pruned
+ participants.put(opening, new Transition.ChangeInfo());
+ participants.put(closing, new Transition.ChangeInfo());
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check combined prune and promote
+ participants.remove(newTask);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check multi promote
+ participants.remove(oldTask);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+ }
+
+ @Test
+ public void testCreateInfo_NestedTasks() {
+ final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task newNestedTask = createTaskInStack(newTask, 0);
+ final Task newNestedTask2 = createTaskInStack(newTask, 0);
+ final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ newTask.setHasBeenVisible(true);
+ oldTask.setHasBeenVisible(false);
+ final ActivityRecord closing = createActivityRecordInTask(oldTask);
+ final ActivityRecord opening = createActivityRecordInTask(newNestedTask);
+ final ActivityRecord opening2 = createActivityRecordInTask(newNestedTask2);
+ closing.setVisible(true);
+ closing.mVisibleRequested = false;
+ opening.setVisible(false);
+ opening.mVisibleRequested = true;
+ opening2.setVisible(false);
+ opening2.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check full promotion from leaf
+ participants.put(oldTask, new Transition.ChangeInfo());
+ participants.put(opening, new Transition.ChangeInfo());
+ participants.put(opening2, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check that unchanging but visible descendant of sibling prevents promotion
+ participants.remove(opening2);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+ }
+
+ @Test
+ public void testCreateInfo_DisplayArea() {
+ final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task showNestedTask = createTaskInStack(showTask, 0);
+ final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final DisplayArea tda = showTask.getDisplayArea();
+ showTask.setHasBeenVisible(true);
+ showTask2.setHasBeenVisible(true);
+ final ActivityRecord showing = createActivityRecordInTask(showNestedTask);
+ final ActivityRecord showing2 = createActivityRecordInTask(showTask2);
+ showing.setVisible(false);
+ showing.mVisibleRequested = true;
+ showing2.setVisible(false);
+ showing2.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check promotion to DisplayArea
+ participants.put(showing, new Transition.ChangeInfo());
+ participants.put(showing2, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(1, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+ assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+
+ ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
+ // Check that organized tasks get reported even if not top
+ showTask.mTaskOrganizer = mockOrg;
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
+ // Even if DisplayArea explicitly participating
+ participants.put(tda, new Transition.ChangeInfo());
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 986807e..6237be0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -265,6 +265,11 @@
return activity;
}
+ /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
+ static ActivityRecord createActivityRecordInTask(Task task) {
+ return createActivityRecordInTask(task.getDisplayContent(), task);
+ }
+
static ActivityRecord createTestActivityRecord(DisplayContent dc) {
final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
postCreateActivitySetup(activity, dc);
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 4a3561b..866e171 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -49,12 +49,11 @@
public final class PhoneAccount implements Parcelable {
/**
- * String extra which determines the order in which {@link PhoneAccount}s are sorted
+ * Integer extra which determines the order in which {@link PhoneAccount}s are sorted
*
* This is an extras key set via {@link Builder#setExtras} which determines the order in which
* {@link PhoneAccount}s from the same {@link ConnectionService} are sorted. The accounts
- * are sorted by this key via standard lexicographical order, (as implemented in
- * {@link String#compareTo}), and this ordering is used to
+ * are sorted in ascending order by this key, and this ordering is used to
* determine priority when a call can be placed via multiple accounts.
*
* When multiple {@link PhoneAccount}s are supplied with the same sort order key, no ordering is
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 72a739c..69caccb 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -35,16 +35,12 @@
method public int getTimeoutSeconds();
method public boolean isEnabled();
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
- field public static final int ERROR_FDN_CHECK_FAILURE = 2; // 0x2
- field public static final int ERROR_NOT_SUPPORTED = 3; // 0x3
- field public static final int ERROR_UNKNOWN = 1; // 0x1
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
field public static final int REASON_BUSY = 1; // 0x1
field public static final int REASON_NOT_REACHABLE = 3; // 0x3
field public static final int REASON_NO_REPLY = 2; // 0x2
field public static final int REASON_UNCONDITIONAL = 0; // 0x0
- field public static final int SUCCESS = 0; // 0x0
}
public final class CallQuality implements android.os.Parcelable {
@@ -755,7 +751,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingStatus(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
@@ -854,6 +850,10 @@
public static interface TelephonyManager.CallForwardingInfoCallback {
method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
}
public final class UiccAccessRule implements android.os.Parcelable {
diff --git a/telephony/java/android/telephony/CallForwardingInfo.java b/telephony/java/android/telephony/CallForwardingInfo.java
index 2106f7f..6ae6d00 100644
--- a/telephony/java/android/telephony/CallForwardingInfo.java
+++ b/telephony/java/android/telephony/CallForwardingInfo.java
@@ -39,27 +39,6 @@
private static final String TAG = "CallForwardingInfo";
/**
- * Indicates that the operation was successful.
- */
- public static final int SUCCESS = 0;
-
- /**
- * Indicates that setting or retrieving the call forwarding info failed with an unknown error.
- */
- public static final int ERROR_UNKNOWN = 1;
-
- /**
- * Indicates that call forwarding is not enabled because the recipient is not on a
- * Fixed Dialing Number (FDN) list.
- */
- public static final int ERROR_FDN_CHECK_FAILURE = 2;
-
- /**
- * Indicates that call forwarding is not supported on the network at this time.
- */
- public static final int ERROR_NOT_SUPPORTED = 3;
-
- /**
* Indicates that call forwarding reason is "unconditional".
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
@@ -104,19 +83,6 @@
public static final int REASON_ALL_CONDITIONAL = 5;
/**
- * Call forwarding errors
- * @hide
- */
- @IntDef(prefix = { "ERROR_" }, value = {
- ERROR_UNKNOWN,
- ERROR_NOT_SUPPORTED,
- ERROR_FDN_CHECK_FAILURE
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CallForwardingError{
- }
-
- /**
* Call forwarding reason types
* @hide
*/
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7a0e1cd..c60b514 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -85,6 +85,8 @@
import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsMmTelFeature;
+import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -92,6 +94,7 @@
import android.util.Log;
import android.util.Pair;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CellNetworkScanResult;
@@ -7381,6 +7384,80 @@
}
/**
+ * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id and MMTel
+ * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
+ * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsMmTelFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsMmTelFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsMmTelFeature getImsMmTelFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getMmTelFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsMmTelFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
+ * feature for emergency calling or {@link null} if the service is not available. If an
+ * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+ * listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getRcsFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Unregister a IImsServiceFeatureCallback previously associated with an ImsFeature through
+ * {@link #getImsMmTelFeatureAndListen(int, IImsServiceFeatureCallback)} or
+ * {@link #getImsRcsFeatureAndListen(int, IImsServiceFeatureCallback)}.
+ * @param slotIndex The SIM slot associated with the callback.
+ * @param featureType The {@link android.telephony.ims.feature.ImsFeature.FeatureType}
+ * associated with the callback.
+ * @param callback The callback to be unregistered.
+ * @hide
+ */
+ public void unregisterImsFeatureCallback(int slotIndex, int featureType,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.unregisterImsFeatureCallback(slotIndex, featureType, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "unregisterImsFeatureCallback, RemoteException: "
+ + e.getMessage());
+ }
+ }
+
+ /**
* @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
* @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
* @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
@@ -12955,6 +13032,40 @@
@SystemApi
public interface CallForwardingInfoCallback {
/**
+ * Indicates that the operation was successful.
+ */
+ int RESULT_SUCCESS = 0;
+
+ /**
+ * Indicates that setting or retrieving the call forwarding info failed with an unknown
+ * error.
+ */
+ int RESULT_ERROR_UNKNOWN = 1;
+
+ /**
+ * Indicates that call forwarding is not enabled because the recipient is not on a
+ * Fixed Dialing Number (FDN) list.
+ */
+ int RESULT_ERROR_FDN_CHECK_FAILURE = 2;
+
+ /**
+ * Indicates that call forwarding is not supported on the network at this time.
+ */
+ int RESULT_ERROR_NOT_SUPPORTED = 3;
+
+ /**
+ * Call forwarding errors
+ * @hide
+ */
+ @IntDef(prefix = { "RESULT_ERROR_" }, value = {
+ RESULT_ERROR_UNKNOWN,
+ RESULT_ERROR_NOT_SUPPORTED,
+ RESULT_ERROR_FDN_CHECK_FAILURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CallForwardingError{
+ }
+ /**
* Called when the call forwarding info is successfully retrieved from the network.
* @param info information about how calls are forwarded
*/
@@ -12964,7 +13075,7 @@
* Called when there was an error retrieving the call forwarding information.
* @param error
*/
- void onError(@CallForwardingInfo.CallForwardingError int error);
+ void onError(@CallForwardingError int error);
}
/**
@@ -13037,9 +13148,9 @@
* @param executor The executor on which the listener will be called. Must be non-null if
* {@code listener} is non-null.
* @param resultListener Asynchronous listener that'll be called when the operation completes.
- * Called with {@link CallForwardingInfo#SUCCESS} if the operation
- * succeeded and an error code from {@link CallForwardingInfo}
- * if it failed.
+ * Called with {@link CallForwardingInfoCallback#RESULT_SUCCESS} if the
+ * operation succeeded and an error code from
+ * {@link CallForwardingInfoCallback} it failed.
*
* @throws IllegalArgumentException if any of the following are true for the parameter
* callForwardingInfo:
@@ -13065,7 +13176,8 @@
@SystemApi
public void setCallForwarding(@NonNull CallForwardingInfo callForwardingInfo,
@Nullable @CallbackExecutor Executor executor,
- @Nullable @CallForwardingInfo.CallForwardingError Consumer<Integer> resultListener) {
+ @Nullable @CallForwardingInfoCallback.CallForwardingError
+ Consumer<Integer> resultListener) {
if (callForwardingInfo == null) {
throw new IllegalArgumentException("callForwardingInfo is null");
}
@@ -13221,7 +13333,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void setCallWaitingStatus(boolean enabled, @Nullable Executor executor,
+ public void setCallWaitingEnabled(boolean enabled, @Nullable Executor executor,
@Nullable Consumer<Integer> resultListener) {
if (resultListener != null) {
Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 39859b1..579200e 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -69,6 +69,7 @@
/** {@hide} */
@IntDef(prefix = "HANDOVER_FAILURE_MODE_", value = {
+ HANDOVER_FAILURE_MODE_UNKNOWN,
HANDOVER_FAILURE_MODE_LEGACY,
HANDOVER_FAILURE_MODE_DO_FALLBACK,
HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER,
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index ee2fce7..f6c14e6 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -59,7 +59,6 @@
* manager.
*/
public class ImsMmTelManager implements RegistrationManager {
- private static final String TAG = "ImsMmTelManager";
/**
* @hide
@@ -810,7 +809,7 @@
}
try {
- iTelephony.isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
+ getITelephony().isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
@Override
public void accept(int result) {
executor.execute(() -> callback.accept(result == 1));
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 8a05bdf..da7311c 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,7 +16,6 @@
package android.telephony.ims;
-import android.annotation.LongDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Service;
@@ -42,11 +41,6 @@
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
* ImsService must register the service in their AndroidManifest to be detected by the framework.
@@ -104,32 +98,6 @@
private static final String LOG_TAG = "ImsService";
/**
- * This ImsService supports the capability to place emergency calls over MMTEL.
- * @hide This is encoded into the {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, but we will be
- * adding other capabilities in a central location, so track this capability here as well.
- */
- public static final long CAPABILITY_EMERGENCY_OVER_MMTEL = 1 << 0;
-
- /**
- * @hide
- */
- @LongDef(flag = true,
- prefix = "CAPABILITY_",
- value = {
- CAPABILITY_EMERGENCY_OVER_MMTEL
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ImsServiceCapability {}
-
- /**
- * Used for logging purposes, see {@link #getCapabilitiesString(long)}
- * @hide
- */
- private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
- put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
- }};
-
- /**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
* @hide
*/
@@ -441,30 +409,4 @@
public ImsRegistrationImplBase getRegistration(int slotId) {
return new ImsRegistrationImplBase();
}
-
- /**
- * @return A string representation of the ImsService capabilties for logging.
- * @hide
- */
- public static String getCapabilitiesString(@ImsServiceCapability long caps) {
- StringBuffer result = new StringBuffer();
- result.append("capabilities={ ");
- // filter incrementally fills 0s from left to right. This is used to keep filtering out
- // more bits in the long until the remaining leftmost bits are all zero.
- long filter = 0xFFFFFFFFFFFFFFFFL;
- // position of iterator to potentially print capability.
- long i = 0;
- while ((caps & filter) != 0 && i <= 63) {
- long bitToCheck = (1L << i);
- if ((caps & bitToCheck) != 0) {
- result.append(CAPABILITIES_LOG_MAP.getOrDefault(bitToCheck, bitToCheck + "?"));
- result.append(" ");
- }
- // shift left by one and fill in another 1 on the leftmost bit.
- filter <<= 1;
- i++;
- }
- result.append("}");
- return result.toString();
- }
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 7bbe30a..5246470 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -27,8 +27,11 @@
* See MmTelFeature#Listener for more information.
* {@hide}
*/
-oneway interface IImsMmTelListener {
+ // This interface is not considered oneway because we need to ensure that these operations are
+ // processed by telephony before the control flow returns to the ImsService to perform
+ // operations on the IImsCallSession.
+interface IImsMmTelListener {
void onIncomingCall(IImsCallSession c, in Bundle extras);
void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason);
- void onVoiceMessageCountUpdate(int count);
+ oneway void onVoiceMessageCountUpdate(int count);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index d012703b..9e46142 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -22,7 +22,6 @@
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
-import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.IIntegerConsumer;
/**
@@ -51,9 +50,4 @@
void setUceSettingEnabled(int subId, boolean isEnabled);
void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
-
- // Internal commands that should not be made public
- void registerRcsFeatureCallback(int slotId, in IImsServiceFeatureCallback callback,
- boolean oneShot);
- void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
}
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.aidl b/telephony/java/com/android/ims/ImsFeatureContainer.aidl
deleted file mode 100644
index 9706f20..0000000
--- a/telephony/java/com/android/ims/ImsFeatureContainer.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ims;
-
-parcelable ImsFeatureContainer;
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.java b/telephony/java/com/android/ims/ImsFeatureContainer.java
deleted file mode 100644
index b259679..0000000
--- a/telephony/java/com/android/ims/ImsFeatureContainer.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ims;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.telephony.ims.ImsService;
-import android.telephony.ims.aidl.IImsConfig;
-import android.telephony.ims.aidl.IImsRegistration;
-import android.telephony.ims.feature.ImsFeature;
-
-import java.util.Objects;
-
-/**
- * Contains an IBinder linking to the appropriate ImsFeature as well as the associated
- * interfaces.
- * @hide
- */
-public final class ImsFeatureContainer implements Parcelable {
- /**
- * ImsFeature that is being tracked.
- */
- public final IBinder imsFeature;
-
- /**
- * IImsConfig interface that should be associated with the ImsFeature.
- */
- public final android.telephony.ims.aidl.IImsConfig imsConfig;
-
- /**
- * IImsRegistration interface that should be associated with this ImsFeature.
- */
- public final IImsRegistration imsRegistration;
-
- /**
- * State of the feature that is being tracked.
- */
- private @ImsFeature.ImsState int mState = ImsFeature.STATE_UNAVAILABLE;
-
- /**
- * Capabilities of this ImsService.
- */
- private @ImsService.ImsServiceCapability long mCapabilities;
- /**
- * Contains the ImsFeature IBinder as well as the ImsService interfaces associated with
- * that feature.
- * @param iFace IBinder connection to the ImsFeature.
- * @param iConfig IImsConfig interface associated with the ImsFeature.
- * @param iReg IImsRegistration interface associated with the ImsFeature
- * @param initialCaps The initial capabilities that the ImsService supports.
- */
- public ImsFeatureContainer(@NonNull IBinder iFace, @NonNull IImsConfig iConfig,
- @NonNull IImsRegistration iReg, long initialCaps) {
- imsFeature = iFace;
- imsConfig = iConfig;
- imsRegistration = iReg;
- mCapabilities = initialCaps;
- }
-
- /**
- * Create an ImsFeatureContainer from a Parcel.
- */
- private ImsFeatureContainer(Parcel in) {
- imsFeature = in.readStrongBinder();
- imsConfig = IImsConfig.Stub.asInterface(in.readStrongBinder());
- imsRegistration = IImsRegistration.Stub.asInterface(in.readStrongBinder());
- mState = in.readInt();
- mCapabilities = in.readLong();
- }
-
- /**
- * @return the capabilties that are associated with the ImsService that this ImsFeature
- * belongs to.
- */
- public @ImsService.ImsServiceCapability long getCapabilities() {
- return mCapabilities;
- }
-
- /**
- * Update the capabilities that are associated with the ImsService that this ImsFeature
- * belongs to.
- */
- public void setCapabilities(@ImsService.ImsServiceCapability long caps) {
- mCapabilities = caps;
- }
-
- /**
- * @return The state of the ImsFeature.
- */
- public @ImsFeature.ImsState int getState() {
- return mState;
- }
-
- /**
- * Set the state that is associated with the ImsService that this ImsFeature
- * belongs to.
- */
- public void setState(@ImsFeature.ImsState int state) {
- mState = state;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- ImsFeatureContainer that = (ImsFeatureContainer) o;
- return imsFeature.equals(that.imsFeature) &&
- imsConfig.equals(that.imsConfig) &&
- imsRegistration.equals(that.imsRegistration) &&
- mState == that.getState() &&
- mCapabilities == that.getCapabilities();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(imsFeature, imsConfig, imsRegistration, mState, mCapabilities);
- }
-
- @Override
- public String toString() {
- return "FeatureContainer{" +
- "imsFeature=" + imsFeature +
- ", imsConfig=" + imsConfig +
- ", imsRegistration=" + imsRegistration +
- ", state=" + ImsFeature.STATE_LOG_MAP.get(mState) +
- ", capabilities = " + ImsService.getCapabilitiesString(mCapabilities) +
- '}';
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(imsFeature);
- dest.writeStrongInterface(imsConfig);
- dest.writeStrongInterface(imsRegistration);
- dest.writeInt(mState);
- dest.writeLong(mCapabilities);
- }
-
-
- public static final Creator<ImsFeatureContainer> CREATOR = new Creator<ImsFeatureContainer>() {
- @Override
- public ImsFeatureContainer createFromParcel(Parcel source) {
- return new ImsFeatureContainer(source);
- }
-
- @Override
- public ImsFeatureContainer[] newArray(int size) {
- return new ImsFeatureContainer[size];
- }
- };
-}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
index f5f67bd..9a9cf53 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
@@ -16,18 +16,13 @@
package com.android.ims.internal;
-import com.android.ims.ImsFeatureContainer;
/**
- * Interface from ImsResolver to FeatureConnections.
- * Callback to FeatureConnections when a feature's status changes.
+ * Interface from ImsResolver to ImsServiceProxy in ImsManager.
+ * Callback to ImsManager when a feature changes in the ImsServiceController.
* {@hide}
*/
oneway interface IImsServiceFeatureCallback {
- void imsFeatureCreated(in ImsFeatureContainer feature);
- // Reason defined in FeatureConnector.UnavailableReason
- void imsFeatureRemoved(int reason);
- // Status defined in ImsFeature.ImsState.
- void imsStatusChanged(int status);
- //Capabilities defined in ImsService.ImsServiceCapability
- void updateCapabilities(long capabilities);
+ void imsFeatureCreated(int slotId, int feature);
+ void imsFeatureRemoved(int slotId, int feature);
+ void imsStatusChanged(int slotId, int feature, int status);
}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index ef5078d..02a74ba 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -829,15 +829,22 @@
* as well as registering the MmTelFeature for callbacks using the IImsServiceFeatureCallback
* interface.
*/
- void registerMmTelFeatureCallback(int slotId, in IImsServiceFeatureCallback callback,
- boolean oneShot);
+ IImsMmTelFeature getMmTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
+
+ /**
+ * Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature
+ * as well as registering the RcsFeature for callbacks using the IImsServiceFeatureCallback
+ * interface.
+ */
+ IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
/**
* Unregister a callback that was previously registered through
- * {@link #registerMmTelFeatureCallback}. This should always be called when the callback is no
- * longer being used.
+ * {@link #getMmTelFeatureAndListen} or {@link #getRcsFeatureAndListen}. This should always be
+ * called when the callback is no longer being used.
*/
- void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
+ void unregisterImsFeatureCallback(int slotId, int featureType,
+ in IImsServiceFeatureCallback callback);
/**
* Returns the IImsRegistration associated with the slot and feature specified.
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index 0f62c4f..e9227e94 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -107,7 +107,10 @@
fun ParcelFileDescriptor.text() = FileReader(fileDescriptor).readText()
@After
- fun resetIdentity() = uiAutomation.dropShellPermissionIdentity()
+ fun resetChangeIdAndIdentity() {
+ command("am compat reset $TEST_CHANGE_ID $TEST_PKG")
+ uiAutomation.dropShellPermissionIdentity()
+ }
@Test
fun execute() {
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index 1c297e7..6928618 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -332,6 +332,7 @@
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isMultiStaConcurrencySupported();
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index 0f71ab4..bf7003c 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -608,10 +608,12 @@
public final class WifiNetworkSuggestion implements android.os.Parcelable {
method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+ method public boolean isOemPaid();
}
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
}
public class WifiScanner {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 0973d54..a4f802a 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -992,6 +992,16 @@
public boolean trusted;
/**
+ * Indicate whether the network is oem paid or not. Networks are considered oem paid
+ * if the corresponding connection is only available to system apps.
+ *
+ * This bit can only be used by suggestion network, see
+ * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)}
+ * @hide
+ */
+ public boolean oemPaid;
+
+ /**
* True if this Wifi configuration is created from a {@link WifiNetworkSuggestion},
* false otherwise.
*
@@ -2158,6 +2168,7 @@
ephemeral = false;
osu = false;
trusted = true; // Networks are considered trusted by default.
+ oemPaid = false;
fromWifiNetworkSuggestion = false;
fromWifiNetworkSpecifier = false;
meteredHint = false;
@@ -2278,11 +2289,12 @@
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
if (this.trusted) sbuf.append(" trusted");
+ if (this.oemPaid) sbuf.append(" oemPaid");
if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
if (this.meteredHint) sbuf.append(" meteredHint");
if (this.useExternalScores) sbuf.append(" useExternalScores");
- if (this.validatedInternetAccess || this.ephemeral || this.trusted
+ if (this.validatedInternetAccess || this.ephemeral || this.trusted || this.oemPaid
|| this.fromWifiNetworkSuggestion || this.fromWifiNetworkSpecifier
|| this.meteredHint || this.useExternalScores) {
sbuf.append("\n");
@@ -2828,6 +2840,7 @@
ephemeral = source.ephemeral;
osu = source.osu;
trusted = source.trusted;
+ oemPaid = source.oemPaid;
fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
meteredHint = source.meteredHint;
@@ -2906,6 +2919,7 @@
dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
dest.writeInt(ephemeral ? 1 : 0);
dest.writeInt(trusted ? 1 : 0);
+ dest.writeInt(oemPaid ? 1 : 0);
dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
dest.writeInt(meteredHint ? 1 : 0);
@@ -2981,6 +2995,7 @@
config.isLegacyPasspointConfig = in.readInt() != 0;
config.ephemeral = in.readInt() != 0;
config.trusted = in.readInt() != 0;
+ config.oemPaid = in.readInt() != 0;
config.fromWifiNetworkSuggestion = in.readInt() != 0;
config.fromWifiNetworkSpecifier = in.readInt() != 0;
config.meteredHint = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 5388367..fe5002e 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -159,6 +159,11 @@
private boolean mTrusted;
/**
+ * Whether the network is oem paid or not.
+ */
+ private boolean mOemPaid;
+
+ /**
* OSU (Online Sign Up) AP for Passpoint R2.
*/
private boolean mOsuAp;
@@ -358,6 +363,7 @@
mMeteredHint = source.mMeteredHint;
mEphemeral = source.mEphemeral;
mTrusted = source.mTrusted;
+ mTrusted = source.mOemPaid;
mRequestingPackageName =
source.mRequestingPackageName;
mOsuAp = source.mOsuAp;
@@ -722,6 +728,16 @@
}
/** {@hide} */
+ public void setOemPaid(boolean oemPaid) {
+ mOemPaid = oemPaid;
+ }
+
+ /** {@hide} */
+ public boolean isOemPaid() {
+ return mOemPaid;
+ }
+
+ /** {@hide} */
public void setOsuAp(boolean osuAp) {
mOsuAp = osuAp;
}
@@ -958,6 +974,7 @@
dest.writeInt(mMeteredHint ? 1 : 0);
dest.writeInt(mEphemeral ? 1 : 0);
dest.writeInt(mTrusted ? 1 : 0);
+ dest.writeInt(mOemPaid ? 1 : 0);
dest.writeInt(score);
dest.writeLong(txSuccess);
dest.writeDouble(mSuccessfulTxPacketsPerSecond);
@@ -1003,6 +1020,7 @@
info.mMeteredHint = in.readInt() != 0;
info.mEphemeral = in.readInt() != 0;
info.mTrusted = in.readInt() != 0;
+ info.mOemPaid = in.readInt() != 0;
info.score = in.readInt();
info.txSuccess = in.readLong();
info.mSuccessfulTxPacketsPerSecond = in.readDouble();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b4e4210..4cf5c14 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -44,6 +44,7 @@
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -2480,6 +2481,18 @@
}
/**
+ * Query whether the device supports 2 or more concurrent stations (STA) or not.
+ *
+ * @return true if this device supports multiple STA concurrency, false otherwise.
+ */
+ public boolean isMultiStaConcurrencySupported() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
+ }
+
+ /**
* @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
* with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
* {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2512,14 +2525,6 @@
}
/**
- * @return true if this adapter supports multiple simultaneous connections
- * @hide
- */
- public boolean isAdditionalStaSupported() {
- return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
- }
-
- /**
* @return true if this adapter supports Tunnel Directed Link Setup
*/
public boolean isTdlsSupported() {
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index e992c83..9162c5f 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -24,7 +24,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.net.MacAddress;
+import android.net.NetworkCapabilities;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.TelephonyManager;
@@ -150,6 +152,11 @@
private boolean mIsNetworkUntrusted;
/**
+ * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added).
+ */
+ private boolean mIsNetworkOemPaid;
+
+ /**
* Whether this network will use enhanced MAC randomization.
*/
private boolean mIsEnhancedMacRandomizationEnabled;
@@ -175,6 +182,7 @@
mWapiPskPassphrase = null;
mWapiEnterpriseConfig = null;
mIsNetworkUntrusted = false;
+ mIsNetworkOemPaid = false;
mPriorityGroup = 0;
mIsEnhancedMacRandomizationEnabled = false;
}
@@ -543,7 +551,7 @@
/**
* Specifies whether the system will bring up the network (if selected) as untrusted. An
- * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+ * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
* capability removed. The Wi-Fi network selection process may use this information to
* influence priority of the suggested network for Wi-Fi network selection (most likely to
* reduce it). The connectivity service may use this information to influence the overall
@@ -562,6 +570,41 @@
return this;
}
+ /**
+ * Specifies whether the system will bring up the network (if selected) as OEM paid. An
+ * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability
+ * added.
+ * Note:
+ * <li>The connectivity service may use this information to influence the overall
+ * network configuration of the device. This network is typically only available to system
+ * apps.
+ * <li>On devices which support only 1 concurrent connection (indicated via
+ * {@link WifiManager#isMultiStaConcurrencySupported()}, Wi-Fi network selection process may
+ * use this information to influence priority of the suggested network for Wi-Fi network
+ * selection (most likely to reduce it).
+ * <li>On devices which support more than 1 concurrent connections (indicated via
+ * {@link WifiManager#isMultiStaConcurrencySupported()}, these OEM paid networks will be
+ * brought up as a secondary concurrent connection (primary connection will be used
+ * for networks available to the user and all apps.
+ * <p>
+ * <li> An OEM paid network's credentials may not be shared with the user using
+ * {@link #setCredentialSharedWithUser(boolean)}.</li>
+ * <li> If not set, defaults to false (i.e. network is not OEM paid).</li>
+ *
+ * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid
+ * (if true) or not OEM paid (if false).
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Builder setOemPaid(boolean isOemPaid) {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ mIsNetworkOemPaid = isOemPaid;
+ return this;
+ }
+
private void setSecurityParamsInWifiConfiguration(
@NonNull WifiConfiguration configuration) {
if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -628,6 +671,7 @@
wifiConfiguration.meteredOverride = mMeteredOverride;
wifiConfiguration.carrierId = mCarrierId;
wifiConfiguration.trusted = !mIsNetworkUntrusted;
+ wifiConfiguration.oemPaid = mIsNetworkOemPaid;
wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
? WifiConfiguration.RANDOMIZATION_ENHANCED
: WifiConfiguration.RANDOMIZATION_PERSISTENT;
@@ -659,6 +703,7 @@
wifiConfiguration.priority = mPriority;
wifiConfiguration.meteredOverride = mMeteredOverride;
wifiConfiguration.trusted = !mIsNetworkUntrusted;
+ wifiConfiguration.oemPaid = mIsNetworkOemPaid;
mPasspointConfiguration.setCarrierId(mCarrierId);
mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
@@ -764,7 +809,15 @@
if (mIsSharedWithUserSet && mIsSharedWithUser) {
throw new IllegalStateException("Should not be both"
+ "setCredentialSharedWithUser and +"
- + "setIsNetworkAsUntrusted to true");
+ + "setUntrusted to true");
+ }
+ mIsSharedWithUser = false;
+ }
+ if (mIsNetworkOemPaid) {
+ if (mIsSharedWithUserSet && mIsSharedWithUser) {
+ throw new IllegalStateException("Should not be both"
+ + "setCredentialSharedWithUser and +"
+ + "setOemPaid to true");
}
mIsSharedWithUser = false;
}
@@ -931,6 +984,7 @@
.append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
.append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
.append(", isUnTrusted=").append(!wifiConfiguration.trusted)
+ .append(", isOemPaid=").append(wifiConfiguration.oemPaid)
.append(", priorityGroup=").append(priorityGroup)
.append(" ]");
return sb.toString();
@@ -1026,6 +1080,18 @@
}
/**
+ * @see Builder#setOemPaid(boolean)
+ * @hide
+ */
+ @SystemApi
+ public boolean isOemPaid() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return wifiConfiguration.oemPaid;
+ }
+
+ /**
* Get the WifiEnterpriseConfig, or null if unset.
* @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
* @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index d4b2051..6894ba0 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -68,6 +68,7 @@
WifiConfiguration config = new WifiConfiguration();
config.setPasspointManagementObjectTree(cookie);
config.trusted = false;
+ config.oemPaid = true;
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 311bbc4..06ae13a 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -61,6 +61,7 @@
writeWifiInfo.txBad = TEST_TX_BAD;
writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
writeWifiInfo.setTrusted(true);
+ writeWifiInfo.setOemPaid(true);
writeWifiInfo.setOsuAp(true);
writeWifiInfo.setFQDN(TEST_FQDN);
writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
@@ -81,6 +82,7 @@
assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
assertTrue(readWifiInfo.isTrusted());
+ assertTrue(readWifiInfo.isOemPaid());
assertTrue(readWifiInfo.isOsuAp());
assertTrue(readWifiInfo.isPasspointAp());
assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName());
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index e7f1916c..bda776d 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -33,6 +33,8 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_AP_STA;
import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P;
@@ -49,6 +51,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
@@ -83,6 +86,7 @@
import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
import android.net.wifi.WifiManager.TrafficStateCallback;
import android.net.wifi.WifiManager.WifiConnectedNetworkScorer;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -1708,6 +1712,34 @@
}
/**
+ * Test behavior of isStaApConcurrencySupported
+ */
+ @Test
+ public void testIsStaApConcurrencyOpenSupported() throws Exception {
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(WIFI_FEATURE_AP_STA));
+ assertTrue(mWifiManager.isStaApConcurrencySupported());
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(~WIFI_FEATURE_AP_STA));
+ assertFalse(mWifiManager.isStaApConcurrencySupported());
+ }
+
+ /**
+ * Test behavior of isMultiStaConcurrencySupported
+ */
+ @Test
+ public void testIsMultiStaConcurrencyOpenSupported() throws Exception {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA));
+ assertTrue(mWifiManager.isMultiStaConcurrencySupported());
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(~WIFI_FEATURE_ADDITIONAL_STA));
+ assertFalse(mWifiManager.isMultiStaConcurrencySupported());
+ }
+
+ /**
* Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
*/
@Test
@@ -1858,7 +1890,6 @@
assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
assertFalse(mWifiManager.isDeviceToApRttSupported());
assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
- assertFalse(mWifiManager.isAdditionalStaSupported());
assertFalse(mWifiManager.isTdlsSupported());
assertFalse(mWifiManager.isOffChannelTdlsSupported());
assertFalse(mWifiManager.isEnhancedPowerReportingSupported());
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 668d238..6e08ca4 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -22,10 +22,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.PasspointTestUtils;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -192,6 +194,34 @@
/**
* Validate correctness of WifiNetworkSuggestion object created by
+ * {@link WifiNetworkSuggestion.Builder#build()} for OWE network.
+ */
+ @Test
+ public void testWifiNetworkSuggestionBuilderForOemPaidEnhancedOpenNetworkWithBssid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setBssid(MacAddress.fromString(TEST_BSSID))
+ .setOemPaid(true)
+ .setIsEnhancedOpen(true)
+ .build();
+
+ assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+ assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID);
+ assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+ .get(WifiConfiguration.KeyMgmt.OWE));
+ assertNull(suggestion.wifiConfiguration.preSharedKey);
+ assertTrue(suggestion.wifiConfiguration.requirePmf);
+ assertTrue(suggestion.wifiConfiguration.oemPaid);
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
+ assertNull(suggestion.getEnterpriseConfig());
+ }
+
+ /**
+ * Validate correctness of WifiNetworkSuggestion object created by
* {@link WifiNetworkSuggestion.Builder#build()} for SAE network.
*/
@Test
@@ -1010,6 +1040,41 @@
}
/**
+ * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+ * correct value to the WifiConfiguration.
+ */
+ @Test
+ public void testSetIsNetworkAsOemPaid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setOemPaid(true)
+ .build();
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ }
+
+ /**
+ * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+ * correct value to the WifiConfiguration.
+ * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+ */
+ @Test
+ public void testSetIsNetworkAsOemPaidOnPasspointNetwork() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setPasspointConfig(passpointConfiguration)
+ .setOemPaid(true)
+ .build();
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ }
+
+ /**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
* when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and
* set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
@@ -1027,6 +1092,24 @@
/**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when set {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} to true and
+ * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+ * together.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPaid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setCredentialSharedWithUser(true)
+ .setOemPaid(true)
+ .build();
+ }
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
* when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutojoinEnabled(boolean)}
* and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
* to false on a passpoint suggestion.