1 /* 2 * Copyright (C) 2014 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 com.android.tv.settings.R; 20 21 import com.android.tv.settings.ActionBehavior; 22 import com.android.tv.settings.ActionKey; 23 import com.android.tv.settings.BaseSettingsActivity; 24 import android.accounts.Account; 25 import android.accounts.AccountManager; 26 import com.android.tv.settings.util.SettingsHelper; 27 import com.android.tv.settings.dialog.old.Action; 28 import com.android.tv.settings.dialog.old.ActionAdapter; 29 import com.android.tv.settings.device.apps.AppManagementActivity; 30 31 import android.app.AppOpsManager; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManager; 36 import android.location.LocationManager; 37 import android.os.Bundle; 38 import android.preference.Preference; 39 import android.provider.Settings; 40 import android.util.Log; 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.List; 44 45 /** 46 * Controls location settings. 47 */ 48 public class LocationActivity extends BaseSettingsActivity implements ActionAdapter.Listener { 49 50 private static final String TAG = "LocationActivity"; 51 private static final boolean DEBUG = false; 52 53 /** 54 * Stores a BatterySipper object and records whether the sipper has been 55 * used. 56 */ 57 private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; 58 /** 59 * Package name of GmsCore 60 */ 61 private static final String GMS_PACKAGE = "com.google.android.gms"; 62 /** 63 * Class name of Google location settings 64 */ 65 private static final String GOOGLE_LOCATION_SETTINGS_CLASS = 66 "com.google.android.location.settings.GoogleLocationSettingsActivity"; 67 /** 68 * Account type for google accounts 69 */ 70 private static final String ACCOUNT_TYPE_GOOGLE = "com.google"; 71 72 /** 73 * The extra key whose value specifies the name of account to be operated 74 * on. 75 */ 76 static final String EXTRA_KEY_ACCOUNT = "com.google.android.location.settings.extra.account"; 77 78 private SettingsHelper mHelper; 79 private String mAccountName = null; 80 81 @Override 82 protected void onCreate(Bundle savedInstanceState) { 83 mHelper = new SettingsHelper(getApplicationContext()); 84 85 super.onCreate(savedInstanceState); 86 } 87 88 @Override 89 protected void onPause() { 90 super.onPause(); 91 } 92 93 @Override 94 protected void onDestroy() { 95 super.onDestroy(); 96 } 97 98 @Override 99 protected Object getInitialState() { 100 return ActionType.LOCATION_OVERVIEW; 101 } 102 103 @Override 104 protected void refreshActionList() { 105 mActions.clear(); 106 switch ((ActionType) mState) { 107 case LOCATION_OVERVIEW: 108 mActions.add(ActionType.LOCATION_STATUS.toAction( 109 mResources, mHelper.getStatusStringFromBoolean(isLocationEnabled()))); 110 if (isLocationEnabled()) { 111 mActions.add(ActionType.LOCATION_MODE.toAction(mResources, 112 getString(R.string.location_mode_wifi_description), false)); 113 mActions.add(ActionType.LOCATION_RECENT_REQUESTS.toAction(mResources)); 114 } 115 break; 116 case LOCATION_STATUS: 117 Action locationStatusOn = ActionType.ON.toAction(mResources); 118 locationStatusOn.setChecked(isLocationEnabled()); 119 Action locationStatusOff = ActionType.OFF.toAction(mResources); 120 locationStatusOff.setChecked(!isLocationEnabled()); 121 mActions.add(locationStatusOn); 122 mActions.add(locationStatusOff); 123 break; 124 case LOCATION_RECENT_REQUESTS: 125 mActions = getRecentRequestActions(); 126 if (mActions.size() == 0) { 127 mActions.add(ActionType.LOCATION_NO_RECENT_REQUESTS.toAction( 128 mResources, false)); 129 } 130 break; 131 case LOCATION_SERVICES: 132 mActions.add(ActionType.LOCATION_SERVICES_GOOGLE.toAction(mResources)); 133 break; 134 case LOCATION_SERVICES_GOOGLE: 135 mActions = getAccountsActions(); 136 break; 137 case LOCATION_SERVICES_GOOGLE_SETTINGS: 138 // TODO add on and off 139 mActions.add(ActionType.LOCATION_SERVICES_GOOGLE_REPORTING.toAction(mResources)); 140 mActions.add(ActionType.LOCATION_SERVICES_GOOGLE_HISTORY.toAction(mResources)); 141 break; 142 case LOCATION_SERVICES_GOOGLE_REPORTING: 143 case LOCATION_SERVICES_GOOGLE_HISTORY: 144 // TODO set checked here 145 mActions.add(ActionType.ON.toAction(mResources)); 146 mActions.add(ActionType.OFF.toAction(mResources)); 147 break; 148 default: 149 } 150 } 151 152 // TODO Remove this. Use our own UI 153 private void startHoloGoogleLocationServicesSettings() { 154 Intent i = new Intent(); 155 i.setClassName(GMS_PACKAGE, GOOGLE_LOCATION_SETTINGS_CLASS); 156 startActivity(i); 157 } 158 159 private ArrayList<Action> getAccountsActions(){ 160 ArrayList<Action> result = new ArrayList<Action>(); 161 Action.Builder builder = new Action.Builder(); 162 Account[] googleAccounts = ((AccountManager) getSystemService(Context.ACCOUNT_SERVICE)) 163 .getAccountsByType(ACCOUNT_TYPE_GOOGLE); 164 for (Account account : googleAccounts) { 165 result.add(builder.key(account.name).title(account.name).build()); 166 } 167 return result; 168 } 169 170 /** 171 * Fills a list of applications which queried location recently within 172 * specified time. TODO: add icons 173 */ 174 private ArrayList<Action> getRecentRequestActions() { 175 ArrayList<Action> result = new ArrayList<Action>(); 176 177 // Retrieve a location usage list from AppOps 178 AppOpsManager aoManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); 179 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps( 180 new int[] { 181 AppOpsManager.OP_MONITOR_LOCATION, 182 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 183 }); 184 long now = System.currentTimeMillis(); 185 for (AppOpsManager.PackageOps ops : appOps) { 186 Action action = getActionFromOps(now, ops); 187 if (action != null) { 188 result.add(action); 189 } 190 } 191 192 return result; 193 } 194 195 private Action getActionFromOps(long now, AppOpsManager.PackageOps ops) { 196 String packageName = ops.getPackageName(); 197 List<AppOpsManager.OpEntry> entries = ops.getOps(); 198 boolean highBattery = false; 199 boolean normalBattery = false; 200 201 // Earliest time for a location request to end and still be shown in 202 // list. 203 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 204 for (AppOpsManager.OpEntry entry : entries) { 205 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { 206 switch (entry.getOp()) { 207 case AppOpsManager.OP_MONITOR_LOCATION: 208 normalBattery = true; 209 break; 210 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: 211 highBattery = true; 212 break; 213 default: 214 break; 215 } 216 } 217 } 218 219 if (!highBattery && !normalBattery) { 220 if (DEBUG) { 221 Log.v(TAG, packageName + " hadn't used location within the time interval."); 222 } 223 return null; 224 } 225 226 Action.Builder builder = new Action.Builder(); 227 // The package is fresh enough, continue. 228 try { 229 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 230 packageName, PackageManager.GET_META_DATA); 231 if (appInfo.uid == ops.getUid()) { 232 builder.key(packageName) 233 .title(getPackageManager().getApplicationLabel(appInfo).toString()) 234 .description(highBattery ? getString(R.string.location_high_battery_use) 235 : getString(R.string.location_low_battery_use)); 236 } else if (DEBUG) { 237 Log.v(TAG, "package " + packageName + " with Uid " + ops.getUid() + 238 " belongs to another inactive account, ignored."); 239 } 240 } catch (PackageManager.NameNotFoundException e) { 241 Log.wtf(TAG, "Package not found: " + packageName); 242 } 243 return builder.build(); 244 } 245 246 private boolean isLocationEnabled() { 247 return Settings.Secure.getInt(getContentResolver(), 248 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) != 249 Settings.Secure.LOCATION_MODE_OFF; 250 } 251 252 @Override 253 protected void updateView() { 254 refreshActionList(); 255 switch ((ActionType) mState) { 256 case LOCATION_OVERVIEW: 257 setView(R.string.system_location, R.string.settings_app_name, 258 R.string.system_desc_location, R.drawable.ic_settings_location); 259 break; 260 case LOCATION_SERVICES_GOOGLE_SETTINGS: 261 setView(mAccountName, getPrevState() != null ? 262 ((ActionType) getPrevState()).getTitle(mResources) : null, 263 ((ActionType) mState).getDescription(mResources), 0); 264 break; 265 case ON: 266 case OFF: 267 case LOCATION_SERVICES_GOOGLE_REPORTING: 268 case LOCATION_SERVICES_GOOGLE_HISTORY: 269 break; 270 default: 271 setView(((ActionType) mState).getTitle(mResources), getPrevState() != null ? 272 ((ActionType) getPrevState()).getTitle(mResources) : null, 273 ((ActionType) mState).getDescription(mResources), 0); 274 break; 275 } 276 } 277 278 @Override 279 public void onActionClicked(Action action) { 280 // clicking on recent location access apps 281 switch ((ActionType) mState) { 282 case LOCATION_RECENT_REQUESTS: 283 // TODO handle no recent apps 284 String packageName = action.getKey(); 285 Intent i = AppManagementActivity.getLaunchIntent(packageName); 286 startActivity(i); 287 return; 288 289 case LOCATION_SERVICES_GOOGLE: 290 mAccountName = action.getTitle(); 291 setState(ActionType.LOCATION_SERVICES_GOOGLE_SETTINGS, true); 292 return; 293 default: 294 } 295 296 ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<ActionType, ActionBehavior>( 297 ActionType.class, ActionBehavior.class, action.getKey()); 298 final ActionType type = actionKey.getType(); 299 switch (type) { 300 case ON: 301 setProperty(true); 302 goBack(); 303 break; 304 case OFF: 305 setProperty(false); 306 goBack(); 307 break; 308 case LOCATION_SERVICES_GOOGLE_REPORTING: 309 startLocationReportingSettings(); 310 break; 311 case LOCATION_SERVICES_GOOGLE_HISTORY: 312 startLocationHistorySettings(); 313 break; 314 case LOCATION_SERVICES_GOOGLE: // TODO remove this here, it should 315 // fall into the default once we 316 // figure out how to now use the 317 // settings in GmsCore 318 startHoloGoogleLocationServicesSettings(); 319 break; 320 default: 321 setState(type, true); 322 break; 323 } 324 } 325 326 @Override 327 protected void setProperty(boolean enable) { 328 switch ((ActionType) mState) { 329 case LOCATION_STATUS: 330 setLocationMode(enable); 331 break; 332 default: 333 } 334 } 335 336 // TODO Location history settings is currently in 337 // com.google.android.location.settings.LocationHistorySettingsActivity and 338 // is non exported 339 private void startLocationHistorySettings() { 340 } 341 342 // TODO Location reporting settings is currently in 343 // com.google.android.location.settings.LocationReportingSettingsActivity 344 // and is non exported 345 private void startLocationReportingSettings() { 346 } 347 348 private void setLocationMode(boolean enable) { 349 if (enable) { 350 // TODO 351 // com.google.android.gms/com.google.android.location.network.ConfirmAlertActivity 352 // pops up when we turn this on. 353 Settings.Secure.putInt(getContentResolver(), Settings.Secure.LOCATION_MODE, 354 Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); 355 } else { 356 Settings.Secure.putInt(getContentResolver(), Settings.Secure.LOCATION_MODE, 357 Settings.Secure.LOCATION_MODE_OFF); 358 } 359 } 360 } 361