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          */
toggleState(Context context)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          */
getContainerId()166         public abstract int getContainerId();
167 
168         /**
169          * Return the ID of the main large image button for the setting.
170          */
getButtonId()171         public abstract int getButtonId();
172 
173         /**
174          * Returns the small indicator image ID underneath the setting.
175          */
getIndicatorId()176         public abstract int getIndicatorId();
177 
178         /**
179          * Returns the resource ID of the setting's content description.
180          */
getButtonDescription()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          */
getButtonImageId(boolean on)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          */
getPosition()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          */
setImageViewResources(Context context, RemoteViews views)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          */
getContentDescription(Context context, int stateResId)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          */
onActualStateChange(Context context, Intent intent)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          */
setCurrentState(Context context, int newState)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          */
isTurningOn()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          */
getTriState(Context context)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          */
getActualState(Context context)341         public abstract int getActualState(Context context);
342 
343         /**
344          * Actually make the desired change to the underlying radio
345          * API.
346          */
requestStateChange(Context context, boolean desiredState)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 {
getContainerId()354         public int getContainerId() { return R.id.btn_wifi; }
getButtonId()355         public int getButtonId() { return R.id.img_wifi; }
getIndicatorId()356         public int getIndicatorId() { return R.id.ind_wifi; }
getButtonDescription()357         public int getButtonDescription() { return R.string.gadget_wifi; }
getButtonImageId(boolean on)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
getPosition()364         public int getPosition() { return POS_LEFT; }
365 
366         @Override
getActualState(Context context)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
requestStateChange(Context context, final boolean desiredState)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
onActualStateChange(Context context, Intent intent)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          */
wifiStateToFiveState(int wifiState)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 {
getContainerId()439         public int getContainerId() { return R.id.btn_bluetooth; }
getButtonId()440         public int getButtonId() { return R.id.img_bluetooth; }
getIndicatorId()441         public int getIndicatorId() { return R.id.ind_bluetooth; }
getButtonDescription()442         public int getButtonDescription() { return R.string.gadget_bluetooth; }
getButtonImageId(boolean on)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
getActualState(Context context)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
requestStateChange(Context context, final boolean desiredState)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
onActualStateChange(Context context, Intent intent)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          */
bluetoothStateToFiveState(int bluetoothState)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 
getContainerId()514         public int getContainerId() { return R.id.btn_location; }
getButtonId()515         public int getButtonId() { return R.id.img_location; }
getIndicatorId()516         public int getIndicatorId() { return R.id.ind_location; }
getButtonDescription()517         public int getButtonDescription() { return R.string.gadget_location; }
getButtonImageId(boolean on)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
getActualState(Context context)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
onActualStateChange(Context context, Intent unused)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
requestStateChange(final Context context, final boolean desiredState)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 {
getContainerId()596         public int getContainerId() { return R.id.btn_sync; }
getButtonId()597         public int getButtonId() { return R.id.img_sync; }
getIndicatorId()598         public int getIndicatorId() { return R.id.ind_sync; }
getButtonDescription()599         public int getButtonDescription() { return R.string.gadget_sync; }
getButtonImageId(boolean on)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
getActualState(Context context)606         public int getActualState(Context context) {
607             boolean on = ContentResolver.getMasterSyncAutomatically();
608             return on ? STATE_ENABLED : STATE_DISABLED;
609         }
610 
611         @Override
onActualStateChange(Context context, Intent unused)612         public void onActualStateChange(Context context, Intent unused) {
613             setCurrentState(context, getActualState(context));
614         }
615 
616         @Override
requestStateChange(final Context context, final boolean desiredState)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 
checkObserver(Context context)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
onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)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
onEnabled(Context context)671     public void onEnabled(Context context) {
672         checkObserver(context);
673     }
674 
675     @Override
onDisabled(Context context)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      */
buildUpdate(Context context)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      */
updateWidget(Context context)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      */
updateButtons(RemoteViews views, Context context)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      */
getLaunchPendingIntent(Context context, int buttonId)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
onReceive(Context context, Intent intent)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      */
getBrightness(Context context)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      */
getBrightnessMode(Context context)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      */
toggleBrightness(Context context)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 
SettingsObserver(Handler handler, Context context)935         SettingsObserver(Handler handler, Context context) {
936             super(handler);
937             mContext = context;
938         }
939 
startObserving()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 
stopObserving()949         void stopObserving() {
950             mContext.getContentResolver().unregisterContentObserver(this);
951         }
952 
953         @Override
onChange(boolean selfChange)954         public void onChange(boolean selfChange) {
955             updateWidget(mContext);
956         }
957     }
958 
959 }
960