Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.widget;
     18 
     19 import android.app.PendingIntent;
     20 import android.appwidget.AppWidgetManager;
     21 import android.appwidget.AppWidgetProvider;
     22 import android.bluetooth.BluetoothAdapter;
     23 import android.content.ComponentName;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.SyncStorageEngine;
     28 import android.content.pm.PackageManager;
     29 import android.database.ContentObserver;
     30 import android.location.LocationManager;
     31 import android.net.ConnectivityManager;
     32 import android.net.Uri;
     33 import android.net.wifi.WifiManager;
     34 import android.os.AsyncTask;
     35 import android.os.Handler;
     36 import android.os.IPowerManager;
     37 import android.os.PowerManager;
     38 import android.os.RemoteException;
     39 import android.os.ServiceManager;
     40 import android.provider.Settings;
     41 import android.util.Log;
     42 import android.widget.RemoteViews;
     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                     Settings.Secure.setLocationProviderEnabled(
    543                         resolver,
    544                         LocationManager.GPS_PROVIDER,
    545                         desiredState);
    546                     return desiredState;
    547                 }
    548 
    549                 @Override
    550                 protected void onPostExecute(Boolean result) {
    551                     setCurrentState(
    552                         context,
    553                         result ? STATE_ENABLED : STATE_DISABLED);
    554                     updateWidget(context);
    555                 }
    556             }.execute();
    557         }
    558     }
    559 
    560     /**
    561      * Subclass of StateTracker for sync state.
    562      */
    563     private static final class SyncStateTracker extends StateTracker {
    564         public int getContainerId() { return R.id.btn_sync; }
    565         public int getButtonId() { return R.id.img_sync; }
    566         public int getIndicatorId() { return R.id.ind_sync; }
    567         public int getButtonDescription() { return R.string.gadget_sync; }
    568         public int getButtonImageId(boolean on) {
    569             return on ? R.drawable.ic_appwidget_settings_sync_on_holo
    570                     : R.drawable.ic_appwidget_settings_sync_off_holo;
    571         }
    572 
    573         @Override
    574         public int getActualState(Context context) {
    575             boolean on = ContentResolver.getMasterSyncAutomatically();
    576             return on ? STATE_ENABLED : STATE_DISABLED;
    577         }
    578 
    579         @Override
    580         public void onActualStateChange(Context context, Intent unused) {
    581             setCurrentState(context, getActualState(context));
    582         }
    583 
    584         @Override
    585         public void requestStateChange(final Context context, final boolean desiredState) {
    586             final ConnectivityManager connManager =
    587                     (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    588             final boolean sync = ContentResolver.getMasterSyncAutomatically();
    589 
    590             new AsyncTask<Void, Void, Boolean>() {
    591                 @Override
    592                 protected Boolean doInBackground(Void... args) {
    593                     // Turning sync on.
    594                     if (desiredState) {
    595                         if (!sync) {
    596                             ContentResolver.setMasterSyncAutomatically(true);
    597                         }
    598                         return true;
    599                     }
    600 
    601                     // Turning sync off
    602                     if (sync) {
    603                         ContentResolver.setMasterSyncAutomatically(false);
    604                     }
    605                     return false;
    606                 }
    607 
    608                 @Override
    609                 protected void onPostExecute(Boolean result) {
    610                     setCurrentState(
    611                         context,
    612                         result ? STATE_ENABLED : STATE_DISABLED);
    613                     updateWidget(context);
    614                 }
    615             }.execute();
    616         }
    617     }
    618 
    619     private static void checkObserver(Context context) {
    620         if (sSettingsObserver == null) {
    621             sSettingsObserver = new SettingsObserver(new Handler(),
    622                     context.getApplicationContext());
    623             sSettingsObserver.startObserving();
    624         }
    625     }
    626 
    627     @Override
    628     public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    629             int[] appWidgetIds) {
    630         // Update each requested appWidgetId
    631         RemoteViews view = buildUpdate(context);
    632 
    633         for (int i = 0; i < appWidgetIds.length; i++) {
    634             appWidgetManager.updateAppWidget(appWidgetIds[i], view);
    635         }
    636     }
    637 
    638     @Override
    639     public void onEnabled(Context context) {
    640         checkObserver(context);
    641     }
    642 
    643     @Override
    644     public void onDisabled(Context context) {
    645         if (sSettingsObserver != null) {
    646             sSettingsObserver.stopObserving();
    647             sSettingsObserver = null;
    648         }
    649     }
    650 
    651     /**
    652      * Load image for given widget and build {@link RemoteViews} for it.
    653      */
    654     static RemoteViews buildUpdate(Context context) {
    655         RemoteViews views = new RemoteViews(context.getPackageName(),
    656                 R.layout.widget);
    657         views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context,
    658                 BUTTON_WIFI));
    659         views.setOnClickPendingIntent(R.id.btn_brightness,
    660                 getLaunchPendingIntent(context,
    661                         BUTTON_BRIGHTNESS));
    662         views.setOnClickPendingIntent(R.id.btn_sync,
    663                 getLaunchPendingIntent(context,
    664                         BUTTON_SYNC));
    665         views.setOnClickPendingIntent(R.id.btn_gps,
    666                 getLaunchPendingIntent(context, BUTTON_GPS));
    667         views.setOnClickPendingIntent(R.id.btn_bluetooth,
    668                 getLaunchPendingIntent(context,
    669                         BUTTON_BLUETOOTH));
    670 
    671         updateButtons(views, context);
    672         return views;
    673     }
    674 
    675     /**
    676      * Updates the widget when something changes, or when a button is pushed.
    677      *
    678      * @param context
    679      */
    680     public static void updateWidget(Context context) {
    681         RemoteViews views = buildUpdate(context);
    682         // Update specific list of appWidgetIds if given, otherwise default to all
    683         final AppWidgetManager gm = AppWidgetManager.getInstance(context);
    684         gm.updateAppWidget(THIS_APPWIDGET, views);
    685         checkObserver(context);
    686     }
    687 
    688     /**
    689      * Updates the buttons based on the underlying states of wifi, etc.
    690      *
    691      * @param views   The RemoteViews to update.
    692      * @param context
    693      */
    694     private static void updateButtons(RemoteViews views, Context context) {
    695         sWifiState.setImageViewResources(context, views);
    696         sBluetoothState.setImageViewResources(context, views);
    697         sGpsState.setImageViewResources(context, views);
    698         sSyncState.setImageViewResources(context, views);
    699 
    700         if (getBrightnessMode(context)) {
    701             views.setContentDescription(R.id.btn_brightness,
    702                     context.getString(R.string.gadget_brightness_template,
    703                             context.getString(R.string.gadget_brightness_state_auto)));
    704             views.setImageViewResource(R.id.img_brightness,
    705                     R.drawable.ic_appwidget_settings_brightness_auto_holo);
    706             views.setImageViewResource(R.id.ind_brightness,
    707                     R.drawable.appwidget_settings_ind_on_r_holo);
    708         } else {
    709             final int brightness = getBrightness(context);
    710             final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    711             // Set the icon
    712             final int full = (int)(pm.getMaximumScreenBrightnessSetting()
    713                     * FULL_BRIGHTNESS_THRESHOLD);
    714             final int half = (int)(pm.getMaximumScreenBrightnessSetting()
    715                     * HALF_BRIGHTNESS_THRESHOLD);
    716             if (brightness > full) {
    717                 views.setContentDescription(R.id.btn_brightness,
    718                         context.getString(R.string.gadget_brightness_template,
    719                                 context.getString(R.string.gadget_brightness_state_full)));
    720                 views.setImageViewResource(R.id.img_brightness,
    721                         R.drawable.ic_appwidget_settings_brightness_full_holo);
    722             } else if (brightness > half) {
    723                 views.setContentDescription(R.id.btn_brightness,
    724                         context.getString(R.string.gadget_brightness_template,
    725                                 context.getString(R.string.gadget_brightness_state_half)));
    726                 views.setImageViewResource(R.id.img_brightness,
    727                         R.drawable.ic_appwidget_settings_brightness_half_holo);
    728             } else {
    729                 views.setContentDescription(R.id.btn_brightness,
    730                         context.getString(R.string.gadget_brightness_template,
    731                                 context.getString(R.string.gadget_brightness_state_off)));
    732                 views.setImageViewResource(R.id.img_brightness,
    733                         R.drawable.ic_appwidget_settings_brightness_off_holo);
    734             }
    735             // Set the ON state
    736             if (brightness > half) {
    737                 views.setImageViewResource(R.id.ind_brightness,
    738                         R.drawable.appwidget_settings_ind_on_r_holo);
    739             } else {
    740                 views.setImageViewResource(R.id.ind_brightness,
    741                         R.drawable.appwidget_settings_ind_off_r_holo);
    742             }
    743         }
    744     }
    745 
    746     /**
    747      * Creates PendingIntent to notify the widget of a button click.
    748      *
    749      * @param context
    750      * @return
    751      */
    752     private static PendingIntent getLaunchPendingIntent(Context context,
    753             int buttonId) {
    754         Intent launchIntent = new Intent();
    755         launchIntent.setClass(context, SettingsAppWidgetProvider.class);
    756         launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
    757         launchIntent.setData(Uri.parse("custom:" + buttonId));
    758         PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
    759                 launchIntent, 0 /* no flags */);
    760         return pi;
    761     }
    762 
    763     /**
    764      * Receives and processes a button pressed intent or state change.
    765      *
    766      * @param context
    767      * @param intent  Indicates the pressed button.
    768      */
    769     @Override
    770     public void onReceive(Context context, Intent intent) {
    771         super.onReceive(context, intent);
    772         String action = intent.getAction();
    773         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    774             sWifiState.onActualStateChange(context, intent);
    775         } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
    776             sBluetoothState.onActualStateChange(context, intent);
    777         } else if (LocationManager.PROVIDERS_CHANGED_ACTION.equals(action)) {
    778             sGpsState.onActualStateChange(context, intent);
    779         } else if (SyncStorageEngine.SYNC_CONNECTION_SETTING_CHANGED_INTENT.getAction()
    780                 .equals(action)) {
    781             sSyncState.onActualStateChange(context, intent);
    782         } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
    783             Uri data = intent.getData();
    784             int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
    785             if (buttonId == BUTTON_WIFI) {
    786                 sWifiState.toggleState(context);
    787             } else if (buttonId == BUTTON_BRIGHTNESS) {
    788                 toggleBrightness(context);
    789             } else if (buttonId == BUTTON_SYNC) {
    790                 sSyncState.toggleState(context);
    791             } else if (buttonId == BUTTON_GPS) {
    792                 sGpsState.toggleState(context);
    793             } else if (buttonId == BUTTON_BLUETOOTH) {
    794                 sBluetoothState.toggleState(context);
    795             }
    796         } else {
    797             // Don't fall-through to updating the widget.  The Intent
    798             // was something unrelated or that our super class took
    799             // care of.
    800             return;
    801         }
    802 
    803         // State changes fall through
    804         updateWidget(context);
    805     }
    806 
    807     /**
    808      * Gets brightness level.
    809      *
    810      * @param context
    811      * @return brightness level between 0 and 255.
    812      */
    813     private static int getBrightness(Context context) {
    814         try {
    815             int brightness = Settings.System.getInt(context.getContentResolver(),
    816                     Settings.System.SCREEN_BRIGHTNESS);
    817             return brightness;
    818         } catch (Exception e) {
    819         }
    820         return 0;
    821     }
    822 
    823     /**
    824      * Gets state of brightness mode.
    825      *
    826      * @param context
    827      * @return true if auto brightness is on.
    828      */
    829     private static boolean getBrightnessMode(Context context) {
    830         try {
    831             int brightnessMode = Settings.System.getInt(context.getContentResolver(),
    832                     Settings.System.SCREEN_BRIGHTNESS_MODE);
    833             return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
    834         } catch (Exception e) {
    835             Log.d(TAG, "getBrightnessMode: " + e);
    836         }
    837         return false;
    838     }
    839 
    840     /**
    841      * Increases or decreases the brightness.
    842      *
    843      * @param context
    844      */
    845     private void toggleBrightness(Context context) {
    846         try {
    847             IPowerManager power = IPowerManager.Stub.asInterface(
    848                     ServiceManager.getService("power"));
    849             if (power != null) {
    850                 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    851 
    852                 ContentResolver cr = context.getContentResolver();
    853                 int brightness = Settings.System.getInt(cr,
    854                         Settings.System.SCREEN_BRIGHTNESS);
    855                 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    856                 //Only get brightness setting if available
    857                 if (context.getResources().getBoolean(
    858                         com.android.internal.R.bool.config_automatic_brightness_available)) {
    859                     brightnessMode = Settings.System.getInt(cr,
    860                             Settings.System.SCREEN_BRIGHTNESS_MODE);
    861                 }
    862 
    863                 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
    864                 // Technically, not a toggle...
    865                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
    866                     brightness = pm.getMinimumScreenBrightnessSetting();
    867                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    868                 } else if (brightness < pm.getDefaultScreenBrightnessSetting()) {
    869                     brightness = pm.getDefaultScreenBrightnessSetting();
    870                 } else if (brightness < pm.getMaximumScreenBrightnessSetting()) {
    871                     brightness = pm.getMaximumScreenBrightnessSetting();
    872                 } else {
    873                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
    874                     brightness = pm.getMinimumScreenBrightnessSetting();
    875                 }
    876 
    877                 if (context.getResources().getBoolean(
    878                         com.android.internal.R.bool.config_automatic_brightness_available)) {
    879                     // Set screen brightness mode (automatic or manual)
    880                     Settings.System.putInt(context.getContentResolver(),
    881                             Settings.System.SCREEN_BRIGHTNESS_MODE,
    882                             brightnessMode);
    883                 } else {
    884                     // Make sure we set the brightness if automatic mode isn't available
    885                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    886                 }
    887                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
    888                     power.setTemporaryScreenBrightnessSettingOverride(brightness);
    889                     Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
    890                 }
    891             }
    892         } catch (RemoteException e) {
    893             Log.d(TAG, "toggleBrightness: " + e);
    894         } catch (Settings.SettingNotFoundException e) {
    895             Log.d(TAG, "toggleBrightness: " + e);
    896         }
    897     }
    898 
    899     /** Observer to watch for changes to the BRIGHTNESS setting */
    900     private static class SettingsObserver extends ContentObserver {
    901 
    902         private Context mContext;
    903 
    904         SettingsObserver(Handler handler, Context context) {
    905             super(handler);
    906             mContext = context;
    907         }
    908 
    909         void startObserving() {
    910             ContentResolver resolver = mContext.getContentResolver();
    911             // Listen to brightness and brightness mode
    912             resolver.registerContentObserver(Settings.System
    913                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this);
    914             resolver.registerContentObserver(Settings.System
    915                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this);
    916         }
    917 
    918         void stopObserving() {
    919             mContext.getContentResolver().unregisterContentObserver(this);
    920         }
    921 
    922         @Override
    923         public void onChange(boolean selfChange) {
    924             updateWidget(mContext);
    925         }
    926     }
    927 
    928 }
    929