Home | History | Annotate | Download | only in widget
      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          */
    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          */
    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 image to show as a function of
    180          * the on-vs-off state.
    181          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    317         public abstract int getActualState(Context context);
    318 
    319         /**
    320          * Actually make the desired change to the underlying radio
    321          * API.
    322          */
    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 {
    330         public int getButtonId() { return R.id.img_wifi; }
    331         public int getIndicatorId() { return R.id.ind_wifi; }
    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
    338         public int getPosition() { return POS_LEFT; }
    339 
    340         @Override
    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
    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
    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          */
    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 {
    413         public int getButtonId() { return R.id.img_bluetooth; }
    414         public int getIndicatorId() { return R.id.ind_bluetooth; }
    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
    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
    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
    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          */
    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 {
    484         public int getButtonId() { return R.id.img_gps; }
    485         public int getIndicatorId() { return R.id.ind_gps; }
    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
    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
    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
    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 {
    534         public int getButtonId() { return R.id.img_sync; }
    535         public int getIndicatorId() { return R.id.ind_sync; }
    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
    542         public int getActualState(Context context) {
    543             boolean on = ContentResolver.getMasterSyncAutomatically();
    544             return on ? STATE_ENABLED : STATE_DISABLED;
    545         }
    546 
    547         @Override
    548         public void onActualStateChange(Context context, Intent unused) {
    549             setCurrentState(context, getActualState(context));
    550         }
    551 
    552         @Override
    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 
    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
    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
    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
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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 
    872         SettingsObserver(Handler handler, Context context) {
    873             super(handler);
    874             mContext = context;
    875         }
    876 
    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 
    886         void stopObserving() {
    887             mContext.getContentResolver().unregisterContentObserver(this);
    888         }
    889 
    890         @Override
    891         public void onChange(boolean selfChange) {
    892             updateWidget(mContext);
    893         }
    894     }
    895 
    896 }
    897