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