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 import static android.os.UserManager.DISALLOW_CONFIG_LOCATION;
21 import static android.os.UserManager.DISALLOW_SHARE_LOCATION;
22 
23 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG;
24 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm;
25 
26 import android.car.drivingstate.CarUxRestrictions;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.location.LocationManager;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.car.settings.R;
36 import com.android.car.settings.common.ConfirmationDialogFragment;
37 import com.android.car.settings.common.FragmentController;
38 import com.android.car.settings.common.PowerPolicyListener;
39 import com.android.car.settings.common.PreferenceController;
40 import com.android.car.settings.enterprise.EnterpriseUtils;
41 import com.android.car.ui.preference.CarUiTwoActionSwitchPreference;
42 
43 /**
44  * Enables/disables ADAS (Advanced Driver-assistance systems) GNSS bypass via SwitchPreference.
45  */
46 public class AdasLocationSwitchPreferenceController extends
47         PreferenceController<CarUiTwoActionSwitchPreference> {
48     private final Context mContext;
49     private final LocationManager mLocationManager;
50 
51     private final BroadcastReceiver mAdasReceiver = new BroadcastReceiver() {
52         @Override
53         public void onReceive(Context context, Intent intent) {
54             refreshUi();
55         }
56     };
57 
58     private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() {
59         @Override
60         public void onReceive(Context context, Intent intent) {
61             // Turns Driver assistance on when main location switch is on. Location service don't
62             // support the case where main location switch on and Driver assistance off
63             if (mLocationManager.isLocationEnabled()) {
64                 mLocationManager.setAdasGnssLocationEnabled(true);
65             }
66             refreshUi();
67         }
68     };
69 
70     private static final IntentFilter INTENT_FILTER_ADAS_GNSS_ENABLED_CHANGED = new IntentFilter(
71             LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED);
72 
73     private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = new IntentFilter(
74             LocationManager.MODE_CHANGED_ACTION);
75     @VisibleForTesting
76     final PowerPolicyListener mPowerPolicyListener;
77 
AdasLocationSwitchPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)78     public AdasLocationSwitchPreferenceController(Context context, String preferenceKey,
79             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
80         super(context, preferenceKey, fragmentController, uxRestrictions);
81         mContext = context;
82         mLocationManager = context.getSystemService(LocationManager.class);
83         mPowerPolicyListener = new PowerPolicyListener(context, LOCATION,
84                 isOn -> {
85                     handlePowerPolicyChange(getPreference(), isOn);
86                 });
87     }
88 
89     @Override
getPreferenceType()90     protected Class<CarUiTwoActionSwitchPreference> getPreferenceType() {
91         return CarUiTwoActionSwitchPreference.class;
92     }
93 
94     @Override
getAvailabilityStatus()95     protected int getAvailabilityStatus() {
96         if (hasUserRestrictionByDpm(getContext(), DISALLOW_CONFIG_LOCATION)
97                 || hasUserRestrictionByDpm(getContext(), DISALLOW_SHARE_LOCATION)) {
98             return AVAILABLE_FOR_VIEWING;
99         }
100         return AVAILABLE;
101     }
102 
103     @Override
updateState(CarUiTwoActionSwitchPreference preference)104     protected void updateState(CarUiTwoActionSwitchPreference preference) {
105         updateSwitchPreference(preference, mLocationManager.isAdasGnssLocationEnabled());
106     }
107 
108     @Override
onCreateInternal()109     protected void onCreateInternal() {
110         getPreference().setOnSecondaryActionClickListener(isChecked -> {
111             if (!isChecked) {
112                 getFragmentController().showDialog(getConfirmationDialog(),
113                         ConfirmationDialogFragment.TAG);
114                 refreshUi();
115             } else {
116                 mLocationManager.setAdasGnssLocationEnabled(true);
117             }
118         });
119         setClickableWhileDisabled(getPreference(), /* clickable= */ true, p -> {
120             if (hasUserRestrictionByDpm(getContext(), DISALLOW_SHARE_LOCATION)) {
121                 showActionDisabledByAdminDialog(DISALLOW_SHARE_LOCATION);
122                 return;
123             }
124             if (hasUserRestrictionByDpm(getContext(), DISALLOW_CONFIG_LOCATION)) {
125                 showActionDisabledByAdminDialog(DISALLOW_CONFIG_LOCATION);
126                 return;
127             }
128         });
129     }
130 
131     @Override
onStartInternal()132     protected void onStartInternal() {
133         mContext.registerReceiver(mAdasReceiver, INTENT_FILTER_ADAS_GNSS_ENABLED_CHANGED);
134         mContext.registerReceiver(mLocationReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED);
135     }
136 
137     @Override
onResumeInternal()138     protected void onResumeInternal() {
139         mPowerPolicyListener.handleCurrentPolicy();
140     }
141 
142     @Override
onStopInternal()143     protected void onStopInternal() {
144         mContext.unregisterReceiver(mAdasReceiver);
145         mContext.unregisterReceiver(mLocationReceiver);
146     }
147 
148     @Override
onDestroyInternal()149     protected void onDestroyInternal() {
150         mPowerPolicyListener.release();
151     }
152 
updateSwitchPreference(CarUiTwoActionSwitchPreference preference, boolean enabled)153     private void updateSwitchPreference(CarUiTwoActionSwitchPreference preference,
154             boolean enabled) {
155         if (enabled && hasUserRestrictionByDpm(getContext(), DISALLOW_SHARE_LOCATION)) {
156             preference.setSecondaryActionChecked(false);
157             preference.setSecondaryActionEnabled(false);
158         } else {
159             preference.setSecondaryActionChecked(enabled);
160             preference.setSecondaryActionEnabled(!mLocationManager.isLocationEnabled());
161         }
162     }
163 
handlePowerPolicyChange(CarUiTwoActionSwitchPreference preference, boolean enabled)164     private void handlePowerPolicyChange(CarUiTwoActionSwitchPreference preference,
165             boolean enabled) {
166         if (hasUserRestrictionByDpm(getContext(), DISALLOW_CONFIG_LOCATION)
167                 || hasUserRestrictionByDpm(getContext(), DISALLOW_SHARE_LOCATION)
168                 ||  mLocationManager.isLocationEnabled()) {
169             preference.setSecondaryActionEnabled(false);
170             return;
171         }
172         preference.setSecondaryActionEnabled(enabled);
173     }
174 
175     /**
176      * Assigns confirm action as negative button listener and cancel action as positive button
177      * listener, because the UX design requires the cancel button has to be on right and the confirm
178      * button on left.
179      */
getConfirmationDialog()180     private ConfirmationDialogFragment getConfirmationDialog() {
181         return new ConfirmationDialogFragment.Builder(getContext())
182                 .setMessage(mContext
183                         .getString(R.string.location_driver_assistance_toggle_off_warning))
184                 .setNegativeButton(mContext
185                         .getString(R.string.driver_assistance_warning_confirm_label), arguments -> {
186                                 mLocationManager.setAdasGnssLocationEnabled(false);
187                         })
188                 .setPositiveButton(android.R.string.cancel,
189                         /* rejectListener= */ null)
190                 .build();
191     }
192 
showActionDisabledByAdminDialog(String restrictionType)193     private void showActionDisabledByAdminDialog(String restrictionType) {
194         getFragmentController().showDialog(
195                 EnterpriseUtils.getActionDisabledByAdminDialog(getContext(),
196                         restrictionType),
197                 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
198     }
199 }
200