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 static android.car.hardware.power.PowerComponent.LOCATION; 20 21 import android.car.drivingstate.CarUxRestrictions; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.location.LocationManager; 27 import android.widget.Toast; 28 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.car.settings.R; 32 import com.android.car.settings.common.ConfirmationDialogFragment; 33 import com.android.car.settings.common.FragmentController; 34 import com.android.car.settings.common.PowerPolicyListener; 35 import com.android.car.settings.common.PreferenceController; 36 import com.android.car.ui.preference.CarUiSwitchPreference; 37 38 /** 39 * Enables/disables ADAS (Advanced Driver-assistance systems) GNSS bypass via SwitchPreference. 40 * 41 * <p>This switch is not affected by {@link android.os.UserManager#DISALLOW_CONFIG_LOCATION} or 42 * {@link android.os.UserManager#DISALLOW_SHARE_LOCATION} to prevent a device policy manager from 43 * changing settings that can negatively impact the safety of the driver. 44 */ 45 public class AdasLocationSwitchPreferenceController extends 46 PreferenceController<CarUiSwitchPreference> { 47 private final LocationManager mLocationManager; 48 49 private final BroadcastReceiver mAdasReceiver = new BroadcastReceiver() { 50 @Override 51 public void onReceive(Context context, Intent intent) { 52 refreshUi(); 53 } 54 }; 55 56 private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() { 57 @Override 58 public void onReceive(Context context, Intent intent) { 59 // Turns Driver assistance on when main location switch is on. Location service don't 60 // support the case where main location switch on and Driver assistance off 61 if (mLocationManager.isLocationEnabled()) { 62 mLocationManager.setAdasGnssLocationEnabled(true); 63 } 64 refreshUi(); 65 } 66 }; 67 68 private static final IntentFilter INTENT_FILTER_ADAS_GNSS_ENABLED_CHANGED = 69 new IntentFilter(LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED); 70 71 private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = 72 new IntentFilter(LocationManager.MODE_CHANGED_ACTION); 73 @VisibleForTesting 74 final PowerPolicyListener mPowerPolicyListener; 75 @VisibleForTesting 76 boolean mIsClickable; 77 private boolean mIsPowerPolicyOn = true; 78 AdasLocationSwitchPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)79 public AdasLocationSwitchPreferenceController(Context context, String preferenceKey, 80 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 81 super(context, preferenceKey, fragmentController, uxRestrictions); 82 mLocationManager = context.getSystemService(LocationManager.class); 83 mPowerPolicyListener = new PowerPolicyListener(context, LOCATION, 84 isOn -> { 85 mIsPowerPolicyOn = isOn; 86 refreshUi(); 87 }); 88 } 89 90 @Override getPreferenceType()91 protected Class<CarUiSwitchPreference> getPreferenceType() { 92 return CarUiSwitchPreference.class; 93 } 94 95 @Override getDefaultAvailabilityStatus()96 protected int getDefaultAvailabilityStatus() { 97 return mIsClickable && mIsPowerPolicyOn && !mLocationManager.isLocationEnabled() 98 ? AVAILABLE 99 : AVAILABLE_FOR_VIEWING; 100 } 101 102 @Override updateState(CarUiSwitchPreference preference)103 protected void updateState(CarUiSwitchPreference preference) { 104 preference.setChecked(mLocationManager.isAdasGnssLocationEnabled()); 105 } 106 107 @Override onCreateInternal()108 protected void onCreateInternal() { 109 mIsClickable = getContext().getResources() 110 .getBoolean(R.bool.config_allow_adas_location_switch_clickable); 111 112 getPreference().setOnPreferenceChangeListener((pref, val) -> { 113 if (mLocationManager.isAdasGnssLocationEnabled()) { 114 getFragmentController().showDialog(getConfirmationDialog(), 115 ConfirmationDialogFragment.TAG); 116 return false; 117 } else { 118 mLocationManager.setAdasGnssLocationEnabled(true); 119 return true; 120 } 121 }); 122 123 setClickableWhileDisabled(getPreference(), /* clickable= */true, preference -> { 124 if (!mIsClickable) { 125 getFragmentController().showDialog(getToggleDisabledDialog(), 126 ConfirmationDialogFragment.TAG); 127 return; 128 } 129 if (!mIsPowerPolicyOn) { 130 Toast.makeText(getContext(), R.string.power_component_disabled, Toast.LENGTH_LONG) 131 .show(); 132 } 133 }); 134 } 135 136 @Override onStartInternal()137 protected void onStartInternal() { 138 getContext().registerReceiver(mAdasReceiver, INTENT_FILTER_ADAS_GNSS_ENABLED_CHANGED, 139 Context.RECEIVER_NOT_EXPORTED); 140 getContext().registerReceiver(mLocationReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED, 141 Context.RECEIVER_NOT_EXPORTED); 142 } 143 144 @Override onResumeInternal()145 protected void onResumeInternal() { 146 mPowerPolicyListener.handleCurrentPolicy(); 147 } 148 149 @Override onStopInternal()150 protected void onStopInternal() { 151 getContext().unregisterReceiver(mAdasReceiver); 152 getContext().unregisterReceiver(mLocationReceiver); 153 } 154 155 @Override onDestroyInternal()156 protected void onDestroyInternal() { 157 mPowerPolicyListener.release(); 158 } 159 160 /** 161 * Assigns confirm action as negative button listener and cancel action as positive button 162 * listener, because the UX design requires the cancel button has to be on right and the confirm 163 * button on left. 164 */ getConfirmationDialog()165 private ConfirmationDialogFragment getConfirmationDialog() { 166 return new ConfirmationDialogFragment.Builder(getContext()) 167 .setMessage(R.string.adas_location_toggle_off_warning) 168 .setNegativeButton( 169 R.string.adas_location_toggle_confirm_label, 170 arguments -> { 171 // This if statement is included because the power policy handler runs 172 // slightly after the UI is initialized. Therefore, there's a small 173 // timeframe for the user to toggle the switch before the UI updates 174 // and disables the switch because the power policy is off. This if 175 // statement mitigates this issue by reverifying the power policy 176 // status. 177 if (mIsPowerPolicyOn) { 178 mLocationManager.setAdasGnssLocationEnabled(false); 179 } 180 }) 181 .setPositiveButton(android.R.string.cancel, /* confirmListener= */ null) 182 .build(); 183 } 184 185 private ConfirmationDialogFragment getToggleDisabledDialog() { 186 return new ConfirmationDialogFragment.Builder(getContext()) 187 .setMessage(R.string.adas_location_toggle_popup_summary) 188 .setPositiveButton(android.R.string.ok, /* confirmListener= */ null) 189 .build(); 190 } 191 } 192