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