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