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