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