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