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