/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.datetime; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; import android.app.time.DetectorStatusTypes; import android.app.time.LocationTimeZoneAlgorithmStatus; import android.app.time.TelephonyTimeZoneAlgorithmStatus; import android.app.time.TimeManager; import android.app.time.TimeZoneCapabilities; import android.app.time.TimeZoneCapabilitiesAndConfig; import android.app.time.TimeZoneDetectorStatus; import android.content.Context; import android.service.timezone.TimeZoneProviderStatus; import android.service.timezone.TimeZoneProviderStatus.DependencyStatus; import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SubSettingLauncher; import com.android.settings.location.LocationSettings; import com.android.settingslib.widget.BannerMessagePreference; import java.util.concurrent.Executor; /** * The controller for the "location time zone detection" entry in the Location settings * screen. */ public class LocationProviderStatusPreferenceController extends BasePreferenceController implements TimeManager.TimeZoneDetectorListener { private final TimeManager mTimeManager; private BannerMessagePreference mPreference = null; public LocationProviderStatusPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mTimeManager = context.getSystemService(TimeManager.class); Executor mainExecutor = context.getMainExecutor(); mTimeManager.addTimeZoneDetectorListener(mainExecutor, this); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(getPreferenceKey()); assert mPreference != null; mPreference .setPositiveButtonText( R.string.location_time_zone_provider_fix_dialog_ok_button) .setPositiveButtonOnClickListener(v -> launchLocationSettings()); } @Override public int getAvailabilityStatus() { // Checks that the summary is non-empty as most status strings are optional. If a status // string is empty, we ignore the status. if (!TextUtils.isEmpty(getSummary())) { return AVAILABLE_UNSEARCHABLE; } return CONDITIONALLY_UNAVAILABLE; } private void launchLocationSettings() { new SubSettingLauncher(mContext) .setDestination(LocationSettings.class.getName()) .setSourceMetricsCategory(getMetricsCategory()) .launch(); } // Android has up to two location time zone providers (LTZPs) which can // (optionally) report their status along several dimensions. Typically there is // only one LTZP on a device, the primary. The UI here only reports status for one // LTZP. This UI logic prioritizes the primary if there is a "bad" status for both. @Nullable private TimeZoneProviderStatus getLtzpStatusToReport() { LocationTimeZoneAlgorithmStatus status = mTimeManager.getTimeZoneCapabilitiesAndConfig().getDetectorStatus() .getLocationTimeZoneAlgorithmStatus(); @Nullable TimeZoneProviderStatus primary = status.getPrimaryProviderReportedStatus(); @Nullable TimeZoneProviderStatus secondary = status.getSecondaryProviderReportedStatus(); if (primary != null && secondary != null) { return pickWorstLtzpStatus(primary, secondary); } else if (primary != null) { return primary; } else { return secondary; } } private static TimeZoneProviderStatus pickWorstLtzpStatus( TimeZoneProviderStatus primary, TimeZoneProviderStatus secondary) { int primaryScore = scoreLtzpStatus(primary); int secondaryScore = scoreLtzpStatus(secondary); return primaryScore >= secondaryScore ? primary : secondary; } private static int scoreLtzpStatus(TimeZoneProviderStatus providerStatus) { @DependencyStatus int locationStatus = providerStatus.getLocationDetectionDependencyStatus(); if (locationStatus <= DEPENDENCY_STATUS_OK) { return 0; } // The enum values currently correspond well to severity. return providerStatus.getLocationDetectionDependencyStatus(); } @Override public void onChange() { if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE); refreshSummary(mPreference); } } @Override public CharSequence getSummary() { final TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = mTimeManager.getTimeZoneCapabilitiesAndConfig(); final TimeZoneDetectorStatus detectorStatus = timeZoneCapabilitiesAndConfig.getDetectorStatus(); final TimeZoneCapabilities timeZoneCapabilities = timeZoneCapabilitiesAndConfig.getCapabilities(); if (!timeZoneCapabilities.isUseLocationEnabled() && hasLocationTimeZoneNoTelephonyFallback(detectorStatus)) { return mContext.getString( R.string.location_time_zone_detection_status_summary_blocked_by_settings); } TimeZoneProviderStatus ltzpStatus = getLtzpStatusToReport(); if (ltzpStatus == null) { return ""; } @DependencyStatus int locationStatus = ltzpStatus.getLocationDetectionDependencyStatus(); if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) { return mContext.getString( R.string.location_time_zone_detection_status_summary_blocked_by_settings); } if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS) { return mContext.getString( R.string.location_time_zone_detection_status_summary_degraded_by_settings); } if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) { return mContext.getString( R.string.location_time_zone_detection_status_summary_blocked_by_environment); } if (locationStatus == TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE) { return mContext.getString( R.string.location_time_zone_detection_status_summary_temporarily_unavailable); } // LTZP-reported network connectivity and time zone resolution statuses are currently // ignored. Partners can tweak this logic if they also want to report these to users. return ""; } /** package */ static boolean hasLocationTimeZoneNoTelephonyFallback(TimeZoneDetectorStatus detectorStatus) { final LocationTimeZoneAlgorithmStatus locationStatus = detectorStatus.getLocationTimeZoneAlgorithmStatus(); final TelephonyTimeZoneAlgorithmStatus telephonyStatus = detectorStatus.getTelephonyTimeZoneAlgorithmStatus(); return telephonyStatus.getAlgorithmStatus() == DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED && locationStatus.getStatus() != DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; } }