1 /* 2 * Copyright (C) 2022 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.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; 19 20 import android.app.time.DetectorStatusTypes; 21 import android.app.time.LocationTimeZoneAlgorithmStatus; 22 import android.app.time.TelephonyTimeZoneAlgorithmStatus; 23 import android.app.time.TimeManager; 24 import android.app.time.TimeZoneCapabilities; 25 import android.app.time.TimeZoneCapabilitiesAndConfig; 26 import android.app.time.TimeZoneDetectorStatus; 27 import android.content.Context; 28 import android.service.timezone.TimeZoneProviderStatus; 29 import android.service.timezone.TimeZoneProviderStatus.DependencyStatus; 30 import android.text.TextUtils; 31 32 import androidx.annotation.Nullable; 33 import androidx.preference.PreferenceScreen; 34 35 import com.android.settings.R; 36 import com.android.settings.core.BasePreferenceController; 37 import com.android.settings.core.SubSettingLauncher; 38 import com.android.settings.location.LocationSettings; 39 import com.android.settingslib.widget.BannerMessagePreference; 40 41 import java.util.concurrent.Executor; 42 43 /** 44 * The controller for the "location time zone detection" entry in the Location settings 45 * screen. 46 */ 47 public class LocationProviderStatusPreferenceController 48 extends BasePreferenceController implements TimeManager.TimeZoneDetectorListener { 49 private final TimeManager mTimeManager; 50 51 private BannerMessagePreference mPreference = null; 52 LocationProviderStatusPreferenceController(Context context, String preferenceKey)53 public LocationProviderStatusPreferenceController(Context context, String preferenceKey) { 54 super(context, preferenceKey); 55 mTimeManager = context.getSystemService(TimeManager.class); 56 57 Executor mainExecutor = context.getMainExecutor(); 58 mTimeManager.addTimeZoneDetectorListener(mainExecutor, this); 59 } 60 61 @Override displayPreference(PreferenceScreen screen)62 public void displayPreference(PreferenceScreen screen) { 63 super.displayPreference(screen); 64 mPreference = screen.findPreference(getPreferenceKey()); 65 assert mPreference != null; 66 mPreference 67 .setPositiveButtonText( 68 R.string.location_time_zone_provider_fix_dialog_ok_button) 69 .setPositiveButtonOnClickListener(v -> launchLocationSettings()); 70 } 71 72 @Override getAvailabilityStatus()73 public int getAvailabilityStatus() { 74 // Checks that the summary is non-empty as most status strings are optional. If a status 75 // string is empty, we ignore the status. 76 if (!TextUtils.isEmpty(getSummary())) { 77 return AVAILABLE_UNSEARCHABLE; 78 } 79 return CONDITIONALLY_UNAVAILABLE; 80 } 81 launchLocationSettings()82 private void launchLocationSettings() { 83 new SubSettingLauncher(mContext) 84 .setDestination(LocationSettings.class.getName()) 85 .setSourceMetricsCategory(getMetricsCategory()) 86 .launch(); 87 } 88 89 // Android has up to two location time zone providers (LTZPs) which can 90 // (optionally) report their status along several dimensions. Typically there is 91 // only one LTZP on a device, the primary. The UI here only reports status for one 92 // LTZP. This UI logic prioritizes the primary if there is a "bad" status for both. 93 @Nullable getLtzpStatusToReport()94 private TimeZoneProviderStatus getLtzpStatusToReport() { 95 LocationTimeZoneAlgorithmStatus status = 96 mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus() 97 .getLocationTimeZoneAlgorithmStatus(); 98 @Nullable TimeZoneProviderStatus primary = status.getPrimaryProviderReportedStatus(); 99 @Nullable TimeZoneProviderStatus secondary = status.getSecondaryProviderReportedStatus(); 100 if (primary != null && secondary != null) { 101 return pickWorstLtzpStatus(primary, secondary); 102 } else if (primary != null) { 103 return primary; 104 } else { 105 return secondary; 106 } 107 } 108 pickWorstLtzpStatus( TimeZoneProviderStatus primary, TimeZoneProviderStatus secondary)109 private static TimeZoneProviderStatus pickWorstLtzpStatus( 110 TimeZoneProviderStatus primary, TimeZoneProviderStatus secondary) { 111 int primaryScore = scoreLtzpStatus(primary); 112 int secondaryScore = scoreLtzpStatus(secondary); 113 return primaryScore >= secondaryScore ? primary : secondary; 114 } 115 scoreLtzpStatus(TimeZoneProviderStatus providerStatus)116 private static int scoreLtzpStatus(TimeZoneProviderStatus providerStatus) { 117 @DependencyStatus int locationStatus = 118 providerStatus.getLocationDetectionDependencyStatus(); 119 if (locationStatus <= DEPENDENCY_STATUS_OK) { 120 return 0; 121 } 122 // The enum values currently correspond well to severity. 123 return providerStatus.getLocationDetectionDependencyStatus(); 124 } 125 126 @Override onChange()127 public void onChange() { 128 if (mPreference != null) { 129 mPreference.setVisible(getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE); 130 refreshSummary(mPreference); 131 } 132 } 133 134 @Override getSummary()135 public CharSequence getSummary() { 136 final TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = 137 mTimeManager.getTimeZoneCapabilitiesAndConfig(); 138 final TimeZoneDetectorStatus detectorStatus = 139 timeZoneCapabilitiesAndConfig.getDetectorStatus(); 140 final TimeZoneCapabilities timeZoneCapabilities = 141 timeZoneCapabilitiesAndConfig.getCapabilities(); 142 143 if (!timeZoneCapabilities.isUseLocationEnabled() 144 && hasLocationTimeZoneNoTelephonyFallback(detectorStatus)) { 145 return mContext.getString( 146 R.string.location_time_zone_detection_status_summary_blocked_by_settings); 147 } 148 149 TimeZoneProviderStatus ltzpStatus = getLtzpStatusToReport(); 150 if (ltzpStatus == null) { 151 return ""; 152 } 153 154 @DependencyStatus int locationStatus = ltzpStatus.getLocationDetectionDependencyStatus(); 155 156 if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) { 157 return mContext.getString( 158 R.string.location_time_zone_detection_status_summary_blocked_by_settings); 159 } 160 if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS) { 161 return mContext.getString( 162 R.string.location_time_zone_detection_status_summary_degraded_by_settings); 163 } 164 if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) { 165 return mContext.getString( 166 R.string.location_time_zone_detection_status_summary_blocked_by_environment); 167 } 168 if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE) { 169 return mContext.getString( 170 R.string.location_time_zone_detection_status_summary_temporarily_unavailable); 171 } 172 173 // LTZP-reported network connectivity and time zone resolution statuses are currently 174 // ignored. Partners can tweak this logic if they also want to report these to users. 175 176 return ""; 177 } 178 179 /** package */ hasLocationTimeZoneNoTelephonyFallback(TimeZoneDetectorStatus detectorStatus)180 static boolean hasLocationTimeZoneNoTelephonyFallback(TimeZoneDetectorStatus detectorStatus) { 181 final LocationTimeZoneAlgorithmStatus locationStatus = 182 detectorStatus.getLocationTimeZoneAlgorithmStatus(); 183 final TelephonyTimeZoneAlgorithmStatus telephonyStatus = 184 detectorStatus.getTelephonyTimeZoneAlgorithmStatus(); 185 return telephonyStatus.getAlgorithmStatus() 186 == DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED 187 && locationStatus.getStatus() 188 != DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; 189 } 190 } 191