Merge "Get ready to add System APIs for use by SUW"

This commit is contained in:
Neil Fuller
2020-09-30 14:46:11 +00:00
committed by Android (Google) Code Review
26 changed files with 988 additions and 717 deletions

View File

@@ -33,6 +33,7 @@ import android.app.prediction.AppPredictionManager;
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 @@ public final class SystemServiceRegistry {
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

View File

@@ -14,11 +14,9 @@
* limitations under the License.
*/
package android.app.timezonedetector;
import android.app.timezonedetector.TimeZoneConfiguration;
package android.app.time;
/** {@hide} */
oneway interface ITimeZoneConfigurationListener {
oneway interface ITimeZoneDetectorListener {
void onChange();
}

View File

@@ -0,0 +1,12 @@
{
"presubmit": [
{
"name": "FrameworksCoreTests",
"options": [
{
"include-filter": "android.app.time."
}
]
}
]
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -14,6 +14,6 @@
* limitations under the License.
*/
package android.app.timezonedetector;
package android.app.time;
parcelable TimeZoneCapabilities;

View File

@@ -14,16 +14,16 @@
* 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;
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;
@@ -42,15 +42,18 @@ import java.util.Objects;
* <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).
* {@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)
@@ -94,64 +97,60 @@ public final class TimeZoneCapabilities implements Parcelable {
}
};
@NonNull private final TimeZoneConfiguration mConfiguration;
private final @CapabilityState int mConfigureAutoDetectionEnabled;
private final @CapabilityState int mConfigureGeoDetectionEnabled;
private final @CapabilityState int mSuggestManualTimeZone;
/**
* 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.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
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) {
return new TimeZoneCapabilities.Builder()
.setConfiguration(in.readParcelable(null))
.setConfigureAutoDetectionEnabled(in.readInt())
.setConfigureGeoDetectionEnabled(in.readInt())
.setSuggestManualTimeZone(in.readInt())
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) {
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;
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
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
* #getConfiguration()}.
* TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
*/
@CapabilityState
public int getConfigureAutoDetectionEnabled() {
return mConfigureAutoDetectionEnabled;
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
* TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
* #getConfiguration()}.
* TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
*/
@CapabilityState
public int getConfigureGeoDetectionEnabled() {
return mConfigureGeoDetectionEnabled;
public int getConfigureGeoDetectionEnabledCapability() {
return mConfigureGeoDetectionEnabledCapability;
}
/**
@@ -160,36 +159,37 @@ public final class TimeZoneCapabilities implements Parcelable {
*
* <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 getSuggestManualTimeZone() {
return mSuggestManualTimeZone;
public int getSuggestManualTimeZoneCapability() {
return mSuggestManualTimeZoneCapability;
}
/**
* 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.
* 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 applyUpdate(TimeZoneConfiguration requestedChanges) {
if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
throw new IllegalArgumentException("User does not match:"
+ " this=" + mConfiguration + ", other=" + requestedChanges);
}
public TimeZoneConfiguration tryApplyConfigChanges(
@NonNull TimeZoneConfiguration config,
@NonNull TimeZoneConfiguration requestedChanges) {
TimeZoneConfiguration.Builder newConfigBuilder =
new TimeZoneConfiguration.Builder(mConfiguration);
if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
new TimeZoneConfiguration.Builder(config);
if (requestedChanges.hasIsAutoDetectionEnabled()) {
if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
return null;
}
newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
}
if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
if (requestedChanges.hasIsGeoDetectionEnabled()) {
if (this.getConfigureGeoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
return null;
}
newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
@@ -212,71 +212,71 @@ public final class TimeZoneCapabilities implements Parcelable {
return false;
}
TimeZoneCapabilities that = (TimeZoneCapabilities) o;
return Objects.equals(mConfiguration, that.mConfiguration)
&& mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
&& mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
&& mSuggestManualTimeZone == that.mSuggestManualTimeZone;
return mUserHandle.equals(that.mUserHandle)
&& mConfigureAutoDetectionEnabledCapability
== that.mConfigureAutoDetectionEnabledCapability
&& mConfigureGeoDetectionEnabledCapability
== that.mConfigureGeoDetectionEnabledCapability
&& mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
}
@Override
public int hashCode() {
return Objects.hash(mConfiguration,
mConfigureAutoDetectionEnabled,
mConfigureGeoDetectionEnabled,
mSuggestManualTimeZone);
return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
}
@Override
public String toString() {
return "TimeZoneDetectorCapabilities{"
+ "mConfiguration=" + mConfiguration
+ ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
+ ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
+ ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
+ "mUserHandle=" + mUserHandle
+ ", mConfigureAutoDetectionEnabledCapability="
+ mConfigureAutoDetectionEnabledCapability
+ ", mConfigureGeoDetectionEnabledCapability="
+ mConfigureGeoDetectionEnabledCapability
+ ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+ '}';
}
/** @hide */
public static class Builder {
private TimeZoneConfiguration mConfiguration;
private @CapabilityState int mConfigureAutoDetectionEnabled;
private @CapabilityState int mConfigureGeoDetectionEnabled;
private @CapabilityState int mSuggestManualTimeZone;
@NonNull private UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private @CapabilityState int mSuggestManualTimeZoneCapability;
/** 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;
public Builder(@NonNull UserHandle userHandle) {
mUserHandle = Objects.requireNonNull(userHandle);
}
/** Sets the state for the automatic time zone detection enabled config. */
public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabled = value;
public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the geolocation time zone detection enabled config. */
public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
this.mConfigureGeoDetectionEnabled = value;
public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
this.mConfigureGeoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the suggestManualTimeZone action. */
public Builder setSuggestManualTimeZone(@CapabilityState int value) {
this.mSuggestManualTimeZone = value;
public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
this.mSuggestManualTimeZoneCapability = value;
return this;
}
/** Returns the {@link TimeZoneCapabilities}. */
@NonNull
public TimeZoneCapabilities build() {
verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
"configureAutoDetectionEnabledCapability");
verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
"configureGeoDetectionEnabledCapability");
verifyCapabilitySet(mSuggestManualTimeZoneCapability,
"suggestManualTimeZoneCapability");
return new TimeZoneCapabilities(this);
}

View File

@@ -0,0 +1,19 @@
/*
* 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;
parcelable TimeZoneCapabilitiesAndConfig;

View File

@@ -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
+ '}';
}
}

View File

@@ -14,6 +14,6 @@
* limitations under the License.
*/
package android.app.timezonedetector;
package android.app.time;
parcelable TimeZoneConfiguration;

View File

@@ -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 @@ import java.util.Objects;
* 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 @@ public final class TimeZoneConfiguration implements Parcelable {
}
};
/** 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,31 +104,50 @@ public final class TimeZoneConfiguration implements Parcelable {
* 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);
return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED);
}
/**
* 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 @@ public final class TimeZoneConfiguration implements Parcelable {
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 @@ public final class TimeZoneConfiguration implements Parcelable {
}
}
/** @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 @@ public final class TimeZoneConfiguration implements Parcelable {
/**
* 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 @@ public final class TimeZoneConfiguration implements Parcelable {
/**
* 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;

View File

@@ -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 @@ import android.app.timezonedetector.TimeZoneConfiguration;
* {@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);

View File

@@ -29,70 +29,6 @@ import android.content.Context;
@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
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}.
*

View File

@@ -21,7 +21,6 @@ import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.ArraySet;
import android.util.Log;
/**
@@ -35,107 +34,11 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
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) {

View File

@@ -40,6 +40,7 @@ import android.app.ActivityManager;
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;
@@ -5089,6 +5090,14 @@ public abstract class Context {
*/
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

View File

@@ -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));
}
}

View File

@@ -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.assertFalse;
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 @@ public class TimeZoneConfigurationTest {
@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 @@ public class TimeZoneConfigurationTest {
{
TimeZoneConfiguration configuration2 =
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(false)
.build();
@@ -90,22 +94,14 @@ public class TimeZoneConfigurationTest {
@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 @@ public class TimeZoneConfigurationTest {
@Test
public void testParcelable() {
TimeZoneConfiguration.Builder builder =
new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
new TimeZoneConfiguration.Builder();
assertRoundTripParcelable(builder.build());
builder.setAutoDetectionEnabled(true);

View File

@@ -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));
}
}

View File

@@ -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 @@ import java.util.Objects;
/**
* Holds all configuration values that affect time zone behavior and some associated logic, e.g.
* {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
* #createCapabilities()}.
* #createCapabilitiesAndConfig()}.
*/
public final class ConfigurationInternal {
@@ -106,10 +107,15 @@ public final class ConfigurationInternal {
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 @@ public final class ConfigurationInternal {
} else {
configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
}
builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
final int configureGeolocationDetectionEnabledCapability;
if (!deviceHasTimeZoneDetection) {
@@ -137,7 +143,8 @@ public final class ConfigurationInternal {
} 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 @@ public final class ConfigurationInternal {
} 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 final class ConfigurationInternal {
*/
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();

View File

@@ -23,7 +23,7 @@ import android.annotation.Nullable;
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;
@@ -159,7 +159,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
}
@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
@@ -171,7 +171,6 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
setAutoDetectionEnabled(autoDetectionEnabled);
if (mGeoDetectionFeatureEnabled) {
final int userId = configuration.getUserId();
final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
}

View File

@@ -18,18 +18,19 @@ package com.android.server.timezonedetector;
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 com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
/**
@@ -109,10 +109,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
@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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
/**
* 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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
// 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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
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() {

View File

@@ -17,9 +17,9 @@ package com.android.server.timezonedetector;
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 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
* <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

View File

@@ -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 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* 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 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
@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 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
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 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
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());

View File

@@ -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 @@ public class ConfigurationInternalTest {
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 @@ public class ConfigurationInternalTest {
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 @@ public class ConfigurationInternalTest {
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 @@ public class ConfigurationInternalTest {
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 @@ public class ConfigurationInternalTest {
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 @@ public class ConfigurationInternalTest {
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());
}
}
}

View File

@@ -22,10 +22,11 @@ import static org.junit.Assert.fail;
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 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
}
@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.

View File

@@ -31,10 +31,10 @@ import static org.mockito.Mockito.verify;
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 @@ public class TimeZoneDetectorServiceTest {
}
@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 @@ public class TimeZoneDetectorServiceTest {
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 @@ public class TimeZoneDetectorServiceTest {
// 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 @@ public class TimeZoneDetectorServiceTest {
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 @@ public class TimeZoneDetectorServiceTest {
}
private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
}

View File

@@ -39,11 +39,11 @@ import static org.junit.Assert.fail;
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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
// 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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
// 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 @@ public class TimeZoneDetectorStrategyImplTest {
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 @@ public class TimeZoneDetectorStrategyImplTest {
}
@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 @@ public class TimeZoneDetectorStrategyImplTest {
* 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;
}