1 /* 2 * Copyright (C) 2021 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 17 package com.android.car.settings.location; 18 19 import android.car.drivingstate.CarUxRestrictions; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.widget.Toast; 23 24 import androidx.annotation.VisibleForTesting; 25 26 import com.android.car.settings.Flags; 27 import com.android.car.settings.R; 28 import com.android.car.settings.common.ConfirmationDialogFragment; 29 import com.android.car.settings.common.FragmentController; 30 import com.android.car.ui.preference.CarUiSwitchPreference; 31 32 /** 33 * Enables/disables ADAS (Advanced Driver-assistance systems) GNSS bypass via SwitchPreference. 34 * 35 * <p>This switch is not affected by {@link android.os.UserManager#DISALLOW_CONFIG_LOCATION} or 36 * {@link android.os.UserManager#DISALLOW_SHARE_LOCATION} to prevent a device policy manager from 37 * changing settings that can negatively impact the safety of the driver. 38 */ 39 public class AdasLocationSwitchPreferenceController extends 40 LocationStateListenerBasePreferenceController<CarUiSwitchPreference> { 41 private static final String AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_NAME = 42 "config_defaultAdasGnssLocationEnabled"; 43 private static final String AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_TYPE = "bool"; 44 private static final String AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_PACKAGE = "android"; 45 46 @VisibleForTesting 47 boolean mIsClickable; 48 @VisibleForTesting 49 boolean mIsVisible; 50 AdasLocationSwitchPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)51 public AdasLocationSwitchPreferenceController(Context context, String preferenceKey, 52 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 53 super(context, preferenceKey, fragmentController, uxRestrictions); 54 } 55 56 @Override getPreferenceType()57 protected Class<CarUiSwitchPreference> getPreferenceType() { 58 return CarUiSwitchPreference.class; 59 } 60 61 @Override getDefaultAvailabilityStatus()62 protected int getDefaultAvailabilityStatus() { 63 if (Flags.requiredInfotainmentAppsSettingsPage()) { 64 if (!mIsVisible) { 65 return CONDITIONALLY_UNAVAILABLE; 66 } 67 if (!getIsPowerPolicyOn() || getLocationManager().isLocationEnabled()) { 68 return AVAILABLE_FOR_VIEWING; 69 } 70 return AVAILABLE; 71 } else { 72 return mIsClickable && getIsPowerPolicyOn() && !getLocationManager().isLocationEnabled() 73 ? AVAILABLE 74 : AVAILABLE_FOR_VIEWING; 75 } 76 } 77 78 @Override updateState(CarUiSwitchPreference preference)79 protected void updateState(CarUiSwitchPreference preference) { 80 preference.setChecked(getLocationManager().isAdasGnssLocationEnabled()); 81 } 82 83 @Override handlePreferenceChanged(CarUiSwitchPreference preference, Object newValue)84 protected boolean handlePreferenceChanged(CarUiSwitchPreference preference, Object newValue) { 85 if (Flags.requiredInfotainmentAppsSettingsPage()) { 86 getLocationManager().setAdasGnssLocationEnabled((Boolean) newValue); 87 return true; 88 } else { 89 if (getLocationManager().isAdasGnssLocationEnabled()) { 90 getFragmentController().showDialog(getConfirmationDialog(), 91 ConfirmationDialogFragment.TAG); 92 return false; 93 } else { 94 getLocationManager().setAdasGnssLocationEnabled((Boolean) newValue); 95 } 96 return true; 97 } 98 } 99 100 @Override onCreateInternal()101 protected void onCreateInternal() { 102 addDefaultBypassLocationStateListener(); 103 setMainLocationStateListener((isEnabled) -> { 104 // Turns ADAS location on when main location switch is on. Location service do 105 // not support the case where main location switch on and ADAS location off. 106 if (isEnabled) { 107 getLocationManager().setAdasGnssLocationEnabled(true); 108 } 109 }); 110 addDefaultPowerPolicyListener(); 111 112 if (Flags.requiredInfotainmentAppsSettingsPage()) { 113 Resources res = Resources.getSystem(); 114 int resId = res.getIdentifier( 115 AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_NAME, 116 AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_TYPE, 117 AUTOMOTIVE_LOCATION_BYPASS_RESOURCE_PACKAGE); 118 boolean defaultLocationBypassEnabled = res.getBoolean(resId); 119 // If by default automotive location bypass is on, then 120 // config_show_location_required_apps_toggle will dictate the visibility. 121 // Otherwise the toggle must be visible. 122 mIsVisible = defaultLocationBypassEnabled 123 ? getContext().getResources().getBoolean( 124 R.bool.config_show_location_required_apps_toggle) 125 : true; 126 } else { 127 mIsClickable = getContext().getResources() 128 .getBoolean(R.bool.config_allow_adas_location_switch_clickable); 129 } 130 131 setClickableWhileDisabled(getPreference(), /* clickable= */true, preference -> { 132 if (Flags.requiredInfotainmentAppsSettingsPage()) { 133 if (!getIsPowerPolicyOn()) { 134 Toast.makeText(getContext(), R.string.power_component_disabled, 135 Toast.LENGTH_LONG).show(); 136 } 137 } else { 138 if (!mIsClickable) { 139 getFragmentController().showDialog(getToggleDisabledDialog(), 140 ConfirmationDialogFragment.TAG); 141 return; 142 } 143 if (!getIsPowerPolicyOn()) { 144 Toast.makeText(getContext(), R.string.power_component_disabled, 145 Toast.LENGTH_LONG).show(); 146 } 147 } 148 }); 149 } 150 151 /** 152 * Assigns confirm action as negative button listener and cancel action as positive button 153 * listener, because the UX design requires the cancel button has to be on right and the confirm 154 * button on left. 155 */ getConfirmationDialog()156 private ConfirmationDialogFragment getConfirmationDialog() { 157 return new ConfirmationDialogFragment.Builder(getContext()) 158 .setMessage(R.string.adas_location_toggle_off_warning) 159 .setNegativeButton( 160 R.string.adas_location_toggle_confirm_label, 161 arguments -> { 162 // This if statement is included because the power policy handler runs 163 // slightly after the UI is initialized. Therefore, there's a small 164 // timeframe for the user to toggle the switch before the UI updates 165 // and disables the switch because the power policy is off. This if 166 // statement mitigates this issue by reverifying the power policy 167 // status. 168 if (getIsPowerPolicyOn()) { 169 getLocationManager().setAdasGnssLocationEnabled(false); 170 } 171 }) 172 .setPositiveButton(android.R.string.cancel, /* confirmListener= */ null) 173 .build(); 174 } 175 176 private ConfirmationDialogFragment getToggleDisabledDialog() { 177 return new ConfirmationDialogFragment.Builder(getContext()) 178 .setMessage(R.string.adas_location_toggle_popup_summary) 179 .setPositiveButton(android.R.string.ok, /* confirmListener= */ null) 180 .build(); 181 } 182 } 183