1 /* 2 * Copyright (C) 2009 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.settings.widget; 18 19 import android.app.PendingIntent; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProvider; 22 import android.bluetooth.BluetoothAdapter; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.database.ContentObserver; 28 import android.location.LocationManager; 29 import android.net.ConnectivityManager; 30 import android.net.Uri; 31 import android.net.wifi.WifiManager; 32 import android.os.AsyncTask; 33 import android.os.Handler; 34 import android.os.IPowerManager; 35 import android.os.PowerManager; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserManager; 39 import android.provider.Settings; 40 import android.util.Log; 41 import android.widget.RemoteViews; 42 43 import com.android.settings.R; 44 import com.android.settings.bluetooth.LocalBluetoothAdapter; 45 import com.android.settings.bluetooth.LocalBluetoothManager; 46 47 /** 48 * Provides control of power-related settings from a widget. 49 */ 50 public class SettingsAppWidgetProvider extends AppWidgetProvider { 51 static final String TAG = "SettingsAppWidgetProvider"; 52 53 static final ComponentName THIS_APPWIDGET = 54 new ComponentName("com.android.settings", 55 "com.android.settings.widget.SettingsAppWidgetProvider"); 56 57 private static LocalBluetoothAdapter sLocalBluetoothAdapter = null; 58 59 private static final int BUTTON_WIFI = 0; 60 private static final int BUTTON_BRIGHTNESS = 1; 61 private static final int BUTTON_SYNC = 2; 62 private static final int BUTTON_LOCATION = 3; 63 private static final int BUTTON_BLUETOOTH = 4; 64 65 // This widget keeps track of two sets of states: 66 // "3-state": STATE_DISABLED, STATE_ENABLED, STATE_INTERMEDIATE 67 // "5-state": STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_UNKNOWN 68 private static final int STATE_DISABLED = 0; 69 private static final int STATE_ENABLED = 1; 70 private static final int STATE_TURNING_ON = 2; 71 private static final int STATE_TURNING_OFF = 3; 72 private static final int STATE_UNKNOWN = 4; 73 private static final int STATE_INTERMEDIATE = 5; 74 75 // Position in the widget bar, to enable different graphics for left, center and right buttons 76 private static final int POS_LEFT = 0; 77 private static final int POS_CENTER = 1; 78 private static final int POS_RIGHT = 2; 79 80 private static final int[] IND_DRAWABLE_OFF = { 81 R.drawable.appwidget_settings_ind_off_l_holo, 82 R.drawable.appwidget_settings_ind_off_c_holo, 83 R.drawable.appwidget_settings_ind_off_r_holo 84 }; 85 86 private static final int[] IND_DRAWABLE_MID = { 87 R.drawable.appwidget_settings_ind_mid_l_holo, 88 R.drawable.appwidget_settings_ind_mid_c_holo, 89 R.drawable.appwidget_settings_ind_mid_r_holo 90 }; 91 92 private static final int[] IND_DRAWABLE_ON = { 93 R.drawable.appwidget_settings_ind_on_l_holo, 94 R.drawable.appwidget_settings_ind_on_c_holo, 95 R.drawable.appwidget_settings_ind_on_r_holo 96 }; 97 98 /** Minimum brightness at which the indicator is shown at half-full and ON */ 99 private static final float HALF_BRIGHTNESS_THRESHOLD = 0.3f; 100 /** Minimum brightness at which the indicator is shown at full */ 101 private static final float FULL_BRIGHTNESS_THRESHOLD = 0.8f; 102 103 private static final StateTracker sWifiState = new WifiStateTracker(); 104 private static final StateTracker sBluetoothState = new BluetoothStateTracker(); 105 private static final StateTracker sLocationState = new LocationStateTracker(); 106 private static final StateTracker sSyncState = new SyncStateTracker(); 107 private static SettingsObserver sSettingsObserver; 108 109 /** 110 * The state machine for a setting's toggling, tracking reality 111 * versus the user's intent. 112 * 113 * This is necessary because reality moves relatively slowly 114 * (turning on & off radio drivers), compared to user's 115 * expectations. 116 */ 117 private abstract static class StateTracker { 118 // Is the state in the process of changing? 119 private boolean mInTransition = false; 120 private Boolean mActualState = null; // initially not set 121 private Boolean mIntendedState = null; // initially not set 122 123 // Did a toggle request arrive while a state update was 124 // already in-flight? If so, the mIntendedState needs to be 125 // requested when the other one is done, unless we happened to 126 // arrive at that state already. 127 private boolean mDeferredStateChangeRequestNeeded = false; 128 129 /** 130 * User pressed a button to change the state. Something 131 * should immediately appear to the user afterwards, even if 132 * we effectively do nothing. Their press must be heard. 133 */ toggleState(Context context)134 public final void toggleState(Context context) { 135 int currentState = getTriState(context); 136 boolean newState = false; 137 switch (currentState) { 138 case STATE_ENABLED: 139 newState = false; 140 break; 141 case STATE_DISABLED: 142 newState = true; 143 break; 144 case STATE_INTERMEDIATE: 145 if (mIntendedState != null) { 146 newState = !mIntendedState; 147 } 148 break; 149 } 150 mIntendedState = newState; 151 if (mInTransition) { 152 // We don't send off a transition request if we're 153 // already transitioning. Makes our state tracking 154 // easier, and is probably nicer on lower levels. 155 // (even though they should be able to take it...) 156 mDeferredStateChangeRequestNeeded = true; 157 } else { 158 mInTransition = true; 159 requestStateChange(context, newState); 160 } 161 } 162 163 /** 164 * Return the ID of the clickable container for the setting. 165 */ getContainerId()166 public abstract int getContainerId(); 167 168 /** 169 * Return the ID of the main large image button for the setting. 170 */ getButtonId()171 public abstract int getButtonId(); 172 173 /** 174 * Returns the small indicator image ID underneath the setting. 175 */ getIndicatorId()176 public abstract int getIndicatorId(); 177 178 /** 179 * Returns the resource ID of the setting's content description. 180 */ getButtonDescription()181 public abstract int getButtonDescription(); 182 183 /** 184 * Returns the resource ID of the image to show as a function of 185 * the on-vs-off state. 186 */ getButtonImageId(boolean on)187 public abstract int getButtonImageId(boolean on); 188 189 /** 190 * Returns the position in the button bar - either POS_LEFT, POS_RIGHT or POS_CENTER. 191 */ getPosition()192 public int getPosition() { return POS_CENTER; } 193 194 /** 195 * Updates the remote views depending on the state (off, on, 196 * turning off, turning on) of the setting. 197 */ setImageViewResources(Context context, RemoteViews views)198 public final void setImageViewResources(Context context, RemoteViews views) { 199 int containerId = getContainerId(); 200 int buttonId = getButtonId(); 201 int indicatorId = getIndicatorId(); 202 int pos = getPosition(); 203 switch (getTriState(context)) { 204 case STATE_DISABLED: 205 views.setContentDescription(containerId, 206 getContentDescription(context, R.string.gadget_state_off)); 207 views.setImageViewResource(buttonId, getButtonImageId(false)); 208 views.setImageViewResource( 209 indicatorId, IND_DRAWABLE_OFF[pos]); 210 break; 211 case STATE_ENABLED: 212 views.setContentDescription(containerId, 213 getContentDescription(context, R.string.gadget_state_on)); 214 views.setImageViewResource(buttonId, getButtonImageId(true)); 215 views.setImageViewResource( 216 indicatorId, IND_DRAWABLE_ON[pos]); 217 break; 218 case STATE_INTERMEDIATE: 219 // In the transitional state, the bottom green bar 220 // shows the tri-state (on, off, transitioning), but 221 // the top dark-gray-or-bright-white logo shows the 222 // user's intent. This is much easier to see in 223 // sunlight. 224 if (isTurningOn()) { 225 views.setContentDescription(containerId, 226 getContentDescription(context, R.string.gadget_state_turning_on)); 227 views.setImageViewResource(buttonId, getButtonImageId(true)); 228 views.setImageViewResource( 229 indicatorId, IND_DRAWABLE_MID[pos]); 230 } else { 231 views.setContentDescription(containerId, 232 getContentDescription(context, R.string.gadget_state_turning_off)); 233 views.setImageViewResource(buttonId, getButtonImageId(false)); 234 views.setImageViewResource( 235 indicatorId, IND_DRAWABLE_OFF[pos]); 236 } 237 break; 238 } 239 } 240 241 /** 242 * Returns the gadget state template populated with the gadget 243 * description and state. 244 */ getContentDescription(Context context, int stateResId)245 private final String getContentDescription(Context context, int stateResId) { 246 final String gadget = context.getString(getButtonDescription()); 247 final String state = context.getString(stateResId); 248 return context.getString(R.string.gadget_state_template, gadget, state); 249 } 250 251 /** 252 * Update internal state from a broadcast state change. 253 */ onActualStateChange(Context context, Intent intent)254 public abstract void onActualStateChange(Context context, Intent intent); 255 256 /** 257 * Sets the value that we're now in. To be called from onActualStateChange. 258 * 259 * @param newState one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, 260 * STATE_TURNING_OFF, STATE_UNKNOWN 261 */ setCurrentState(Context context, int newState)262 protected final void setCurrentState(Context context, int newState) { 263 final boolean wasInTransition = mInTransition; 264 switch (newState) { 265 case STATE_DISABLED: 266 mInTransition = false; 267 mActualState = false; 268 break; 269 case STATE_ENABLED: 270 mInTransition = false; 271 mActualState = true; 272 break; 273 case STATE_TURNING_ON: 274 mInTransition = true; 275 mActualState = false; 276 break; 277 case STATE_TURNING_OFF: 278 mInTransition = true; 279 mActualState = true; 280 break; 281 } 282 283 if (wasInTransition && !mInTransition) { 284 if (mDeferredStateChangeRequestNeeded) { 285 Log.v(TAG, "processing deferred state change"); 286 if (mActualState != null && mIntendedState != null && 287 mIntendedState.equals(mActualState)) { 288 Log.v(TAG, "... but intended state matches, so no changes."); 289 } else if (mIntendedState != null) { 290 mInTransition = true; 291 requestStateChange(context, mIntendedState); 292 } 293 mDeferredStateChangeRequestNeeded = false; 294 } 295 } 296 } 297 298 299 /** 300 * If we're in a transition mode, this returns true if we're 301 * transitioning towards being enabled. 302 */ isTurningOn()303 public final boolean isTurningOn() { 304 return mIntendedState != null && mIntendedState; 305 } 306 307 /** 308 * Returns simplified 3-state value from underlying 5-state. 309 * 310 * @param context 311 * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE 312 */ getTriState(Context context)313 public final int getTriState(Context context) { 314 if (mInTransition) { 315 // If we know we just got a toggle request recently 316 // (which set mInTransition), don't even ask the 317 // underlying interface for its state. We know we're 318 // changing. This avoids blocking the UI thread 319 // during UI refresh post-toggle if the underlying 320 // service state accessor has coarse locking on its 321 // state (to be fixed separately). 322 return STATE_INTERMEDIATE; 323 } 324 switch (getActualState(context)) { 325 case STATE_DISABLED: 326 return STATE_DISABLED; 327 case STATE_ENABLED: 328 return STATE_ENABLED; 329 default: 330 return STATE_INTERMEDIATE; 331 } 332 } 333 334 /** 335 * Gets underlying actual state. 336 * 337 * @param context 338 * @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING, STATE_DISABLING, 339 * or or STATE_UNKNOWN. 340 */ getActualState(Context context)341 public abstract int getActualState(Context context); 342 343 /** 344 * Actually make the desired change to the underlying radio 345 * API. 346 */ requestStateChange(Context context, boolean desiredState)347 protected abstract void requestStateChange(Context context, boolean desiredState); 348 } 349 350 /** 351 * Subclass of StateTracker to get/set Wifi state. 352 */ 353 private static final class WifiStateTracker extends StateTracker { getContainerId()354 public int getContainerId() { return R.id.btn_wifi; } getButtonId()355 public int getButtonId() { return R.id.img_wifi; } getIndicatorId()356 public int getIndicatorId() { return R.id.ind_wifi; } getButtonDescription()357 public int getButtonDescription() { return R.string.gadget_wifi; } getButtonImageId(boolean on)358 public int getButtonImageId(boolean on) { 359 return on ? R.drawable.ic_appwidget_settings_wifi_on_holo 360 : R.drawable.ic_appwidget_settings_wifi_off_holo; 361 } 362 363 @Override getPosition()364 public int getPosition() { return POS_LEFT; } 365 366 @Override getActualState(Context context)367 public int getActualState(Context context) { 368 WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 369 if (wifiManager != null) { 370 return wifiStateToFiveState(wifiManager.getWifiState()); 371 } 372 return STATE_UNKNOWN; 373 } 374 375 @Override requestStateChange(Context context, final boolean desiredState)376 protected void requestStateChange(Context context, final boolean desiredState) { 377 final WifiManager wifiManager = 378 (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 379 if (wifiManager == null) { 380 Log.d(TAG, "No wifiManager."); 381 return; 382 } 383 384 // Actually request the wifi change and persistent 385 // settings write off the UI thread, as it can take a 386 // user-noticeable amount of time, especially if there's 387 // disk contention. 388 new AsyncTask<Void, Void, Void>() { 389 @Override 390 protected Void doInBackground(Void... args) { 391 /** 392 * Disable tethering if enabling Wifi 393 */ 394 int wifiApState = wifiManager.getWifiApState(); 395 if (desiredState && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || 396 (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { 397 wifiManager.setWifiApEnabled(null, false); 398 } 399 400 wifiManager.setWifiEnabled(desiredState); 401 return null; 402 } 403 }.execute(); 404 } 405 406 @Override onActualStateChange(Context context, Intent intent)407 public void onActualStateChange(Context context, Intent intent) { 408 if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { 409 return; 410 } 411 int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1); 412 setCurrentState(context, wifiStateToFiveState(wifiState)); 413 } 414 415 /** 416 * Converts WifiManager's state values into our 417 * Wifi/Bluetooth-common state values. 418 */ wifiStateToFiveState(int wifiState)419 private static int wifiStateToFiveState(int wifiState) { 420 switch (wifiState) { 421 case WifiManager.WIFI_STATE_DISABLED: 422 return STATE_DISABLED; 423 case WifiManager.WIFI_STATE_ENABLED: 424 return STATE_ENABLED; 425 case WifiManager.WIFI_STATE_DISABLING: 426 return STATE_TURNING_OFF; 427 case WifiManager.WIFI_STATE_ENABLING: 428 return STATE_TURNING_ON; 429 default: 430 return STATE_UNKNOWN; 431 } 432 } 433 } 434 435 /** 436 * Subclass of StateTracker to get/set Bluetooth state. 437 */ 438 private static final class BluetoothStateTracker extends StateTracker { getContainerId()439 public int getContainerId() { return R.id.btn_bluetooth; } getButtonId()440 public int getButtonId() { return R.id.img_bluetooth; } getIndicatorId()441 public int getIndicatorId() { return R.id.ind_bluetooth; } getButtonDescription()442 public int getButtonDescription() { return R.string.gadget_bluetooth; } getButtonImageId(boolean on)443 public int getButtonImageId(boolean on) { 444 return on ? R.drawable.ic_appwidget_settings_bluetooth_on_holo 445 : R.drawable.ic_appwidget_settings_bluetooth_off_holo; 446 } 447 448 @Override getActualState(Context context)449 public int getActualState(Context context) { 450 if (sLocalBluetoothAdapter == null) { 451 LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); 452 if (manager == null) { 453 return STATE_UNKNOWN; // On emulator? 454 } 455 sLocalBluetoothAdapter = manager.getBluetoothAdapter(); 456 } 457 return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState()); 458 } 459 460 @Override requestStateChange(Context context, final boolean desiredState)461 protected void requestStateChange(Context context, final boolean desiredState) { 462 if (sLocalBluetoothAdapter == null) { 463 Log.d(TAG, "No LocalBluetoothManager"); 464 return; 465 } 466 // Actually request the Bluetooth change and persistent 467 // settings write off the UI thread, as it can take a 468 // user-noticeable amount of time, especially if there's 469 // disk contention. 470 new AsyncTask<Void, Void, Void>() { 471 @Override 472 protected Void doInBackground(Void... args) { 473 sLocalBluetoothAdapter.setBluetoothEnabled(desiredState); 474 return null; 475 } 476 }.execute(); 477 } 478 479 @Override onActualStateChange(Context context, Intent intent)480 public void onActualStateChange(Context context, Intent intent) { 481 if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { 482 return; 483 } 484 int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 485 setCurrentState(context, bluetoothStateToFiveState(bluetoothState)); 486 } 487 488 /** 489 * Converts BluetoothAdapter's state values into our 490 * Wifi/Bluetooth-common state values. 491 */ bluetoothStateToFiveState(int bluetoothState)492 private static int bluetoothStateToFiveState(int bluetoothState) { 493 switch (bluetoothState) { 494 case BluetoothAdapter.STATE_OFF: 495 return STATE_DISABLED; 496 case BluetoothAdapter.STATE_ON: 497 return STATE_ENABLED; 498 case BluetoothAdapter.STATE_TURNING_ON: 499 return STATE_TURNING_ON; 500 case BluetoothAdapter.STATE_TURNING_OFF: 501 return STATE_TURNING_OFF; 502 default: 503 return STATE_UNKNOWN; 504 } 505 } 506 } 507 508 /** 509 * Subclass of StateTracker for location state. 510 */ 511 private static final class LocationStateTracker extends StateTracker { 512 private int mCurrentLocationMode = Settings.Secure.LOCATION_MODE_OFF; 513 getContainerId()514 public int getContainerId() { return R.id.btn_location; } getButtonId()515 public int getButtonId() { return R.id.img_location; } getIndicatorId()516 public int getIndicatorId() { return R.id.ind_location; } getButtonDescription()517 public int getButtonDescription() { return R.string.gadget_location; } getButtonImageId(boolean on)518 public int getButtonImageId(boolean on) { 519 if (on) { 520 switch (mCurrentLocationMode) { 521 case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: 522 case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: 523 return R.drawable.ic_appwidget_settings_location_on_holo; 524 default: 525 return R.drawable.ic_appwidget_settings_location_saving_holo; 526 } 527 } 528 529 return R.drawable.ic_appwidget_settings_location_off_holo; 530 } 531 532 @Override getActualState(Context context)533 public int getActualState(Context context) { 534 ContentResolver resolver = context.getContentResolver(); 535 mCurrentLocationMode = Settings.Secure.getInt(resolver, 536 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); 537 return (mCurrentLocationMode == Settings.Secure.LOCATION_MODE_OFF) 538 ? STATE_DISABLED : STATE_ENABLED; 539 } 540 541 @Override onActualStateChange(Context context, Intent unused)542 public void onActualStateChange(Context context, Intent unused) { 543 // Note: the broadcast location providers changed intent 544 // doesn't include an extras bundles saying what the new value is. 545 setCurrentState(context, getActualState(context)); 546 } 547 548 @Override requestStateChange(final Context context, final boolean desiredState)549 public void requestStateChange(final Context context, final boolean desiredState) { 550 final ContentResolver resolver = context.getContentResolver(); 551 new AsyncTask<Void, Void, Boolean>() { 552 @Override 553 protected Boolean doInBackground(Void... args) { 554 final UserManager um = 555 (UserManager) context.getSystemService(Context.USER_SERVICE); 556 if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) { 557 int currentMode = Settings.Secure.getInt(resolver, 558 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); 559 int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; 560 switch (currentMode) { 561 case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: 562 mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING; 563 break; 564 case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: 565 mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; 566 break; 567 case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: 568 mode = Settings.Secure.LOCATION_MODE_OFF; 569 break; 570 case Settings.Secure.LOCATION_MODE_OFF: 571 mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; 572 break; 573 } 574 Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode); 575 return mode != Settings.Secure.LOCATION_MODE_OFF; 576 } 577 578 return getActualState(context) == STATE_ENABLED; 579 } 580 581 @Override 582 protected void onPostExecute(Boolean result) { 583 setCurrentState( 584 context, 585 result ? STATE_ENABLED : STATE_DISABLED); 586 updateWidget(context); 587 } 588 }.execute(); 589 } 590 } 591 592 /** 593 * Subclass of StateTracker for sync state. 594 */ 595 private static final class SyncStateTracker extends StateTracker { getContainerId()596 public int getContainerId() { return R.id.btn_sync; } getButtonId()597 public int getButtonId() { return R.id.img_sync; } getIndicatorId()598 public int getIndicatorId() { return R.id.ind_sync; } getButtonDescription()599 public int getButtonDescription() { return R.string.gadget_sync; } getButtonImageId(boolean on)600 public int getButtonImageId(boolean on) { 601 return on ? R.drawable.ic_appwidget_settings_sync_on_holo 602 : R.drawable.ic_appwidget_settings_sync_off_holo; 603 } 604 605 @Override getActualState(Context context)606 public int getActualState(Context context) { 607 boolean on = ContentResolver.getMasterSyncAutomatically(); 608 return on ? STATE_ENABLED : STATE_DISABLED; 609 } 610 611 @Override onActualStateChange(Context context, Intent unused)612 public void onActualStateChange(Context context, Intent unused) { 613 setCurrentState(context, getActualState(context)); 614 } 615 616 @Override requestStateChange(final Context context, final boolean desiredState)617 public void requestStateChange(final Context context, final boolean desiredState) { 618 final ConnectivityManager connManager = 619 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 620 final boolean sync = ContentResolver.getMasterSyncAutomatically(); 621 622 new AsyncTask<Void, Void, Boolean>() { 623 @Override 624 protected Boolean doInBackground(Void... args) { 625 // Turning sync on. 626 if (desiredState) { 627 if (!sync) { 628 ContentResolver.setMasterSyncAutomatically(true); 629 } 630 return true; 631 } 632 633 // Turning sync off 634 if (sync) { 635 ContentResolver.setMasterSyncAutomatically(false); 636 } 637 return false; 638 } 639 640 @Override 641 protected void onPostExecute(Boolean result) { 642 setCurrentState( 643 context, 644 result ? STATE_ENABLED : STATE_DISABLED); 645 updateWidget(context); 646 } 647 }.execute(); 648 } 649 } 650 checkObserver(Context context)651 private static void checkObserver(Context context) { 652 if (sSettingsObserver == null) { 653 sSettingsObserver = new SettingsObserver(new Handler(), 654 context.getApplicationContext()); 655 sSettingsObserver.startObserving(); 656 } 657 } 658 659 @Override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)660 public void onUpdate(Context context, AppWidgetManager appWidgetManager, 661 int[] appWidgetIds) { 662 // Update each requested appWidgetId 663 RemoteViews view = buildUpdate(context); 664 665 for (int i = 0; i < appWidgetIds.length; i++) { 666 appWidgetManager.updateAppWidget(appWidgetIds[i], view); 667 } 668 } 669 670 @Override onEnabled(Context context)671 public void onEnabled(Context context) { 672 checkObserver(context); 673 } 674 675 @Override onDisabled(Context context)676 public void onDisabled(Context context) { 677 if (sSettingsObserver != null) { 678 sSettingsObserver.stopObserving(); 679 sSettingsObserver = null; 680 } 681 } 682 683 /** 684 * Load image for given widget and build {@link RemoteViews} for it. 685 */ buildUpdate(Context context)686 static RemoteViews buildUpdate(Context context) { 687 RemoteViews views = new RemoteViews(context.getPackageName(), 688 R.layout.widget); 689 views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, 690 BUTTON_WIFI)); 691 views.setOnClickPendingIntent(R.id.btn_brightness, 692 getLaunchPendingIntent(context, 693 BUTTON_BRIGHTNESS)); 694 views.setOnClickPendingIntent(R.id.btn_sync, 695 getLaunchPendingIntent(context, 696 BUTTON_SYNC)); 697 views.setOnClickPendingIntent(R.id.btn_location, 698 getLaunchPendingIntent(context, BUTTON_LOCATION)); 699 views.setOnClickPendingIntent(R.id.btn_bluetooth, 700 getLaunchPendingIntent(context, 701 BUTTON_BLUETOOTH)); 702 703 updateButtons(views, context); 704 return views; 705 } 706 707 /** 708 * Updates the widget when something changes, or when a button is pushed. 709 * 710 * @param context 711 */ updateWidget(Context context)712 public static void updateWidget(Context context) { 713 RemoteViews views = buildUpdate(context); 714 // Update specific list of appWidgetIds if given, otherwise default to all 715 final AppWidgetManager gm = AppWidgetManager.getInstance(context); 716 gm.updateAppWidget(THIS_APPWIDGET, views); 717 checkObserver(context); 718 } 719 720 /** 721 * Updates the buttons based on the underlying states of wifi, etc. 722 * 723 * @param views The RemoteViews to update. 724 * @param context 725 */ updateButtons(RemoteViews views, Context context)726 private static void updateButtons(RemoteViews views, Context context) { 727 sWifiState.setImageViewResources(context, views); 728 sBluetoothState.setImageViewResources(context, views); 729 sLocationState.setImageViewResources(context, views); 730 sSyncState.setImageViewResources(context, views); 731 732 if (getBrightnessMode(context)) { 733 views.setContentDescription(R.id.btn_brightness, 734 context.getString(R.string.gadget_brightness_template, 735 context.getString(R.string.gadget_brightness_state_auto))); 736 views.setImageViewResource(R.id.img_brightness, 737 R.drawable.ic_appwidget_settings_brightness_auto_holo); 738 views.setImageViewResource(R.id.ind_brightness, 739 R.drawable.appwidget_settings_ind_on_r_holo); 740 } else { 741 final int brightness = getBrightness(context); 742 final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 743 // Set the icon 744 final int full = (int)(pm.getMaximumScreenBrightnessSetting() 745 * FULL_BRIGHTNESS_THRESHOLD); 746 final int half = (int)(pm.getMaximumScreenBrightnessSetting() 747 * HALF_BRIGHTNESS_THRESHOLD); 748 if (brightness > full) { 749 views.setContentDescription(R.id.btn_brightness, 750 context.getString(R.string.gadget_brightness_template, 751 context.getString(R.string.gadget_brightness_state_full))); 752 views.setImageViewResource(R.id.img_brightness, 753 R.drawable.ic_appwidget_settings_brightness_full_holo); 754 } else if (brightness > half) { 755 views.setContentDescription(R.id.btn_brightness, 756 context.getString(R.string.gadget_brightness_template, 757 context.getString(R.string.gadget_brightness_state_half))); 758 views.setImageViewResource(R.id.img_brightness, 759 R.drawable.ic_appwidget_settings_brightness_half_holo); 760 } else { 761 views.setContentDescription(R.id.btn_brightness, 762 context.getString(R.string.gadget_brightness_template, 763 context.getString(R.string.gadget_brightness_state_off))); 764 views.setImageViewResource(R.id.img_brightness, 765 R.drawable.ic_appwidget_settings_brightness_off_holo); 766 } 767 // Set the ON state 768 if (brightness > half) { 769 views.setImageViewResource(R.id.ind_brightness, 770 R.drawable.appwidget_settings_ind_on_r_holo); 771 } else { 772 views.setImageViewResource(R.id.ind_brightness, 773 R.drawable.appwidget_settings_ind_off_r_holo); 774 } 775 } 776 } 777 778 /** 779 * Creates PendingIntent to notify the widget of a button click. 780 * 781 * @param context 782 * @return 783 */ getLaunchPendingIntent(Context context, int buttonId)784 private static PendingIntent getLaunchPendingIntent(Context context, 785 int buttonId) { 786 Intent launchIntent = new Intent(); 787 launchIntent.setClass(context, SettingsAppWidgetProvider.class); 788 launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE); 789 launchIntent.setData(Uri.parse("custom:" + buttonId)); 790 PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */, 791 launchIntent, 0 /* no flags */); 792 return pi; 793 } 794 795 /** 796 * Receives and processes a button pressed intent or state change. 797 * 798 * @param context 799 * @param intent Indicates the pressed button. 800 */ 801 @Override onReceive(Context context, Intent intent)802 public void onReceive(Context context, Intent intent) { 803 super.onReceive(context, intent); 804 String action = intent.getAction(); 805 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 806 sWifiState.onActualStateChange(context, intent); 807 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 808 sBluetoothState.onActualStateChange(context, intent); 809 } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) { 810 sLocationState.onActualStateChange(context, intent); 811 } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) { 812 sSyncState.onActualStateChange(context, intent); 813 } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { 814 Uri data = intent.getData(); 815 int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); 816 if (buttonId == BUTTON_WIFI) { 817 sWifiState.toggleState(context); 818 } else if (buttonId == BUTTON_BRIGHTNESS) { 819 toggleBrightness(context); 820 } else if (buttonId == BUTTON_SYNC) { 821 sSyncState.toggleState(context); 822 } else if (buttonId == BUTTON_LOCATION) { 823 sLocationState.toggleState(context); 824 } else if (buttonId == BUTTON_BLUETOOTH) { 825 sBluetoothState.toggleState(context); 826 } 827 } else { 828 // Don't fall-through to updating the widget. The Intent 829 // was something unrelated or that our super class took 830 // care of. 831 return; 832 } 833 834 // State changes fall through 835 updateWidget(context); 836 } 837 838 /** 839 * Gets brightness level. 840 * 841 * @param context 842 * @return brightness level between 0 and 255. 843 */ getBrightness(Context context)844 private static int getBrightness(Context context) { 845 try { 846 int brightness = Settings.System.getInt(context.getContentResolver(), 847 Settings.System.SCREEN_BRIGHTNESS); 848 return brightness; 849 } catch (Exception e) { 850 } 851 return 0; 852 } 853 854 /** 855 * Gets state of brightness mode. 856 * 857 * @param context 858 * @return true if auto brightness is on. 859 */ getBrightnessMode(Context context)860 private static boolean getBrightnessMode(Context context) { 861 try { 862 int brightnessMode = Settings.System.getInt(context.getContentResolver(), 863 Settings.System.SCREEN_BRIGHTNESS_MODE); 864 return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; 865 } catch (Exception e) { 866 Log.d(TAG, "getBrightnessMode: " + e); 867 } 868 return false; 869 } 870 871 /** 872 * Increases or decreases the brightness. 873 * 874 * @param context 875 */ toggleBrightness(Context context)876 private void toggleBrightness(Context context) { 877 try { 878 IPowerManager power = IPowerManager.Stub.asInterface( 879 ServiceManager.getService("power")); 880 if (power != null) { 881 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 882 883 ContentResolver cr = context.getContentResolver(); 884 int brightness = Settings.System.getInt(cr, 885 Settings.System.SCREEN_BRIGHTNESS); 886 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 887 //Only get brightness setting if available 888 if (context.getResources().getBoolean( 889 com.android.internal.R.bool.config_automatic_brightness_available)) { 890 brightnessMode = Settings.System.getInt(cr, 891 Settings.System.SCREEN_BRIGHTNESS_MODE); 892 } 893 894 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM 895 // Technically, not a toggle... 896 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { 897 brightness = pm.getMinimumScreenBrightnessSetting(); 898 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 899 } else if (brightness < pm.getDefaultScreenBrightnessSetting()) { 900 brightness = pm.getDefaultScreenBrightnessSetting(); 901 } else if (brightness < pm.getMaximumScreenBrightnessSetting()) { 902 brightness = pm.getMaximumScreenBrightnessSetting(); 903 } else { 904 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; 905 brightness = pm.getMinimumScreenBrightnessSetting(); 906 } 907 908 if (context.getResources().getBoolean( 909 com.android.internal.R.bool.config_automatic_brightness_available)) { 910 // Set screen brightness mode (automatic or manual) 911 Settings.System.putInt(context.getContentResolver(), 912 Settings.System.SCREEN_BRIGHTNESS_MODE, 913 brightnessMode); 914 } else { 915 // Make sure we set the brightness if automatic mode isn't available 916 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 917 } 918 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) { 919 power.setTemporaryScreenBrightnessSettingOverride(brightness); 920 Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); 921 } 922 } 923 } catch (RemoteException e) { 924 Log.d(TAG, "toggleBrightness: " + e); 925 } catch (Settings.SettingNotFoundException e) { 926 Log.d(TAG, "toggleBrightness: " + e); 927 } 928 } 929 930 /** Observer to watch for changes to the BRIGHTNESS setting */ 931 private static class SettingsObserver extends ContentObserver { 932 933 private Context mContext; 934 SettingsObserver(Handler handler, Context context)935 SettingsObserver(Handler handler, Context context) { 936 super(handler); 937 mContext = context; 938 } 939 startObserving()940 void startObserving() { 941 ContentResolver resolver = mContext.getContentResolver(); 942 // Listen to brightness and brightness mode 943 resolver.registerContentObserver(Settings.System 944 .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this); 945 resolver.registerContentObserver(Settings.System 946 .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this); 947 } 948 stopObserving()949 void stopObserving() { 950 mContext.getContentResolver().unregisterContentObserver(this); 951 } 952 953 @Override onChange(boolean selfChange)954 public void onChange(boolean selfChange) { 955 updateWidget(mContext); 956 } 957 } 958 959 } 960