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 getAvailabilityStatus()121 public int getAvailabilityStatus() { 122 TimeZoneCapabilities timeZoneCapabilities = 123 getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false).getCapabilities(); 124 int capability = timeZoneCapabilities.getConfigureGeoDetectionEnabledCapability(); 125 126 // The preference only has two states: present and not present. The preference is never 127 // present but disabled. 128 if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) { 129 return UNSUPPORTED_ON_DEVICE; 130 } else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) { 131 return AVAILABLE; 132 } else { 133 throw new IllegalStateException("Unknown capability=" + capability); 134 } 135 } 136 137 @Override getSummary()138 public CharSequence getSummary() { 139 TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = 140 getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false); 141 TimeZoneCapabilities capabilities = timeZoneCapabilitiesAndConfig.getCapabilities(); 142 int configureGeoDetectionEnabledCapability = 143 capabilities.getConfigureGeoDetectionEnabledCapability(); 144 TimeZoneConfiguration configuration = timeZoneCapabilitiesAndConfig.getConfiguration(); 145 146 int summaryResId; 147 if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_SUPPORTED) { 148 // The preference should not be visible, but text is referenced in case this changes. 149 summaryResId = R.string.location_time_zone_detection_not_supported; 150 } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_ALLOWED) { 151 // The preference should not be visible, but text is referenced in case this changes. 152 summaryResId = R.string.location_time_zone_detection_not_allowed; 153 } else if (configureGeoDetectionEnabledCapability == CAPABILITY_NOT_APPLICABLE) { 154 // The TimeZoneCapabilities deliberately doesn't provide information about why the user 155 // doesn't have the capability, but the user's "location enabled" being off and the 156 // global automatic detection setting will always be considered overriding reasons why 157 // location time zone detection cannot be used. 158 if (!mLocationManager.isLocationEnabled()) { 159 summaryResId = R.string.location_app_permission_summary_location_off; 160 } else if (!configuration.isAutoDetectionEnabled()) { 161 summaryResId = R.string.location_time_zone_detection_auto_is_off; 162 } else { 163 // This is in case there are other reasons in future why location time zone 164 // detection is not applicable. 165 summaryResId = R.string.location_time_zone_detection_not_applicable; 166 } 167 } else if (configureGeoDetectionEnabledCapability == CAPABILITY_POSSESSED) { 168 // If capability is possessed, toggle status already tells all the information needed. 169 // Returning null will make previous text stick on toggling. 170 // See AbstractPreferenceController#refreshSummary. 171 return ""; 172 } else { 173 // This is unexpected: getAvailabilityStatus() should ensure that the UI element isn't 174 // even shown for known cases, or the capability is unknown. 175 throw new IllegalStateException("Unexpected configureGeoDetectionEnabledCapability=" 176 + configureGeoDetectionEnabledCapability); 177 } 178 return mContext.getString(summaryResId); 179 } 180 181 @Override onChange()182 public void onChange() { 183 refreshUi(); 184 } 185 refreshUi()186 private void refreshUi() { 187 // Force a refresh of cached user capabilities and config before refreshing the summary. 188 getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ true); 189 refreshSummary(mPreference); 190 } 191 192 /** 193 * Returns the current user capabilities and configuration. {@code forceRefresh} can be {@code 194 * true} to discard any cached copy. 195 */ getTimeZoneCapabilitiesAndConfig(boolean forceRefresh)196 private TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig(boolean forceRefresh) { 197 if (forceRefresh || mTimeZoneCapabilitiesAndConfig == null) { 198 mTimeZoneCapabilitiesAndConfig = mTimeManager.getTimeZoneCapabilitiesAndConfig(); 199 } 200 return mTimeZoneCapabilitiesAndConfig; 201 } 202 } 203