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 package com.android.settings.datetime;
17 
18 import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
19 import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
20 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
21 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
22 
23 import android.app.time.TimeManager;
24 import android.app.time.TimeZoneCapabilities;
25 import android.app.time.TimeZoneCapabilitiesAndConfig;
26 import android.app.time.TimeZoneConfiguration;
27 import android.content.Context;
28 import android.location.LocationManager;
29 
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceScreen;
32 
33 import com.android.settings.R;
34 import com.android.settings.core.InstrumentedPreferenceFragment;
35 import com.android.settings.core.TogglePreferenceController;
36 import com.android.settingslib.core.lifecycle.LifecycleObserver;
37 import com.android.settingslib.core.lifecycle.events.OnStart;
38 import com.android.settingslib.core.lifecycle.events.OnStop;
39 
40 import java.util.concurrent.Executor;
41 
42 /**
43  * The controller for the "location time zone detection" entry in the Location settings
44  * screen.
45  */
46 public class LocationTimeZoneDetectionPreferenceController
47         extends TogglePreferenceController
48         implements LifecycleObserver, OnStart, OnStop, TimeManager.TimeZoneDetectorListener {
49 
50     private static final String TAG = "location_time_zone_detection";
51 
52     private final TimeManager mTimeManager;
53     private final LocationManager mLocationManager;
54     private TimeZoneCapabilitiesAndConfig mTimeZoneCapabilitiesAndConfig;
55     private InstrumentedPreferenceFragment mFragment;
56     private Preference mPreference;
57 
LocationTimeZoneDetectionPreferenceController(Context context)58     public LocationTimeZoneDetectionPreferenceController(Context context) {
59         super(context, TAG);
60         mTimeManager = context.getSystemService(TimeManager.class);
61         mLocationManager = context.getSystemService(LocationManager.class);
62     }
63 
setFragment(InstrumentedPreferenceFragment fragment)64     void setFragment(InstrumentedPreferenceFragment fragment) {
65         mFragment = fragment;
66     }
67 
68     @Override
isChecked()69     public boolean isChecked() {
70         TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
71                 mTimeManager.getTimeZoneCapabilitiesAndConfig();
72         TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
73         return configuration.isGeoDetectionEnabled();
74     }
75 
76     @Override
setChecked(boolean isChecked)77     public boolean setChecked(boolean isChecked) {
78         if (isChecked && !mLocationManager.isLocationEnabled()) {
79             new LocationToggleDisabledDialogFragment(mContext)
80                     .show(mFragment.getFragmentManager(), TAG);
81             // Toggle status is not updated.
82             return false;
83         } else {
84             TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
85                     .setGeoDetectionEnabled(isChecked)
86                     .build();
87             return mTimeManager.updateTimeZoneConfiguration(configuration);
88         }
89     }
90 
91     @Override
displayPreference(PreferenceScreen screen)92     public void displayPreference(PreferenceScreen screen) {
93         super.displayPreference(screen);
94         mPreference = screen.findPreference(getPreferenceKey());
95     }
96 
97     @Override
onStart()98     public void onStart() {
99         // Register for updates to the user's time zone capabilities or configuration which could
100         // require UI changes.
101         Executor mainExecutor = mContext.getMainExecutor();
102         mTimeManager.addTimeZoneDetectorListener(mainExecutor, this);
103         // Setup the initial state of the summary.
104         refreshUi();
105     }
106 
107     @Override
onStop()108     public void onStop() {
109         mTimeManager.removeTimeZoneDetectorListener(this);
110     }
111 
112     @Override
isSliceable()113     public boolean isSliceable() {
114         // Prevent use in a slice, which would enable search to display a toggle in the search
115         // results: LocationToggleDisabledDialogFragment has to be shown under some circumstances
116         // which doesn't work when embedded in search. b/185906072
117         return false;
118     }
119 
120     @Override
getSliceHighlightMenuRes()121     public int getSliceHighlightMenuRes() {
122         // not needed since it's not sliceable
123         return NO_RES;
124     }
125 
126     @Override
getAvailabilityStatus()127     public int getAvailabilityStatus() {
128         TimeZoneCapabilities timeZoneCapabilities =
129                 getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false).getCapabilities();
130         int capability = timeZoneCapabilities.getConfigureGeoDetectionEnabledCapability();
131 
132         // The preference only has two states: present and not present. The preference is never
133         // present but disabled.
134         if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) {
135             return UNSUPPORTED_ON_DEVICE;
136         } else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) {
137             return AVAILABLE;
138         } else {
139             throw new IllegalStateException("Unknown capability=" + capability);
140         }
141     }
142 
143     @Override
getSummary()144     public CharSequence getSummary() {
145         TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig =
146                 getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false);
147         TimeZoneCapabilities capabilities = timeZoneCapabilitiesAndConfig.getCapabilities();
148         int configureGeoDetectionEnabledCapability =
149                 capabilities.getConfigureGeoDetectionEnabledCapability();
150         TimeZoneConfiguration configuration = timeZoneCapabilitiesAndConfig.getConfiguration();
151 
152         int summaryResId;
153         if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_SUPPORTED) {
154             // The preference should not be visible, but text is referenced in case this changes.
155             summaryResId = R.string.location_time_zone_detection_not_supported;
156         } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_ALLOWED) {
157             // The preference should not be visible, but text is referenced in case this changes.
158             summaryResId = R.string.location_time_zone_detection_not_allowed;
159         } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_APPLICABLE) {
160             // The TimeZoneCapabilities deliberately doesn't provide information about why the user
161             // doesn't have the capability, but the user's "location enabled" being off and the
162             // global automatic detection setting will always be considered overriding reasons why
163             // location time zone detection cannot be used.
164             if (!mLocationManager.isLocationEnabled()) {
165                 summaryResId = R.string.location_app_permission_summary_location_off;
166             } else if (!configuration.isAutoDetectionEnabled()) {
167                 summaryResId = R.string.location_time_zone_detection_auto_is_off;
168             } else {
169                 // This is in case there are other reasons in future why location time zone
170                 // detection is not applicable.
171                 summaryResId = R.string.location_time_zone_detection_not_applicable;
172             }
173         } else if (configureGeoDetectionEnabledCapability == CAPABILITY_POSSESSED) {
174             // If capability is possessed, toggle status already tells all the information needed.
175             // Returning null will make previous text stick on toggling.
176             // See AbstractPreferenceController#refreshSummary.
177             return "";
178         } else {
179             // This is unexpected: getAvailabilityStatus() should ensure that the UI element isn't
180             // even shown for known cases, or the capability is unknown.
181             throw new IllegalStateException("Unexpected configureGeoDetectionEnabledCapability="
182                     + configureGeoDetectionEnabledCapability);
183         }
184         return mContext.getString(summaryResId);
185     }
186 
187     @Override
onChange()188     public void onChange() {
189         refreshUi();
190     }
191 
refreshUi()192     private void refreshUi() {
193         // Force a refresh of cached user capabilities and config before refreshing the summary.
194         getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ true);
195         refreshSummary(mPreference);
196     }
197 
198     /**
199      * Returns the current user capabilities and configuration. {@code forceRefresh} can be {@code
200      * true} to discard any cached copy.
201      */
getTimeZoneCapabilitiesAndConfig(boolean forceRefresh)202     private TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig(boolean forceRefresh) {
203         if (forceRefresh || mTimeZoneCapabilitiesAndConfig == null) {
204             mTimeZoneCapabilitiesAndConfig = mTimeManager.getTimeZoneCapabilitiesAndConfig();
205         }
206         return mTimeZoneCapabilitiesAndConfig;
207     }
208 }
209