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