1 /*
2  * Copyright (C) 2015 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.tv.settings.system;
18 
19 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
20 import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;
21 
22 import android.app.tvsettings.TvSettingsEnums;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.location.LocationManager;
28 import android.os.BatteryManager;
29 import android.os.Bundle;
30 import android.os.Process;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.provider.Settings;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import androidx.annotation.Keep;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceCategory;
40 import androidx.preference.PreferenceGroup;
41 import androidx.preference.PreferenceScreen;
42 
43 import com.android.settingslib.RestrictedLockUtils;
44 import com.android.settingslib.RestrictedLockUtilsInternal;
45 import com.android.settingslib.RestrictedPreference;
46 import com.android.settingslib.location.RecentLocationApps;
47 import com.android.tv.settings.R;
48 import com.android.tv.settings.SettingsPreferenceFragment;
49 import com.android.tv.settings.device.apps.AppManagementFragment;
50 import com.android.tv.settings.overlay.FlavorUtils;
51 import com.android.tv.settings.widget.SwitchWithSoundPreference;
52 import com.android.tv.twopanelsettings.SummaryListPreference;
53 
54 import java.util.ArrayList;
55 import java.util.Comparator;
56 import java.util.List;
57 
58 /**
59  * The location settings screen in TV settings.
60  */
61 @Keep
62 public class LocationFragment extends SettingsPreferenceFragment implements
63         Preference.OnPreferenceChangeListener {
64 
65     private static final String TAG = "LocationFragment";
66 
67     private static final String LOCATION_MODE_WIFI = "wifi";
68     private static final String LOCATION_MODE_OFF = "off";
69 
70     private static final String KEY_LOCATION_MODE = "locationMode";
71     private static final String KEY_WIFI_ALWAYS_SCAN = "wifi_always_scan";
72 
73     private static final String MODE_CHANGING_ACTION =
74             "com.android.settings.location.MODE_CHANGING";
75     private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
76     private static final String NEW_MODE_KEY = "NEW_MODE";
77 
78     private SummaryListPreference mLocationMode;
79     private RestrictedPreference mRestrictedLocationMode;
80     private SwitchWithSoundPreference mAlwaysScan;
81 
82     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
83         @Override
84         public void onReceive(Context context, Intent intent) {
85             if (Log.isLoggable(TAG, Log.DEBUG)) {
86                 Log.d(TAG, "Received location mode change intent: " + intent);
87             }
88             refreshLocationMode();
89         }
90     };
91 
newInstance()92     public static LocationFragment newInstance() {
93         return new LocationFragment();
94     }
95 
96     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)97     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
98         final Context themedContext = getPreferenceManager().getContext();
99         final PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(
100                 themedContext);
101         screen.setTitle(R.string.system_location);
102 
103         final RestrictedLockUtils.EnforcedAdmin admin = checkIfUserRestrictionEnforcedByAdmin();
104         if (admin == null) {
105             mLocationMode = new SummaryListPreference(themedContext);
106             screen.addPreference(mLocationMode);
107             mLocationMode.setKey(KEY_LOCATION_MODE);
108             mLocationMode.setPersistent(false);
109             mLocationMode.setTitle(R.string.location_status);
110             mLocationMode.setDialogTitle(R.string.location_status);
111             mLocationMode.setSummary("%s");
112             mLocationMode.setEntries(new CharSequence[] {
113                     getString(R.string.location_mode_wifi_description),
114                     getString(R.string.off)
115             });
116             mLocationMode.setEntryValues(new CharSequence[] {
117                     LOCATION_MODE_WIFI,
118                     LOCATION_MODE_OFF
119             });
120             mLocationMode.setSummaries(new CharSequence[] {
121                     getString(R.string.system_location_summary),
122                     null
123             });
124             mLocationMode.setOnPreferenceChangeListener(this);
125             mLocationMode.setOnPreferenceClickListener(
126                     preference -> {
127                         logEntrySelected(TvSettingsEnums.PRIVACY_LOCATION_STATUS);
128                         return false;
129                     });
130 
131             mLocationMode.setEnabled(!hasUserRestriction());
132         } else {
133             mRestrictedLocationMode = new RestrictedPreference(themedContext);
134             screen.addPreference(mRestrictedLocationMode);
135             mRestrictedLocationMode.setPersistent(false);
136             mRestrictedLocationMode.setTitle(R.string.location_status);
137             mRestrictedLocationMode.setDisabledByAdmin(admin);
138         }
139 
140         if (FlavorUtils.isTwoPanel(getContext())) {
141             mAlwaysScan = new SwitchWithSoundPreference(themedContext);
142             mAlwaysScan.setKey(KEY_WIFI_ALWAYS_SCAN);
143             mAlwaysScan.setTitle(R.string.wifi_setting_always_scan);
144             mAlwaysScan.setSummary(R.string.wifi_setting_always_scan_context);
145             screen.addPreference(mAlwaysScan);
146             mAlwaysScan.setOnPreferenceChangeListener(this);
147         }
148         final PreferenceCategory recentRequests = new PreferenceCategory(themedContext);
149         screen.addPreference(recentRequests);
150         recentRequests.setTitle(R.string.location_category_recent_location_requests);
151 
152         List<RecentLocationApps.Request> recentLocationRequests =
153                 new RecentLocationApps(themedContext).getAppList(true);
154         List<Preference> recentLocationPrefs = new ArrayList<>(recentLocationRequests.size());
155         for (final RecentLocationApps.Request request : recentLocationRequests) {
156             Preference pref = new Preference(themedContext);
157             pref.setIcon(request.icon);
158             pref.setTitle(request.label);
159             // Most Android TV devices don't have built-in batteries and we ONLY show "High/Low
160             // battery use" for devices with built-in batteries when they are not plugged-in.
161             final BatteryManager batteryManager = (BatteryManager) getContext()
162                     .getSystemService(Context.BATTERY_SERVICE);
163             if (batteryManager != null && !batteryManager.isCharging()) {
164                 if (request.isHighBattery) {
165                     pref.setSummary(R.string.location_high_battery_use);
166                 } else {
167                     pref.setSummary(R.string.location_low_battery_use);
168                 }
169             }
170             pref.setOnPreferenceClickListener(
171                     preference -> {
172                         logEntrySelected(TvSettingsEnums.PRIVACY_LOCATION_REQUESTED_APP);
173                         return false;
174                     });
175             pref.setFragment(AppManagementFragment.class.getName());
176             AppManagementFragment.prepareArgs(pref.getExtras(), request.packageName);
177             recentLocationPrefs.add(pref);
178         }
179 
180         if (recentLocationRequests.size() > 0) {
181             addPreferencesSorted(recentLocationPrefs, recentRequests);
182         } else {
183             // If there's no item to display, add a "No recent apps" item.
184             Preference banner = new Preference(themedContext);
185             banner.setTitle(R.string.location_no_recent_apps);
186             banner.setSelectable(false);
187             recentRequests.addPreference(banner);
188         }
189 
190         // TODO: are location services relevant on TV?
191 
192         setPreferenceScreen(screen);
193     }
194 
hasUserRestriction()195     private boolean hasUserRestriction() {
196         final UserManager um = UserManager.get(getContext());
197         return um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)
198                 || um.hasUserRestriction(UserManager.DISALLOW_CONFIG_LOCATION);
199     }
200 
checkIfUserRestrictionEnforcedByAdmin()201     private RestrictedLockUtils.EnforcedAdmin checkIfUserRestrictionEnforcedByAdmin() {
202         final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
203                 .checkIfRestrictionEnforced(getContext(),
204                         UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
205 
206         if (admin != null) {
207             return admin;
208         }
209 
210         return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(),
211                 UserManager.DISALLOW_CONFIG_LOCATION, UserHandle.myUserId());
212     }
213 
214     // When selecting the location preference, LeanbackPreferenceFragment
215     // creates an inner view with the selection options; that's when we want to
216     // register our receiver, bacause from now on user can change the location
217     // providers.
218     @Override
onCreate(Bundle savedInstanceState)219     public void onCreate(Bundle savedInstanceState) {
220         super.onCreate(savedInstanceState);
221         getActivity().registerReceiver(mReceiver,
222                 new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
223         refreshLocationMode();
224     }
225 
226     @Override
onResume()227     public void onResume() {
228         super.onResume();
229         updateConnectivity();
230     }
231 
232     @Override
onDestroy()233     public void onDestroy() {
234         super.onDestroy();
235         getActivity().unregisterReceiver(mReceiver);
236     }
237 
addPreferencesSorted(List<Preference> prefs, PreferenceGroup container)238     private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
239         // If there's some items to display, sort the items and add them to the container.
240         prefs.sort(Comparator.comparing(lhs -> lhs.getTitle().toString()));
241         for (Preference entry : prefs) {
242             container.addPreference(entry);
243         }
244     }
245 
246     @Override
onPreferenceChange(Preference preference, Object newValue)247     public boolean onPreferenceChange(Preference preference, Object newValue) {
248         if (TextUtils.equals(preference.getKey(), KEY_LOCATION_MODE)) {
249             int mode = Settings.Secure.LOCATION_MODE_OFF;
250             if (TextUtils.equals((CharSequence) newValue, LOCATION_MODE_WIFI)) {
251                 mode = Settings.Secure.LOCATION_MODE_ON;
252                 logEntrySelected(TvSettingsEnums.PRIVACY_LOCATION_STATUS_USE_WIFI);
253             } else if (TextUtils.equals((CharSequence) newValue, LOCATION_MODE_OFF)) {
254                 mode = Settings.Secure.LOCATION_MODE_OFF;
255                 logEntrySelected(TvSettingsEnums.PRIVACY_LOCATION_STATUS_OFF);
256             } else {
257                 Log.wtf(TAG, "Tried to set unknown location mode!");
258             }
259 
260             writeLocationMode(mode);
261             refreshLocationMode();
262         }
263         return true;
264     }
265 
266     @Override
onPreferenceTreeClick(Preference preference)267     public boolean onPreferenceTreeClick(Preference preference) {
268         if (TextUtils.equals(preference.getKey(), KEY_WIFI_ALWAYS_SCAN)) {
269             Settings.Global.putInt(getActivity().getContentResolver(),
270                     Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
271                     mAlwaysScan.isChecked() ? 1 : 0);
272             logToggleInteracted(
273                     TvSettingsEnums.PRIVACY_LOCATION_ALWAYS_SCANNING_NETWORKS,
274                     mAlwaysScan.isChecked());
275             updateConnectivity();
276         }
277         return super.onPreferenceTreeClick(preference);
278     }
279 
writeLocationMode(int mode)280     private void writeLocationMode(int mode) {
281         int currentMode = Settings.Secure.getInt(getActivity().getContentResolver(),
282                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
283         Intent intent = new Intent(MODE_CHANGING_ACTION);
284         intent.putExtra(CURRENT_MODE_KEY, currentMode);
285         intent.putExtra(NEW_MODE_KEY, mode);
286         getActivity().sendBroadcast(intent, android.Manifest.permission.WRITE_SECURE_SETTINGS);
287         getActivity().getSystemService(LocationManager.class).setLocationEnabledForUser(
288                 mode != Settings.Secure.LOCATION_MODE_OFF,
289                 Process.myUserHandle());
290     }
291 
refreshLocationMode()292     private void refreshLocationMode() {
293         boolean locationEnabled = getActivity().getSystemService(LocationManager.class)
294                 .isLocationEnabled();
295         if (mLocationMode != null) {
296             if (locationEnabled) {
297                 mLocationMode.setValue(LOCATION_MODE_WIFI);
298             } else {
299                 mLocationMode.setValue(LOCATION_MODE_OFF);
300             }
301         }
302         if (mRestrictedLocationMode != null) {
303             if (locationEnabled) {
304                 mRestrictedLocationMode
305                         .setSummary(getString(R.string.location_mode_wifi_description));
306             } else {
307                 mRestrictedLocationMode.setSummary(getString(R.string.off));
308             }
309         }
310     }
311 
updateConnectivity()312     private void updateConnectivity() {
313         if (FlavorUtils.isTwoPanel(getContext())) {
314             int scanAlwaysAvailable = Settings.Global.getInt(getActivity().getContentResolver(),
315                     Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
316             mAlwaysScan.setChecked(scanAlwaysAvailable == 1);
317         }
318     }
319 
320     @Override
getPageId()321     protected int getPageId() {
322         return TvSettingsEnums.PRIVACY_LOCATION;
323     }
324 }
325