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_GPS = 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 sGpsState = new GpsStateTracker(); 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 */ 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 */ 166 public abstract int getContainerId(); 167 168 /** 169 * Return the ID of the main large image button for the setting. 170 */ 171 public abstract int getButtonId(); 172 173 /** 174 * Returns the small indicator image ID underneath the setting. 175 */ 176 public abstract int getIndicatorId(); 177 178 /** 179 * Returns the resource ID of the setting's content description. 180 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 341 public abstract int getActualState(Context context); 342 343 /** 344 * Actually make the desired change to the underlying radio 345 * API. 346 */ 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 { 354 public int getContainerId() { return R.id.btn_wifi; } 355 public int getButtonId() { return R.id.img_wifi; } 356 public int getIndicatorId() { return R.id.ind_wifi; } 357 public int getButtonDescription() { return R.string.gadget_wifi; } 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 364 public int getPosition() { return POS_LEFT; } 365 366 @Override 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 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 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 */ 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 { 439 public int getContainerId() { return R.id.btn_bluetooth; } 440 public int getButtonId() { return R.id.img_bluetooth; } 441 public int getIndicatorId() { return R.id.ind_bluetooth; } 442 public int getButtonDescription() { return R.string.gadget_bluetooth; } 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 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 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 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 */ 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 GPS state. 510 */ 511 private static final class GpsStateTracker extends StateTracker { 512 public int getContainerId() { return R.id.btn_gps; } 513 public int getButtonId() { return R.id.img_gps; } 514 public int getIndicatorId() { return R.id.ind_gps; } 515 public int getButtonDescription() { return R.string.gadget_gps; } 516 public int getButtonImageId(boolean on) { 517 return on ? R.drawable.ic_appwidget_settings_gps_on_holo 518 : R.drawable.ic_appwidget_settings_gps_off_holo; 519 } 520 521 @Override 522 public int getActualState(Context context) { 523 ContentResolver resolver = context.getContentResolver(); 524 boolean on = Settings.Secure.isLocationProviderEnabled( 525 resolver, LocationManager.GPS_PROVIDER); 526 return on ? STATE_ENABLED : STATE_DISABLED; 527 } 528 529 @Override 530 public void onActualStateChange(Context context, Intent unused) { 531 // Note: the broadcast location providers changed intent 532 // doesn't include an extras bundles saying what the new value is. 533 setCurrentState(context, getActualState(context)); 534 } 535 536 @Override 537 public void requestStateChange(final Context context, final boolean desiredState) { 538 final ContentResolver resolver = context.getContentResolver(); 539 new AsyncTask<Void, Void, Boolean>() { 540 @Override 541 protected Boolean doInBackground(Void... args) { 542 final UserManager um = 543 (UserManager) context.getSystemService(Context.USER_SERVICE); 544 if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) { 545 Settings.Secure.setLocationProviderEnabled( 546 resolver, 547 LocationManager.GPS_PROVIDER, 548 desiredState); 549 return desiredState; 550 } 551 return Settings.Secure.isLocationProviderEnabled( 552 resolver, 553 LocationManager.GPS_PROVIDER); 554 } 555 556 @Override 557 protected void onPostExecute(Boolean result) { 558 setCurrentState( 559 context, 560 result ? STATE_ENABLED : STATE_DISABLED); 561 updateWidget(context); 562 } 563 }.execute(); 564 } 565 } 566 567 /** 568 * Subclass of StateTracker for sync state. 569 */ 570 private static final class SyncStateTracker extends StateTracker { 571 public int getContainerId() { return R.id.btn_sync; } 572 public int getButtonId() { return R.id.img_sync; } 573 public int getIndicatorId() { return R.id.ind_sync; } 574 public int getButtonDescription() { return R.string.gadget_sync; } 575 public int getButtonImageId(boolean on) { 576 return on ? R.drawable.ic_appwidget_settings_sync_on_holo 577 : R.drawable.ic_appwidget_settings_sync_off_holo; 578 } 579 580 @Override 581 public int getActualState(Context context) { 582 boolean on = ContentResolver.getMasterSyncAutomatically(); 583 return on ? STATE_ENABLED : STATE_DISABLED; 584 } 585 586 @Override 587 public void onActualStateChange(Context context, Intent unused) { 588 setCurrentState(context, getActualState(context)); 589 } 590 591 @Override 592 public void requestStateChange(final Context context, final boolean desiredState) { 593 final ConnectivityManager connManager = 594 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 595 final boolean sync = ContentResolver.getMasterSyncAutomatically(); 596 597 new AsyncTask<Void, Void, Boolean>() { 598 @Override 599 protected Boolean doInBackground(Void... args) { 600 // Turning sync on. 601 if (desiredState) { 602 if (!sync) { 603 ContentResolver.setMasterSyncAutomatically(true); 604 } 605 return true; 606 } 607 608 // Turning sync off 609 if (sync) { 610 ContentResolver.setMasterSyncAutomatically(false); 611 } 612 return false; 613 } 614 615 @Override 616 protected void onPostExecute(Boolean result) { 617 setCurrentState( 618 context, 619 result ? STATE_ENABLED : STATE_DISABLED); 620 updateWidget(context); 621 } 622 }.execute(); 623 } 624 } 625 626 private static void checkObserver(Context context) { 627 if (sSettingsObserver == null) { 628 sSettingsObserver = new SettingsObserver(new Handler(), 629 context.getApplicationContext()); 630 sSettingsObserver.startObserving(); 631 } 632 } 633 634 @Override 635 public void onUpdate(Context context, AppWidgetManager appWidgetManager, 636 int[] appWidgetIds) { 637 // Update each requested appWidgetId 638 RemoteViews view = buildUpdate(context); 639 640 for (int i = 0; i < appWidgetIds.length; i++) { 641 appWidgetManager.updateAppWidget(appWidgetIds[i], view); 642 } 643 } 644 645 @Override 646 public void onEnabled(Context context) { 647 checkObserver(context); 648 } 649 650 @Override 651 public void onDisabled(Context context) { 652 if (sSettingsObserver != null) { 653 sSettingsObserver.stopObserving(); 654 sSettingsObserver = null; 655 } 656 } 657 658 /** 659 * Load image for given widget and build {@link RemoteViews} for it. 660 */ 661 static RemoteViews buildUpdate(Context context) { 662 RemoteViews views = new RemoteViews(context.getPackageName(), 663 R.layout.widget); 664 views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, 665 BUTTON_WIFI)); 666 views.setOnClickPendingIntent(R.id.btn_brightness, 667 getLaunchPendingIntent(context, 668 BUTTON_BRIGHTNESS)); 669 views.setOnClickPendingIntent(R.id.btn_sync, 670 getLaunchPendingIntent(context, 671 BUTTON_SYNC)); 672 views.setOnClickPendingIntent(R.id.btn_gps, 673 getLaunchPendingIntent(context, BUTTON_GPS)); 674 views.setOnClickPendingIntent(R.id.btn_bluetooth, 675 getLaunchPendingIntent(context, 676 BUTTON_BLUETOOTH)); 677 678 updateButtons(views, context); 679 return views; 680 } 681 682 /** 683 * Updates the widget when something changes, or when a button is pushed. 684 * 685 * @param context 686 */ 687 public static void updateWidget(Context context) { 688 RemoteViews views = buildUpdate(context); 689 // Update specific list of appWidgetIds if given, otherwise default to all 690 final AppWidgetManager gm = AppWidgetManager.getInstance(context); 691 gm.updateAppWidget(THIS_APPWIDGET, views); 692 checkObserver(context); 693 } 694 695 /** 696 * Updates the buttons based on the underlying states of wifi, etc. 697 * 698 * @param views The RemoteViews to update. 699 * @param context 700 */ 701 private static void updateButtons(RemoteViews views, Context context) { 702 sWifiState.setImageViewResources(context, views); 703 sBluetoothState.setImageViewResources(context, views); 704 sGpsState.setImageViewResources(context, views); 705 sSyncState.setImageViewResources(context, views); 706 707 if (getBrightnessMode(context)) { 708 views.setContentDescription(R.id.btn_brightness, 709 context.getString(R.string.gadget_brightness_template, 710 context.getString(R.string.gadget_brightness_state_auto))); 711 views.setImageViewResource(R.id.img_brightness, 712 R.drawable.ic_appwidget_settings_brightness_auto_holo); 713 views.setImageViewResource(R.id.ind_brightness, 714 R.drawable.appwidget_settings_ind_on_r_holo); 715 } else { 716 final int brightness = getBrightness(context); 717 final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 718 // Set the icon 719 final int full = (int)(pm.getMaximumScreenBrightnessSetting() 720 * FULL_BRIGHTNESS_THRESHOLD); 721 final int half = (int)(pm.getMaximumScreenBrightnessSetting() 722 * HALF_BRIGHTNESS_THRESHOLD); 723 if (brightness > full) { 724 views.setContentDescription(R.id.btn_brightness, 725 context.getString(R.string.gadget_brightness_template, 726 context.getString(R.string.gadget_brightness_state_full))); 727 views.setImageViewResource(R.id.img_brightness, 728 R.drawable.ic_appwidget_settings_brightness_full_holo); 729 } else if (brightness > half) { 730 views.setContentDescription(R.id.btn_brightness, 731 context.getString(R.string.gadget_brightness_template, 732 context.getString(R.string.gadget_brightness_state_half))); 733 views.setImageViewResource(R.id.img_brightness, 734 R.drawable.ic_appwidget_settings_brightness_half_holo); 735 } else { 736 views.setContentDescription(R.id.btn_brightness, 737 context.getString(R.string.gadget_brightness_template, 738 context.getString(R.string.gadget_brightness_state_off))); 739 views.setImageViewResource(R.id.img_brightness, 740 R.drawable.ic_appwidget_settings_brightness_off_holo); 741 } 742 // Set the ON state 743 if (brightness > half) { 744 views.setImageViewResource(R.id.ind_brightness, 745 R.drawable.appwidget_settings_ind_on_r_holo); 746 } else { 747 views.setImageViewResource(R.id.ind_brightness, 748 R.drawable.appwidget_settings_ind_off_r_holo); 749 } 750 } 751 } 752 753 /** 754 * Creates PendingIntent to notify the widget of a button click. 755 * 756 * @param context 757 * @return 758 */ 759 private static PendingIntent getLaunchPendingIntent(Context context, 760 int buttonId) { 761 Intent launchIntent = new Intent(); 762 launchIntent.setClass(context, SettingsAppWidgetProvider.class); 763 launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE); 764 launchIntent.setData(Uri.parse("custom:" + buttonId)); 765 PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */, 766 launchIntent, 0 /* no flags */); 767 return pi; 768 } 769 770 /** 771 * Receives and processes a button pressed intent or state change. 772 * 773 * @param context 774 * @param intent Indicates the pressed button. 775 */ 776 @Override 777 public void onReceive(Context context, Intent intent) { 778 super.onReceive(context, intent); 779 String action = intent.getAction(); 780 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 781 sWifiState.onActualStateChange(context, intent); 782 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 783 sBluetoothState.onActualStateChange(context, intent); 784 } else if (LocationManager.PROVIDERS_CHANGED_ACTION.equals(action)) { 785 sGpsState.onActualStateChange(context, intent); 786 } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) { 787 sSyncState.onActualStateChange(context, intent); 788 } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { 789 Uri data = intent.getData(); 790 int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); 791 if (buttonId == BUTTON_WIFI) { 792 sWifiState.toggleState(context); 793 } else if (buttonId == BUTTON_BRIGHTNESS) { 794 toggleBrightness(context); 795 } else if (buttonId == BUTTON_SYNC) { 796 sSyncState.toggleState(context); 797 } else if (buttonId == BUTTON_GPS) { 798 sGpsState.toggleState(context); 799 } else if (buttonId == BUTTON_BLUETOOTH) { 800 sBluetoothState.toggleState(context); 801 } 802 } else { 803 // Don't fall-through to updating the widget. The Intent 804 // was something unrelated or that our super class took 805 // care of. 806 return; 807 } 808 809 // State changes fall through 810 updateWidget(context); 811 } 812 813 /** 814 * Gets brightness level. 815 * 816 * @param context 817 * @return brightness level between 0 and 255. 818 */ 819 private static int getBrightness(Context context) { 820 try { 821 int brightness = Settings.System.getInt(context.getContentResolver(), 822 Settings.System.SCREEN_BRIGHTNESS); 823 return brightness; 824 } catch (Exception e) { 825 } 826 return 0; 827 } 828 829 /** 830 * Gets state of brightness mode. 831 * 832 * @param context 833 * @return true if auto brightness is on. 834 */ 835 private static boolean getBrightnessMode(Context context) { 836 try { 837 int brightnessMode = Settings.System.getInt(context.getContentResolver(), 838 Settings.System.SCREEN_BRIGHTNESS_MODE); 839 return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; 840 } catch (Exception e) { 841 Log.d(TAG, "getBrightnessMode: " + e); 842 } 843 return false; 844 } 845 846 /** 847 * Increases or decreases the brightness. 848 * 849 * @param context 850 */ 851 private void toggleBrightness(Context context) { 852 try { 853 IPowerManager power = IPowerManager.Stub.asInterface( 854 ServiceManager.getService("power")); 855 if (power != null) { 856 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 857 858 ContentResolver cr = context.getContentResolver(); 859 int brightness = Settings.System.getInt(cr, 860 Settings.System.SCREEN_BRIGHTNESS); 861 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 862 //Only get brightness setting if available 863 if (context.getResources().getBoolean( 864 com.android.internal.R.bool.config_automatic_brightness_available)) { 865 brightnessMode = Settings.System.getInt(cr, 866 Settings.System.SCREEN_BRIGHTNESS_MODE); 867 } 868 869 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM 870 // Technically, not a toggle... 871 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { 872 brightness = pm.getMinimumScreenBrightnessSetting(); 873 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 874 } else if (brightness < pm.getDefaultScreenBrightnessSetting()) { 875 brightness = pm.getDefaultScreenBrightnessSetting(); 876 } else if (brightness < pm.getMaximumScreenBrightnessSetting()) { 877 brightness = pm.getMaximumScreenBrightnessSetting(); 878 } else { 879 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; 880 brightness = pm.getMinimumScreenBrightnessSetting(); 881 } 882 883 if (context.getResources().getBoolean( 884 com.android.internal.R.bool.config_automatic_brightness_available)) { 885 // Set screen brightness mode (automatic or manual) 886 Settings.System.putInt(context.getContentResolver(), 887 Settings.System.SCREEN_BRIGHTNESS_MODE, 888 brightnessMode); 889 } else { 890 // Make sure we set the brightness if automatic mode isn't available 891 brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; 892 } 893 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) { 894 power.setTemporaryScreenBrightnessSettingOverride(brightness); 895 Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); 896 } 897 } 898 } catch (RemoteException e) { 899 Log.d(TAG, "toggleBrightness: " + e); 900 } catch (Settings.SettingNotFoundException e) { 901 Log.d(TAG, "toggleBrightness: " + e); 902 } 903 } 904 905 /** Observer to watch for changes to the BRIGHTNESS setting */ 906 private static class SettingsObserver extends ContentObserver { 907 908 private Context mContext; 909 910 SettingsObserver(Handler handler, Context context) { 911 super(handler); 912 mContext = context; 913 } 914 915 void startObserving() { 916 ContentResolver resolver = mContext.getContentResolver(); 917 // Listen to brightness and brightness mode 918 resolver.registerContentObserver(Settings.System 919 .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this); 920 resolver.registerContentObserver(Settings.System 921 .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this); 922 } 923 924 void stopObserving() { 925 mContext.getContentResolver().unregisterContentObserver(this); 926 } 927 928 @Override 929 public void onChange(boolean selfChange) { 930 updateWidget(mContext); 931 } 932 } 933 934 } 935