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