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_LOCATION = 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 sLocationState = new LocationStateTracker();
    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 location state.
    510      */
    511     private static final class LocationStateTracker extends StateTracker {
    512         private int mCurrentLocationMode = Settings.Secure.LOCATION_MODE_OFF;
    513 
    514         public int getContainerId() { return R.id.btn_location; }
    515         public int getButtonId() { return R.id.img_location; }
    516         public int getIndicatorId() { return R.id.ind_location; }
    517         public int getButtonDescription() { return R.string.gadget_location; }
    518         public int getButtonImageId(boolean on) {
    519             if (on) {
    520                 switch (mCurrentLocationMode) {
    521                     case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
    522                     case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
    523                         return R.drawable.ic_appwidget_settings_location_on_holo;
    524                     default:
    525                         return R.drawable.ic_appwidget_settings_location_saving_holo;
    526                 }
    527             }
    528 
    529             return R.drawable.ic_appwidget_settings_location_off_holo;
    530         }
    531 
    532         @Override
    533         public int getActualState(Context context) {
    534             ContentResolver resolver = context.getContentResolver();
    535             mCurrentLocationMode = Settings.Secure.getInt(resolver,
    536                     Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
    537             return (mCurrentLocationMode == Settings.Secure.LOCATION_MODE_OFF)
    538                     ? STATE_DISABLED : STATE_ENABLED;
    539         }
    540 
    541         @Override
    542         public void onActualStateChange(Context context, Intent unused) {
    543             // Note: the broadcast location providers changed intent
    544             // doesn't include an extras bundles saying what the new value is.
    545             setCurrentState(context, getActualState(context));
    546         }
    547 
    548         @Override
    549         public void requestStateChange(final Context context, final boolean desiredState) {
    550             final ContentResolver resolver = context.getContentResolver();
    551             new AsyncTask<Void, Void, Boolean>() {
    552                 @Override
    553                 protected Boolean doInBackground(Void... args) {
    554                     final UserManager um =
    555                             (UserManager) context.getSystemService(Context.USER_SERVICE);
    556                     if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
    557                         int currentMode = Settings.Secure.getInt(resolver,
    558                                 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
    559                         int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
    560                         switch (currentMode) {
    561                             case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
    562                                 mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING;
    563                                 break;
    564                             case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
    565                                 mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
    566                                 break;
    567                             case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
    568                                 mode = Settings.Secure.LOCATION_MODE_OFF;
    569                                 break;
    570                             case Settings.Secure.LOCATION_MODE_OFF:
    571                                 mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
    572                                 break;
    573                         }
    574                         Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode);
    575                         return mode != Settings.Secure.LOCATION_MODE_OFF;
    576                     }
    577 
    578                     return getActualState(context) == STATE_ENABLED;
    579                 }
    580 
    581                 @Override
    582                 protected void onPostExecute(Boolean result) {
    583                     setCurrentState(
    584                         context,
    585                         result ? STATE_ENABLED : STATE_DISABLED);
    586                     updateWidget(context);
    587                 }
    588             }.execute();
    589         }
    590     }
    591 
    592     /**
    593      * Subclass of StateTracker for sync state.
    594      */
    595     private static final class SyncStateTracker extends StateTracker {
    596         public int getContainerId() { return R.id.btn_sync; }
    597         public int getButtonId() { return R.id.img_sync; }
    598         public int getIndicatorId() { return R.id.ind_sync; }
    599         public int getButtonDescription() { return R.string.gadget_sync; }
    600         public int getButtonImageId(boolean on) {
    601             return on ? R.drawable.ic_appwidget_settings_sync_on_holo
    602                     : R.drawable.ic_appwidget_settings_sync_off_holo;
    603         }
    604 
    605         @Override
    606         public int getActualState(Context context) {
    607             boolean on = ContentResolver.getMasterSyncAutomatically();
    608             return on ? STATE_ENABLED : STATE_DISABLED;
    609         }
    610 
    611         @Override
    612         public void onActualStateChange(Context context, Intent unused) {
    613             setCurrentState(context, getActualState(context));
    614         }
    615 
    616         @Override
    617         public void requestStateChange(final Context context, final boolean desiredState) {
    618             final ConnectivityManager connManager =
    619                     (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    620             final boolean sync = ContentResolver.getMasterSyncAutomatically();
    621 
    622             new AsyncTask<Void, Void, Boolean>() {
    623                 @Override
    624                 protected Boolean doInBackground(Void... args) {
    625                     // Turning sync on.
    626                     if (desiredState) {
    627                         if (!sync) {
    628                             ContentResolver.setMasterSyncAutomatically(true);
    629                         }
    630                         return true;
    631                     }
    632 
    633                     // Turning sync off
    634                     if (sync) {
    635                         ContentResolver.setMasterSyncAutomatically(false);
    636                     }
    637                     return false;
    638                 }
    639 
    640                 @Override
    641                 protected void onPostExecute(Boolean result) {
    642                     setCurrentState(
    643                         context,
    644                         result ? STATE_ENABLED : STATE_DISABLED);
    645                     updateWidget(context);
    646                 }
    647             }.execute();
    648         }
    649     }
    650 
    651     private static void checkObserver(Context context) {
    652         if (sSettingsObserver == null) {
    653             sSettingsObserver = new SettingsObserver(new Handler(),
    654                     context.getApplicationContext());
    655             sSettingsObserver.startObserving();
    656         }
    657     }
    658 
    659     @Override
    660     public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    661             int[] appWidgetIds) {
    662         // Update each requested appWidgetId
    663         RemoteViews view = buildUpdate(context);
    664 
    665         for (int i = 0; i < appWidgetIds.length; i++) {
    666             appWidgetManager.updateAppWidget(appWidgetIds[i], view);
    667         }
    668     }
    669 
    670     @Override
    671     public void onEnabled(Context context) {
    672         checkObserver(context);
    673     }
    674 
    675     @Override
    676     public void onDisabled(Context context) {
    677         if (sSettingsObserver != null) {
    678             sSettingsObserver.stopObserving();
    679             sSettingsObserver = null;
    680         }
    681     }
    682 
    683     /**
    684      * Load image for given widget and build {@link RemoteViews} for it.
    685      */
    686     static RemoteViews buildUpdate(Context context) {
    687         RemoteViews views = new RemoteViews(context.getPackageName(),
    688                 R.layout.widget);
    689         views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context,
    690                 BUTTON_WIFI));
    691         views.setOnClickPendingIntent(R.id.btn_brightness,
    692                 getLaunchPendingIntent(context,
    693                         BUTTON_BRIGHTNESS));
    694         views.setOnClickPendingIntent(R.id.btn_sync,
    695                 getLaunchPendingIntent(context,
    696                         BUTTON_SYNC));
    697         views.setOnClickPendingIntent(R.id.btn_location,
    698                 getLaunchPendingIntent(context, BUTTON_LOCATION));
    699         views.setOnClickPendingIntent(R.id.btn_bluetooth,
    700                 getLaunchPendingIntent(context,
    701                         BUTTON_BLUETOOTH));
    702 
    703         updateButtons(views, context);
    704         return views;
    705     }
    706 
    707     /**
    708      * Updates the widget when something changes, or when a button is pushed.
    709      *
    710      * @param context
    711      */
    712     public static void updateWidget(Context context) {
    713         RemoteViews views = buildUpdate(context);
    714         // Update specific list of appWidgetIds if given, otherwise default to all
    715         final AppWidgetManager gm = AppWidgetManager.getInstance(context);
    716         gm.updateAppWidget(THIS_APPWIDGET, views);
    717         checkObserver(context);
    718     }
    719 
    720     /**
    721      * Updates the buttons based on the underlying states of wifi, etc.
    722      *
    723      * @param views   The RemoteViews to update.
    724      * @param context
    725      */
    726     private static void updateButtons(RemoteViews views, Context context) {
    727         sWifiState.setImageViewResources(context, views);
    728         sBluetoothState.setImageViewResources(context, views);
    729         sLocationState.setImageViewResources(context, views);
    730         sSyncState.setImageViewResources(context, views);
    731 
    732         if (getBrightnessMode(context)) {
    733             views.setContentDescription(R.id.btn_brightness,
    734                     context.getString(R.string.gadget_brightness_template,
    735                             context.getString(R.string.gadget_brightness_state_auto)));
    736             views.setImageViewResource(R.id.img_brightness,
    737                     R.drawable.ic_appwidget_settings_brightness_auto_holo);
    738             views.setImageViewResource(R.id.ind_brightness,
    739                     R.drawable.appwidget_settings_ind_on_r_holo);
    740         } else {
    741             final int brightness = getBrightness(context);
    742             final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    743             // Set the icon
    744             final int full = (int)(pm.getMaximumScreenBrightnessSetting()
    745                     * FULL_BRIGHTNESS_THRESHOLD);
    746             final int half = (int)(pm.getMaximumScreenBrightnessSetting()
    747                     * HALF_BRIGHTNESS_THRESHOLD);
    748             if (brightness > full) {
    749                 views.setContentDescription(R.id.btn_brightness,
    750                         context.getString(R.string.gadget_brightness_template,
    751                                 context.getString(R.string.gadget_brightness_state_full)));
    752                 views.setImageViewResource(R.id.img_brightness,
    753                         R.drawable.ic_appwidget_settings_brightness_full_holo);
    754             } else if (brightness > half) {
    755                 views.setContentDescription(R.id.btn_brightness,
    756                         context.getString(R.string.gadget_brightness_template,
    757                                 context.getString(R.string.gadget_brightness_state_half)));
    758                 views.setImageViewResource(R.id.img_brightness,
    759                         R.drawable.ic_appwidget_settings_brightness_half_holo);
    760             } else {
    761                 views.setContentDescription(R.id.btn_brightness,
    762                         context.getString(R.string.gadget_brightness_template,
    763                                 context.getString(R.string.gadget_brightness_state_off)));
    764                 views.setImageViewResource(R.id.img_brightness,
    765                         R.drawable.ic_appwidget_settings_brightness_off_holo);
    766             }
    767             // Set the ON state
    768             if (brightness > half) {
    769                 views.setImageViewResource(R.id.ind_brightness,
    770                         R.drawable.appwidget_settings_ind_on_r_holo);
    771             } else {
    772                 views.setImageViewResource(R.id.ind_brightness,
    773                         R.drawable.appwidget_settings_ind_off_r_holo);
    774             }
    775         }
    776     }
    777 
    778     /**
    779      * Creates PendingIntent to notify the widget of a button click.
    780      *
    781      * @param context
    782      * @return
    783      */
    784     private static PendingIntent getLaunchPendingIntent(Context context,
    785             int buttonId) {
    786         Intent launchIntent = new Intent();
    787         launchIntent.setClass(context, SettingsAppWidgetProvider.class);
    788         launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
    789         launchIntent.setData(Uri.parse("custom:" + buttonId));
    790         PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */,
    791                 launchIntent, 0 /* no flags */);
    792         return pi;
    793     }
    794 
    795     /**
    796      * Receives and processes a button pressed intent or state change.
    797      *
    798      * @param context
    799      * @param intent  Indicates the pressed button.
    800      */
    801     @Override
    802     public void onReceive(Context context, Intent intent) {
    803         super.onReceive(context, intent);
    804         String action = intent.getAction();
    805         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    806             sWifiState.onActualStateChange(context, intent);
    807         } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
    808             sBluetoothState.onActualStateChange(context, intent);
    809         } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
    810             sLocationState.onActualStateChange(context, intent);
    811         } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) {
    812             sSyncState.onActualStateChange(context, intent);
    813         } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
    814             Uri data = intent.getData();
    815             int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
    816             if (buttonId == BUTTON_WIFI) {
    817                 sWifiState.toggleState(context);
    818             } else if (buttonId == BUTTON_BRIGHTNESS) {
    819                 toggleBrightness(context);
    820             } else if (buttonId == BUTTON_SYNC) {
    821                 sSyncState.toggleState(context);
    822             } else if (buttonId == BUTTON_LOCATION) {
    823                 sLocationState.toggleState(context);
    824             } else if (buttonId == BUTTON_BLUETOOTH) {
    825                 sBluetoothState.toggleState(context);
    826             }
    827         } else {
    828             // Don't fall-through to updating the widget.  The Intent
    829             // was something unrelated or that our super class took
    830             // care of.
    831             return;
    832         }
    833 
    834         // State changes fall through
    835         updateWidget(context);
    836     }
    837 
    838     /**
    839      * Gets brightness level.
    840      *
    841      * @param context
    842      * @return brightness level between 0 and 255.
    843      */
    844     private static int getBrightness(Context context) {
    845         try {
    846             int brightness = Settings.System.getInt(context.getContentResolver(),
    847                     Settings.System.SCREEN_BRIGHTNESS);
    848             return brightness;
    849         } catch (Exception e) {
    850         }
    851         return 0;
    852     }
    853 
    854     /**
    855      * Gets state of brightness mode.
    856      *
    857      * @param context
    858      * @return true if auto brightness is on.
    859      */
    860     private static boolean getBrightnessMode(Context context) {
    861         try {
    862             int brightnessMode = Settings.System.getInt(context.getContentResolver(),
    863                     Settings.System.SCREEN_BRIGHTNESS_MODE);
    864             return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
    865         } catch (Exception e) {
    866             Log.d(TAG, "getBrightnessMode: " + e);
    867         }
    868         return false;
    869     }
    870 
    871     /**
    872      * Increases or decreases the brightness.
    873      *
    874      * @param context
    875      */
    876     private void toggleBrightness(Context context) {
    877         try {
    878             IPowerManager power = IPowerManager.Stub.asInterface(
    879                     ServiceManager.getService("power"));
    880             if (power != null) {
    881                 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    882 
    883                 ContentResolver cr = context.getContentResolver();
    884                 int brightness = Settings.System.getInt(cr,
    885                         Settings.System.SCREEN_BRIGHTNESS);
    886                 int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    887                 //Only get brightness setting if available
    888                 if (context.getResources().getBoolean(
    889                         com.android.internal.R.bool.config_automatic_brightness_available)) {
    890                     brightnessMode = Settings.System.getInt(cr,
    891                             Settings.System.SCREEN_BRIGHTNESS_MODE);
    892                 }
    893 
    894                 // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM
    895                 // Technically, not a toggle...
    896                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
    897                     brightness = pm.getMinimumScreenBrightnessSetting();
    898                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    899                 } else if (brightness < pm.getDefaultScreenBrightnessSetting()) {
    900                     brightness = pm.getDefaultScreenBrightnessSetting();
    901                 } else if (brightness < pm.getMaximumScreenBrightnessSetting()) {
    902                     brightness = pm.getMaximumScreenBrightnessSetting();
    903                 } else {
    904                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
    905                     brightness = pm.getMinimumScreenBrightnessSetting();
    906                 }
    907 
    908                 if (context.getResources().getBoolean(
    909                         com.android.internal.R.bool.config_automatic_brightness_available)) {
    910                     // Set screen brightness mode (automatic or manual)
    911                     Settings.System.putInt(context.getContentResolver(),
    912                             Settings.System.SCREEN_BRIGHTNESS_MODE,
    913                             brightnessMode);
    914                 } else {
    915                     // Make sure we set the brightness if automatic mode isn't available
    916                     brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
    917                 }
    918                 if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
    919                     power.setTemporaryScreenBrightnessSettingOverride(brightness);
    920                     Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness);
    921                 }
    922             }
    923         } catch (RemoteException e) {
    924             Log.d(TAG, "toggleBrightness: " + e);
    925         } catch (Settings.SettingNotFoundException e) {
    926             Log.d(TAG, "toggleBrightness: " + e);
    927         }
    928     }
    929 
    930     /** Observer to watch for changes to the BRIGHTNESS setting */
    931     private static class SettingsObserver extends ContentObserver {
    932 
    933         private Context mContext;
    934 
    935         SettingsObserver(Handler handler, Context context) {
    936             super(handler);
    937             mContext = context;
    938         }
    939 
    940         void startObserving() {
    941             ContentResolver resolver = mContext.getContentResolver();
    942             // Listen to brightness and brightness mode
    943             resolver.registerContentObserver(Settings.System
    944                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this);
    945             resolver.registerContentObserver(Settings.System
    946                     .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this);
    947         }
    948 
    949         void stopObserving() {
    950             mContext.getContentResolver().unregisterContentObserver(this);
    951         }
    952 
    953         @Override
    954         public void onChange(boolean selfChange) {
    955             updateWidget(mContext);
    956         }
    957     }
    958 
    959 }
    960