1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.timezonedetector;
18 
19 import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
20 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
21 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
22 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
23 
24 import android.annotation.NonNull;
25 import android.annotation.UserIdInt;
26 import android.app.time.TimeZoneCapabilities;
27 import android.app.time.TimeZoneCapabilitiesAndConfig;
28 import android.app.time.TimeZoneConfiguration;
29 import android.os.UserHandle;
30 
31 import java.util.Objects;
32 
33 /**
34  * Holds configuration values that affect user-facing time zone behavior and some associated logic.
35  * Some configuration is global, some is user scoped, but this class deliberately doesn't make a
36  * distinction for simplicity.
37  */
38 public final class ConfigurationInternal {
39 
40     private final boolean mTelephonyDetectionSupported;
41     private final boolean mGeoDetectionSupported;
42     private final boolean mAutoDetectionEnabled;
43     private final @UserIdInt int mUserId;
44     private final boolean mUserConfigAllowed;
45     private final boolean mLocationEnabled;
46     private final boolean mGeoDetectionEnabled;
47 
ConfigurationInternal(Builder builder)48     private ConfigurationInternal(Builder builder) {
49         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
50         mGeoDetectionSupported = builder.mGeoDetectionSupported;
51         mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
52 
53         mUserId = builder.mUserId;
54         mUserConfigAllowed = builder.mUserConfigAllowed;
55         mLocationEnabled = builder.mLocationEnabled;
56         mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
57     }
58 
59     /** Returns true if the device supports any form of auto time zone detection. */
isAutoDetectionSupported()60     public boolean isAutoDetectionSupported() {
61         return mTelephonyDetectionSupported || mGeoDetectionSupported;
62     }
63 
64     /** Returns true if the device supports telephony time zone detection. */
isTelephonyDetectionSupported()65     public boolean isTelephonyDetectionSupported() {
66         return mTelephonyDetectionSupported;
67     }
68 
69     /** Returns true if the device supports geolocation time zone detection. */
isGeoDetectionSupported()70     public boolean isGeoDetectionSupported() {
71         return mGeoDetectionSupported;
72     }
73 
74     /** Returns the value of the auto time zone detection enabled setting. */
getAutoDetectionEnabledSetting()75     public boolean getAutoDetectionEnabledSetting() {
76         return mAutoDetectionEnabled;
77     }
78 
79     /**
80      * Returns true if auto time zone detection behavior is actually enabled, which can be distinct
81      * from the raw setting value.
82      */
getAutoDetectionEnabledBehavior()83     public boolean getAutoDetectionEnabledBehavior() {
84         return isAutoDetectionSupported() && mAutoDetectionEnabled;
85     }
86 
87     /** Returns the ID of the user this configuration is associated with. */
getUserId()88     public @UserIdInt int getUserId() {
89         return mUserId;
90     }
91 
92     /** Returns the handle of the user this configuration is associated with. */
93     @NonNull
getUserHandle()94     public UserHandle getUserHandle() {
95         return UserHandle.of(mUserId);
96     }
97 
98     /** Returns true if the user allowed to modify time zone configuration. */
isUserConfigAllowed()99     public boolean isUserConfigAllowed() {
100         return mUserConfigAllowed;
101     }
102 
103     /** Returns true if user's location can be used generally. */
isLocationEnabled()104     public boolean isLocationEnabled() {
105         return mLocationEnabled;
106     }
107 
108     /** Returns the value of the geolocation time zone detection enabled setting. */
getGeoDetectionEnabledSetting()109     public boolean getGeoDetectionEnabledSetting() {
110         return mGeoDetectionEnabled;
111     }
112 
113     /**
114      * Returns true if geolocation time zone detection behavior is actually enabled, which can be
115      * distinct from the raw setting value.
116      */
getGeoDetectionEnabledBehavior()117     public boolean getGeoDetectionEnabledBehavior() {
118         return getAutoDetectionEnabledBehavior()
119                 && isGeoDetectionSupported()
120                 && isLocationEnabled()
121                 && getGeoDetectionEnabledSetting();
122     }
123 
124     /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
createCapabilitiesAndConfig()125     public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig() {
126         return new TimeZoneCapabilitiesAndConfig(asCapabilities(), asConfiguration());
127     }
128 
129     @NonNull
asCapabilities()130     private TimeZoneCapabilities asCapabilities() {
131         UserHandle userHandle = UserHandle.of(mUserId);
132         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
133 
134         boolean allowConfigDateTime = isUserConfigAllowed();
135 
136         // Automatic time zone detection is only supported on devices if there is a telephony
137         // network available or geolocation time zone detection is possible.
138         boolean deviceHasAutoTimeZoneDetection = isAutoDetectionSupported();
139 
140         final int configureAutoDetectionEnabledCapability;
141         if (!deviceHasAutoTimeZoneDetection) {
142             configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
143         } else if (!allowConfigDateTime) {
144             configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
145         } else {
146             configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
147         }
148         builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
149 
150         boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
151         // Note: allowConfigDateTime does not restrict the ability to change location time zone
152         // detection enabled. This is intentional as it has user privacy implications and so it
153         // makes sense to leave this under a user's control.
154         final int configureGeolocationDetectionEnabledCapability;
155         if (!deviceHasLocationTimeZoneDetection) {
156             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
157         } else if (!mAutoDetectionEnabled || !isLocationEnabled()) {
158             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
159         } else {
160             configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
161         }
162         builder.setConfigureGeoDetectionEnabledCapability(
163                 configureGeolocationDetectionEnabledCapability);
164 
165         // The ability to make manual time zone suggestions can also be restricted by policy. With
166         // the current logic above, this could lead to a situation where a device hardware does not
167         // support auto detection, the device has been forced into "auto" mode by an admin and the
168         // user is unable to disable auto detection.
169         final int suggestManualTimeZoneCapability;
170         if (!allowConfigDateTime) {
171             suggestManualTimeZoneCapability = CAPABILITY_NOT_ALLOWED;
172         } else if (getAutoDetectionEnabledBehavior()) {
173             suggestManualTimeZoneCapability = CAPABILITY_NOT_APPLICABLE;
174         } else {
175             suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
176         }
177         builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
178 
179         return builder.build();
180     }
181 
182     /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
asConfiguration()183     private TimeZoneConfiguration asConfiguration() {
184         return new TimeZoneConfiguration.Builder()
185                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
186                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
187                 .build();
188     }
189 
190     /**
191      * Merges the configuration values from this with any properties set in {@code
192      * newConfiguration}. The new configuration has precedence. Used to apply user updates to
193      * internal configuration.
194      */
merge(TimeZoneConfiguration newConfiguration)195     public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
196         Builder builder = new Builder(this);
197         if (newConfiguration.hasIsAutoDetectionEnabled()) {
198             builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
199         }
200         if (newConfiguration.hasIsGeoDetectionEnabled()) {
201             builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
202         }
203         return builder.build();
204     }
205 
206     @Override
equals(Object o)207     public boolean equals(Object o) {
208         if (this == o) {
209             return true;
210         }
211         if (o == null || getClass() != o.getClass()) {
212             return false;
213         }
214         ConfigurationInternal that = (ConfigurationInternal) o;
215         return mUserId == that.mUserId
216                 && mUserConfigAllowed == that.mUserConfigAllowed
217                 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
218                 && mGeoDetectionSupported == that.mGeoDetectionSupported
219                 && mAutoDetectionEnabled == that.mAutoDetectionEnabled
220                 && mLocationEnabled == that.mLocationEnabled
221                 && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
222     }
223 
224     @Override
hashCode()225     public int hashCode() {
226         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
227                 mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled,
228                 mGeoDetectionEnabled);
229     }
230 
231     @Override
toString()232     public String toString() {
233         return "ConfigurationInternal{"
234                 + "mUserId=" + mUserId
235                 + ", mUserConfigAllowed=" + mUserConfigAllowed
236                 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
237                 + ", mGeoDetectionSupported=" + mGeoDetectionSupported
238                 + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
239                 + ", mLocationEnabled=" + mLocationEnabled
240                 + ", mGeoDetectionEnabled=" + mGeoDetectionEnabled
241                 + '}';
242     }
243 
244     /**
245      * A Builder for {@link ConfigurationInternal}.
246      */
247     public static class Builder {
248 
249         private final @UserIdInt int mUserId;
250 
251         private boolean mUserConfigAllowed;
252         private boolean mTelephonyDetectionSupported;
253         private boolean mGeoDetectionSupported;
254         private boolean mAutoDetectionEnabled;
255         private boolean mLocationEnabled;
256         private boolean mGeoDetectionEnabled;
257 
258         /**
259          * Creates a new Builder with only the userId set.
260          */
Builder(@serIdInt int userId)261         public Builder(@UserIdInt int userId) {
262             mUserId = userId;
263         }
264 
265         /**
266          * Creates a new Builder by copying values from an existing instance.
267          */
Builder(ConfigurationInternal toCopy)268         public Builder(ConfigurationInternal toCopy) {
269             this.mUserId = toCopy.mUserId;
270             this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
271             this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
272             this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
273             this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
274             this.mLocationEnabled = toCopy.mLocationEnabled;
275             this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
276         }
277 
278         /**
279          * Sets whether the user is allowed to configure time zone settings on this device.
280          */
setUserConfigAllowed(boolean configAllowed)281         public Builder setUserConfigAllowed(boolean configAllowed) {
282             mUserConfigAllowed = configAllowed;
283             return this;
284         }
285 
286         /**
287          * Sets whether telephony time zone detection is supported on this device.
288          */
setTelephonyDetectionFeatureSupported(boolean supported)289         public Builder setTelephonyDetectionFeatureSupported(boolean supported) {
290             mTelephonyDetectionSupported = supported;
291             return this;
292         }
293 
294         /**
295          * Sets whether geolocation time zone detection is supported on this device.
296          */
setGeoDetectionFeatureSupported(boolean supported)297         public Builder setGeoDetectionFeatureSupported(boolean supported) {
298             mGeoDetectionSupported = supported;
299             return this;
300         }
301 
302         /**
303          * Sets the value of the automatic time zone detection enabled setting for this device.
304          */
setAutoDetectionEnabled(boolean enabled)305         public Builder setAutoDetectionEnabled(boolean enabled) {
306             mAutoDetectionEnabled = enabled;
307             return this;
308         }
309 
310         /**
311          * Sets the value of the location mode setting for this user.
312          */
setLocationEnabled(boolean enabled)313         public Builder setLocationEnabled(boolean enabled) {
314             mLocationEnabled = enabled;
315             return this;
316         }
317 
318         /**
319          * Sets the value of the geolocation time zone detection setting for this user.
320          */
setGeoDetectionEnabled(boolean enabled)321         public Builder setGeoDetectionEnabled(boolean enabled) {
322             mGeoDetectionEnabled = enabled;
323             return this;
324         }
325 
326         /** Returns a new {@link ConfigurationInternal}. */
327         @NonNull
build()328         public ConfigurationInternal build() {
329             return new ConfigurationInternal(this);
330         }
331     }
332 }
333