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