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