| /* | |
| * Copyright (C) 2009 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.android.settings.widget; | |
| import android.app.PendingIntent; | |
| import android.appwidget.AppWidgetManager; | |
| import android.appwidget.AppWidgetProvider; | |
| import android.bluetooth.BluetoothAdapter; | |
| import android.content.ComponentName; | |
| import android.content.ContentResolver; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.database.ContentObserver; | |
| import android.location.LocationManager; | |
| import android.net.ConnectivityManager; | |
| import android.net.Uri; | |
| import android.net.wifi.WifiManager; | |
| import android.os.AsyncTask; | |
| import android.os.Handler; | |
| import android.os.IPowerManager; | |
| import android.os.PowerManager; | |
| import android.os.RemoteException; | |
| import android.os.ServiceManager; | |
| import android.os.UserManager; | |
| import android.provider.Settings; | |
| import android.util.Log; | |
| import android.widget.RemoteViews; | |
| import com.android.settings.R; | |
| import com.android.settings.bluetooth.Utils; | |
| import com.android.settingslib.bluetooth.LocalBluetoothAdapter; | |
| import com.android.settingslib.bluetooth.LocalBluetoothManager; | |
| /** | |
| * Provides control of power-related settings from a widget. | |
| */ | |
| public class SettingsAppWidgetProvider extends AppWidgetProvider { | |
| static final String TAG = "SettingsAppWidgetProvider"; | |
| static final ComponentName THIS_APPWIDGET = | |
| new ComponentName("com.android.settings", | |
| "com.android.settings.widget.SettingsAppWidgetProvider"); | |
| private static LocalBluetoothAdapter sLocalBluetoothAdapter = null; | |
| private static final int BUTTON_WIFI = 0; | |
| private static final int BUTTON_BRIGHTNESS = 1; | |
| private static final int BUTTON_SYNC = 2; | |
| private static final int BUTTON_LOCATION = 3; | |
| private static final int BUTTON_BLUETOOTH = 4; | |
| // This widget keeps track of two sets of states: | |
| // "3-state": STATE_DISABLED, STATE_ENABLED, STATE_INTERMEDIATE | |
| // "5-state": STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_UNKNOWN | |
| private static final int STATE_DISABLED = 0; | |
| private static final int STATE_ENABLED = 1; | |
| private static final int STATE_TURNING_ON = 2; | |
| private static final int STATE_TURNING_OFF = 3; | |
| private static final int STATE_UNKNOWN = 4; | |
| private static final int STATE_INTERMEDIATE = 5; | |
| // Position in the widget bar, to enable different graphics for left, center and right buttons | |
| private static final int POS_LEFT = 0; | |
| private static final int POS_CENTER = 1; | |
| private static final int POS_RIGHT = 2; | |
| private static final int[] IND_DRAWABLE_OFF = { | |
| R.drawable.appwidget_settings_ind_off_l_holo, | |
| R.drawable.appwidget_settings_ind_off_c_holo, | |
| R.drawable.appwidget_settings_ind_off_r_holo | |
| }; | |
| private static final int[] IND_DRAWABLE_MID = { | |
| R.drawable.appwidget_settings_ind_mid_l_holo, | |
| R.drawable.appwidget_settings_ind_mid_c_holo, | |
| R.drawable.appwidget_settings_ind_mid_r_holo | |
| }; | |
| private static final int[] IND_DRAWABLE_ON = { | |
| R.drawable.appwidget_settings_ind_on_l_holo, | |
| R.drawable.appwidget_settings_ind_on_c_holo, | |
| R.drawable.appwidget_settings_ind_on_r_holo | |
| }; | |
| /** Minimum brightness at which the indicator is shown at half-full and ON */ | |
| private static final float HALF_BRIGHTNESS_THRESHOLD = 0.3f; | |
| /** Minimum brightness at which the indicator is shown at full */ | |
| private static final float FULL_BRIGHTNESS_THRESHOLD = 0.8f; | |
| private static final StateTracker sWifiState = new WifiStateTracker(); | |
| private static final StateTracker sBluetoothState = new BluetoothStateTracker(); | |
| private static final StateTracker sLocationState = new LocationStateTracker(); | |
| private static final StateTracker sSyncState = new SyncStateTracker(); | |
| private static SettingsObserver sSettingsObserver; | |
| /** | |
| * The state machine for a setting's toggling, tracking reality | |
| * versus the user's intent. | |
| * | |
| * This is necessary because reality moves relatively slowly | |
| * (turning on & off radio drivers), compared to user's | |
| * expectations. | |
| */ | |
| private abstract static class StateTracker { | |
| // Is the state in the process of changing? | |
| private boolean mInTransition = false; | |
| private Boolean mActualState = null; // initially not set | |
| private Boolean mIntendedState = null; // initially not set | |
| // Did a toggle request arrive while a state update was | |
| // already in-flight? If so, the mIntendedState needs to be | |
| // requested when the other one is done, unless we happened to | |
| // arrive at that state already. | |
| private boolean mDeferredStateChangeRequestNeeded = false; | |
| /** | |
| * User pressed a button to change the state. Something | |
| * should immediately appear to the user afterwards, even if | |
| * we effectively do nothing. Their press must be heard. | |
| */ | |
| public final void toggleState(Context context) { | |
| int currentState = getTriState(context); | |
| boolean newState = false; | |
| switch (currentState) { | |
| case STATE_ENABLED: | |
| newState = false; | |
| break; | |
| case STATE_DISABLED: | |
| newState = true; | |
| break; | |
| case STATE_INTERMEDIATE: | |
| if (mIntendedState != null) { | |
| newState = !mIntendedState; | |
| } | |
| break; | |
| } | |
| mIntendedState = newState; | |
| if (mInTransition) { | |
| // We don't send off a transition request if we're | |
| // already transitioning. Makes our state tracking | |
| // easier, and is probably nicer on lower levels. | |
| // (even though they should be able to take it...) | |
| mDeferredStateChangeRequestNeeded = true; | |
| } else { | |
| mInTransition = true; | |
| requestStateChange(context, newState); | |
| } | |
| } | |
| /** | |
| * Return the ID of the clickable container for the setting. | |
| */ | |
| public abstract int getContainerId(); | |
| /** | |
| * Return the ID of the main large image button for the setting. | |
| */ | |
| public abstract int getButtonId(); | |
| /** | |
| * Returns the small indicator image ID underneath the setting. | |
| */ | |
| public abstract int getIndicatorId(); | |
| /** | |
| * Returns the resource ID of the setting's content description. | |
| */ | |
| public abstract int getButtonDescription(); | |
| /** | |
| * Returns the resource ID of the image to show as a function of | |
| * the on-vs-off state. | |
| */ | |
| public abstract int getButtonImageId(boolean on); | |
| /** | |
| * Returns the position in the button bar - either POS_LEFT, POS_RIGHT or POS_CENTER. | |
| */ | |
| public int getPosition() { return POS_CENTER; } | |
| /** | |
| * Updates the remote views depending on the state (off, on, | |
| * turning off, turning on) of the setting. | |
| */ | |
| public final void setImageViewResources(Context context, RemoteViews views) { | |
| int containerId = getContainerId(); | |
| int buttonId = getButtonId(); | |
| int indicatorId = getIndicatorId(); | |
| int pos = getPosition(); | |
| switch (getTriState(context)) { | |
| case STATE_DISABLED: | |
| views.setContentDescription(containerId, | |
| getContentDescription(context, R.string.gadget_state_off)); | |
| views.setImageViewResource(buttonId, getButtonImageId(false)); | |
| views.setImageViewResource( | |
| indicatorId, IND_DRAWABLE_OFF[pos]); | |
| break; | |
| case STATE_ENABLED: | |
| views.setContentDescription(containerId, | |
| getContentDescription(context, R.string.gadget_state_on)); | |
| views.setImageViewResource(buttonId, getButtonImageId(true)); | |
| views.setImageViewResource( | |
| indicatorId, IND_DRAWABLE_ON[pos]); | |
| break; | |
| case STATE_INTERMEDIATE: | |
| // In the transitional state, the bottom green bar | |
| // shows the tri-state (on, off, transitioning), but | |
| // the top dark-gray-or-bright-white logo shows the | |
| // user's intent. This is much easier to see in | |
| // sunlight. | |
| if (isTurningOn()) { | |
| views.setContentDescription(containerId, | |
| getContentDescription(context, R.string.gadget_state_turning_on)); | |
| views.setImageViewResource(buttonId, getButtonImageId(true)); | |
| views.setImageViewResource( | |
| indicatorId, IND_DRAWABLE_MID[pos]); | |
| } else { | |
| views.setContentDescription(containerId, | |
| getContentDescription(context, R.string.gadget_state_turning_off)); | |
| views.setImageViewResource(buttonId, getButtonImageId(false)); | |
| views.setImageViewResource( | |
| indicatorId, IND_DRAWABLE_OFF[pos]); | |
| } | |
| break; | |
| } | |
| } | |
| /** | |
| * Returns the gadget state template populated with the gadget | |
| * description and state. | |
| */ | |
| private final String getContentDescription(Context context, int stateResId) { | |
| final String gadget = context.getString(getButtonDescription()); | |
| final String state = context.getString(stateResId); | |
| return context.getString(R.string.gadget_state_template, gadget, state); | |
| } | |
| /** | |
| * Update internal state from a broadcast state change. | |
| */ | |
| public abstract void onActualStateChange(Context context, Intent intent); | |
| /** | |
| * Sets the value that we're now in. To be called from onActualStateChange. | |
| * | |
| * @param newState one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, | |
| * STATE_TURNING_OFF, STATE_UNKNOWN | |
| */ | |
| protected final void setCurrentState(Context context, int newState) { | |
| final boolean wasInTransition = mInTransition; | |
| switch (newState) { | |
| case STATE_DISABLED: | |
| mInTransition = false; | |
| mActualState = false; | |
| break; | |
| case STATE_ENABLED: | |
| mInTransition = false; | |
| mActualState = true; | |
| break; | |
| case STATE_TURNING_ON: | |
| mInTransition = true; | |
| mActualState = false; | |
| break; | |
| case STATE_TURNING_OFF: | |
| mInTransition = true; | |
| mActualState = true; | |
| break; | |
| } | |
| if (wasInTransition && !mInTransition) { | |
| if (mDeferredStateChangeRequestNeeded) { | |
| Log.v(TAG, "processing deferred state change"); | |
| if (mActualState != null && mIntendedState != null && | |
| mIntendedState.equals(mActualState)) { | |
| Log.v(TAG, "... but intended state matches, so no changes."); | |
| } else if (mIntendedState != null) { | |
| mInTransition = true; | |
| requestStateChange(context, mIntendedState); | |
| } | |
| mDeferredStateChangeRequestNeeded = false; | |
| } | |
| } | |
| } | |
| /** | |
| * If we're in a transition mode, this returns true if we're | |
| * transitioning towards being enabled. | |
| */ | |
| public final boolean isTurningOn() { | |
| return mIntendedState != null && mIntendedState; | |
| } | |
| /** | |
| * Returns simplified 3-state value from underlying 5-state. | |
| * | |
| * @param context | |
| * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE | |
| */ | |
| public final int getTriState(Context context) { | |
| if (mInTransition) { | |
| // If we know we just got a toggle request recently | |
| // (which set mInTransition), don't even ask the | |
| // underlying interface for its state. We know we're | |
| // changing. This avoids blocking the UI thread | |
| // during UI refresh post-toggle if the underlying | |
| // service state accessor has coarse locking on its | |
| // state (to be fixed separately). | |
| return STATE_INTERMEDIATE; | |
| } | |
| switch (getActualState(context)) { | |
| case STATE_DISABLED: | |
| return STATE_DISABLED; | |
| case STATE_ENABLED: | |
| return STATE_ENABLED; | |
| default: | |
| return STATE_INTERMEDIATE; | |
| } | |
| } | |
| /** | |
| * Gets underlying actual state. | |
| * | |
| * @param context | |
| * @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING, STATE_DISABLING, | |
| * or or STATE_UNKNOWN. | |
| */ | |
| public abstract int getActualState(Context context); | |
| /** | |
| * Actually make the desired change to the underlying radio | |
| * API. | |
| */ | |
| protected abstract void requestStateChange(Context context, boolean desiredState); | |
| } | |
| /** | |
| * Subclass of StateTracker to get/set Wifi state. | |
| */ | |
| private static final class WifiStateTracker extends StateTracker { | |
| public int getContainerId() { return R.id.btn_wifi; } | |
| public int getButtonId() { return R.id.img_wifi; } | |
| public int getIndicatorId() { return R.id.ind_wifi; } | |
| public int getButtonDescription() { return R.string.gadget_wifi; } | |
| public int getButtonImageId(boolean on) { | |
| return on ? R.drawable.ic_appwidget_settings_wifi_on_holo | |
| : R.drawable.ic_appwidget_settings_wifi_off_holo; | |
| } | |
| @Override | |
| public int getPosition() { return POS_LEFT; } | |
| @Override | |
| public int getActualState(Context context) { | |
| WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); | |
| if (wifiManager != null) { | |
| return wifiStateToFiveState(wifiManager.getWifiState()); | |
| } | |
| return STATE_UNKNOWN; | |
| } | |
| @Override | |
| protected void requestStateChange(Context context, final boolean desiredState) { | |
| final WifiManager wifiManager = | |
| (WifiManager) context.getSystemService(Context.WIFI_SERVICE); | |
| if (wifiManager == null) { | |
| Log.d(TAG, "No wifiManager."); | |
| return; | |
| } | |
| // Actually request the wifi change and persistent | |
| // settings write off the UI thread, as it can take a | |
| // user-noticeable amount of time, especially if there's | |
| // disk contention. | |
| new AsyncTask<Void, Void, Void>() { | |
| @Override | |
| protected Void doInBackground(Void... args) { | |
| /** | |
| * Disable tethering if enabling Wifi | |
| */ | |
| int wifiApState = wifiManager.getWifiApState(); | |
| if (desiredState && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || | |
| (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { | |
| wifiManager.setWifiApEnabled(null, false); | |
| } | |
| wifiManager.setWifiEnabled(desiredState); | |
| return null; | |
| } | |
| }.execute(); | |
| } | |
| @Override | |
| public void onActualStateChange(Context context, Intent intent) { | |
| if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { | |
| return; | |
| } | |
| int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1); | |
| setCurrentState(context, wifiStateToFiveState(wifiState)); | |
| } | |
| /** | |
| * Converts WifiManager's state values into our | |
| * Wifi/Bluetooth-common state values. | |
| */ | |
| private static int wifiStateToFiveState(int wifiState) { | |
| switch (wifiState) { | |
| case WifiManager.WIFI_STATE_DISABLED: | |
| return STATE_DISABLED; | |
| case WifiManager.WIFI_STATE_ENABLED: | |
| return STATE_ENABLED; | |
| case WifiManager.WIFI_STATE_DISABLING: | |
| return STATE_TURNING_OFF; | |
| case WifiManager.WIFI_STATE_ENABLING: | |
| return STATE_TURNING_ON; | |
| default: | |
| return STATE_UNKNOWN; | |
| } | |
| } | |
| } | |
| /** | |
| * Subclass of StateTracker to get/set Bluetooth state. | |
| */ | |
| private static final class BluetoothStateTracker extends StateTracker { | |
| public int getContainerId() { return R.id.btn_bluetooth; } | |
| public int getButtonId() { return R.id.img_bluetooth; } | |
| public int getIndicatorId() { return R.id.ind_bluetooth; } | |
| public int getButtonDescription() { return R.string.gadget_bluetooth; } | |
| public int getButtonImageId(boolean on) { | |
| return on ? R.drawable.ic_appwidget_settings_bluetooth_on_holo | |
| : R.drawable.ic_appwidget_settings_bluetooth_off_holo; | |
| } | |
| @Override | |
| public int getActualState(Context context) { | |
| if (sLocalBluetoothAdapter == null) { | |
| LocalBluetoothManager manager = Utils.getLocalBtManager(context); | |
| if (manager == null) { | |
| return STATE_UNKNOWN; // On emulator? | |
| } | |
| sLocalBluetoothAdapter = manager.getBluetoothAdapter(); | |
| if (sLocalBluetoothAdapter == null) { | |
| return STATE_UNKNOWN; // On emulator? | |
| } | |
| } | |
| return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState()); | |
| } | |
| @Override | |
| protected void requestStateChange(Context context, final boolean desiredState) { | |
| if (sLocalBluetoothAdapter == null) { | |
| Log.d(TAG, "No LocalBluetoothManager"); | |
| return; | |
| } | |
| // Actually request the Bluetooth change and persistent | |
| // settings write off the UI thread, as it can take a | |
| // user-noticeable amount of time, especially if there's | |
| // disk contention. | |
| new AsyncTask<Void, Void, Void>() { | |
| @Override | |
| protected Void doInBackground(Void... args) { | |
| sLocalBluetoothAdapter.setBluetoothEnabled(desiredState); | |
| return null; | |
| } | |
| }.execute(); | |
| } | |
| @Override | |
| public void onActualStateChange(Context context, Intent intent) { | |
| if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { | |
| return; | |
| } | |
| int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); | |
| setCurrentState(context, bluetoothStateToFiveState(bluetoothState)); | |
| } | |
| /** | |
| * Converts BluetoothAdapter's state values into our | |
| * Wifi/Bluetooth-common state values. | |
| */ | |
| private static int bluetoothStateToFiveState(int bluetoothState) { | |
| switch (bluetoothState) { | |
| case BluetoothAdapter.STATE_OFF: | |
| return STATE_DISABLED; | |
| case BluetoothAdapter.STATE_ON: | |
| return STATE_ENABLED; | |
| case BluetoothAdapter.STATE_TURNING_ON: | |
| return STATE_TURNING_ON; | |
| case BluetoothAdapter.STATE_TURNING_OFF: | |
| return STATE_TURNING_OFF; | |
| default: | |
| return STATE_UNKNOWN; | |
| } | |
| } | |
| } | |
| /** | |
| * Subclass of StateTracker for location state. | |
| */ | |
| private static final class LocationStateTracker extends StateTracker { | |
| private int mCurrentLocationMode = Settings.Secure.LOCATION_MODE_OFF; | |
| public int getContainerId() { return R.id.btn_location; } | |
| public int getButtonId() { return R.id.img_location; } | |
| public int getIndicatorId() { return R.id.ind_location; } | |
| public int getButtonDescription() { return R.string.gadget_location; } | |
| public int getButtonImageId(boolean on) { | |
| if (on) { | |
| switch (mCurrentLocationMode) { | |
| case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: | |
| case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: | |
| return R.drawable.ic_appwidget_settings_location_on_holo; | |
| default: | |
| return R.drawable.ic_appwidget_settings_location_saving_holo; | |
| } | |
| } | |
| return R.drawable.ic_appwidget_settings_location_off_holo; | |
| } | |
| @Override | |
| public int getActualState(Context context) { | |
| ContentResolver resolver = context.getContentResolver(); | |
| mCurrentLocationMode = Settings.Secure.getInt(resolver, | |
| Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); | |
| return (mCurrentLocationMode == Settings.Secure.LOCATION_MODE_OFF) | |
| ? STATE_DISABLED : STATE_ENABLED; | |
| } | |
| @Override | |
| public void onActualStateChange(Context context, Intent unused) { | |
| // Note: the broadcast location providers changed intent | |
| // doesn't include an extras bundles saying what the new value is. | |
| setCurrentState(context, getActualState(context)); | |
| } | |
| @Override | |
| public void requestStateChange(final Context context, final boolean desiredState) { | |
| final ContentResolver resolver = context.getContentResolver(); | |
| new AsyncTask<Void, Void, Boolean>() { | |
| @Override | |
| protected Boolean doInBackground(Void... args) { | |
| final UserManager um = | |
| (UserManager) context.getSystemService(Context.USER_SERVICE); | |
| if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) { | |
| int currentMode = Settings.Secure.getInt(resolver, | |
| Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); | |
| int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; | |
| switch (currentMode) { | |
| case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: | |
| mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING; | |
| break; | |
| case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: | |
| mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; | |
| break; | |
| case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: | |
| mode = Settings.Secure.LOCATION_MODE_OFF; | |
| break; | |
| case Settings.Secure.LOCATION_MODE_OFF: | |
| mode = Settings.Secure.LOCATION_MODE_PREVIOUS; | |
| break; | |
| } | |
| Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode); | |
| return mode != Settings.Secure.LOCATION_MODE_OFF; | |
| } | |
| return getActualState(context) == STATE_ENABLED; | |
| } | |
| @Override | |
| protected void onPostExecute(Boolean result) { | |
| setCurrentState( | |
| context, | |
| result ? STATE_ENABLED : STATE_DISABLED); | |
| updateWidget(context); | |
| } | |
| }.execute(); | |
| } | |
| } | |
| /** | |
| * Subclass of StateTracker for sync state. | |
| */ | |
| private static final class SyncStateTracker extends StateTracker { | |
| public int getContainerId() { return R.id.btn_sync; } | |
| public int getButtonId() { return R.id.img_sync; } | |
| public int getIndicatorId() { return R.id.ind_sync; } | |
| public int getButtonDescription() { return R.string.gadget_sync; } | |
| public int getButtonImageId(boolean on) { | |
| return on ? R.drawable.ic_appwidget_settings_sync_on_holo | |
| : R.drawable.ic_appwidget_settings_sync_off_holo; | |
| } | |
| @Override | |
| public int getActualState(Context context) { | |
| boolean on = ContentResolver.getMasterSyncAutomatically(); | |
| return on ? STATE_ENABLED : STATE_DISABLED; | |
| } | |
| @Override | |
| public void onActualStateChange(Context context, Intent unused) { | |
| setCurrentState(context, getActualState(context)); | |
| } | |
| @Override | |
| public void requestStateChange(final Context context, final boolean desiredState) { | |
| final ConnectivityManager connManager = | |
| (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); | |
| final boolean sync = ContentResolver.getMasterSyncAutomatically(); | |
| new AsyncTask<Void, Void, Boolean>() { | |
| @Override | |
| protected Boolean doInBackground(Void... args) { | |
| // Turning sync on. | |
| if (desiredState) { | |
| if (!sync) { | |
| ContentResolver.setMasterSyncAutomatically(true); | |
| } | |
| return true; | |
| } | |
| // Turning sync off | |
| if (sync) { | |
| ContentResolver.setMasterSyncAutomatically(false); | |
| } | |
| return false; | |
| } | |
| @Override | |
| protected void onPostExecute(Boolean result) { | |
| setCurrentState( | |
| context, | |
| result ? STATE_ENABLED : STATE_DISABLED); | |
| updateWidget(context); | |
| } | |
| }.execute(); | |
| } | |
| } | |
| private static void checkObserver(Context context) { | |
| if (sSettingsObserver == null) { | |
| sSettingsObserver = new SettingsObserver(new Handler(), | |
| context.getApplicationContext()); | |
| sSettingsObserver.startObserving(); | |
| } | |
| } | |
| @Override | |
| public void onUpdate(Context context, AppWidgetManager appWidgetManager, | |
| int[] appWidgetIds) { | |
| // Update each requested appWidgetId | |
| RemoteViews view = buildUpdate(context); | |
| for (int i = 0; i < appWidgetIds.length; i++) { | |
| appWidgetManager.updateAppWidget(appWidgetIds[i], view); | |
| } | |
| } | |
| @Override | |
| public void onEnabled(Context context) { | |
| checkObserver(context); | |
| } | |
| @Override | |
| public void onDisabled(Context context) { | |
| if (sSettingsObserver != null) { | |
| sSettingsObserver.stopObserving(); | |
| sSettingsObserver = null; | |
| } | |
| } | |
| /** | |
| * Load image for given widget and build {@link RemoteViews} for it. | |
| */ | |
| static RemoteViews buildUpdate(Context context) { | |
| RemoteViews views = new RemoteViews(context.getPackageName(), | |
| R.layout.widget); | |
| views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, | |
| BUTTON_WIFI)); | |
| views.setOnClickPendingIntent(R.id.btn_brightness, | |
| getLaunchPendingIntent(context, | |
| BUTTON_BRIGHTNESS)); | |
| views.setOnClickPendingIntent(R.id.btn_sync, | |
| getLaunchPendingIntent(context, | |
| BUTTON_SYNC)); | |
| views.setOnClickPendingIntent(R.id.btn_location, | |
| getLaunchPendingIntent(context, BUTTON_LOCATION)); | |
| views.setOnClickPendingIntent(R.id.btn_bluetooth, | |
| getLaunchPendingIntent(context, | |
| BUTTON_BLUETOOTH)); | |
| updateButtons(views, context); | |
| return views; | |
| } | |
| /** | |
| * Updates the widget when something changes, or when a button is pushed. | |
| * | |
| * @param context | |
| */ | |
| public static void updateWidget(Context context) { | |
| RemoteViews views = buildUpdate(context); | |
| // Update specific list of appWidgetIds if given, otherwise default to all | |
| final AppWidgetManager gm = AppWidgetManager.getInstance(context); | |
| gm.updateAppWidget(THIS_APPWIDGET, views); | |
| checkObserver(context); | |
| } | |
| /** | |
| * Updates the buttons based on the underlying states of wifi, etc. | |
| * | |
| * @param views The RemoteViews to update. | |
| * @param context | |
| */ | |
| private static void updateButtons(RemoteViews views, Context context) { | |
| sWifiState.setImageViewResources(context, views); | |
| sBluetoothState.setImageViewResources(context, views); | |
| sLocationState.setImageViewResources(context, views); | |
| sSyncState.setImageViewResources(context, views); | |
| if (getBrightnessMode(context)) { | |
| views.setContentDescription(R.id.btn_brightness, | |
| context.getString(R.string.gadget_brightness_template, | |
| context.getString(R.string.gadget_brightness_state_auto))); | |
| views.setImageViewResource(R.id.img_brightness, | |
| R.drawable.ic_appwidget_settings_brightness_auto_holo); | |
| views.setImageViewResource(R.id.ind_brightness, | |
| R.drawable.appwidget_settings_ind_on_r_holo); | |
| } else { | |
| final int brightness = getBrightness(context); | |
| final PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); | |
| // Set the icon | |
| final int full = (int)(pm.getMaximumScreenBrightnessSetting() | |
| * FULL_BRIGHTNESS_THRESHOLD); | |
| final int half = (int)(pm.getMaximumScreenBrightnessSetting() | |
| * HALF_BRIGHTNESS_THRESHOLD); | |
| if (brightness > full) { | |
| views.setContentDescription(R.id.btn_brightness, | |
| context.getString(R.string.gadget_brightness_template, | |
| context.getString(R.string.gadget_brightness_state_full))); | |
| views.setImageViewResource(R.id.img_brightness, | |
| R.drawable.ic_appwidget_settings_brightness_full_holo); | |
| } else if (brightness > half) { | |
| views.setContentDescription(R.id.btn_brightness, | |
| context.getString(R.string.gadget_brightness_template, | |
| context.getString(R.string.gadget_brightness_state_half))); | |
| views.setImageViewResource(R.id.img_brightness, | |
| R.drawable.ic_appwidget_settings_brightness_half_holo); | |
| } else { | |
| views.setContentDescription(R.id.btn_brightness, | |
| context.getString(R.string.gadget_brightness_template, | |
| context.getString(R.string.gadget_brightness_state_off))); | |
| views.setImageViewResource(R.id.img_brightness, | |
| R.drawable.ic_appwidget_settings_brightness_off_holo); | |
| } | |
| // Set the ON state | |
| if (brightness > half) { | |
| views.setImageViewResource(R.id.ind_brightness, | |
| R.drawable.appwidget_settings_ind_on_r_holo); | |
| } else { | |
| views.setImageViewResource(R.id.ind_brightness, | |
| R.drawable.appwidget_settings_ind_off_r_holo); | |
| } | |
| } | |
| } | |
| /** | |
| * Creates PendingIntent to notify the widget of a button click. | |
| * | |
| * @param context | |
| * @return | |
| */ | |
| private static PendingIntent getLaunchPendingIntent(Context context, | |
| int buttonId) { | |
| Intent launchIntent = new Intent(); | |
| launchIntent.setClass(context, SettingsAppWidgetProvider.class); | |
| launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE); | |
| launchIntent.setData(Uri.parse("custom:" + buttonId)); | |
| PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* no requestCode */, | |
| launchIntent, 0 /* no flags */); | |
| return pi; | |
| } | |
| /** | |
| * Receives and processes a button pressed intent or state change. | |
| * | |
| * @param context | |
| * @param intent Indicates the pressed button. | |
| */ | |
| @Override | |
| public void onReceive(Context context, Intent intent) { | |
| super.onReceive(context, intent); | |
| String action = intent.getAction(); | |
| if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { | |
| sWifiState.onActualStateChange(context, intent); | |
| } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { | |
| sBluetoothState.onActualStateChange(context, intent); | |
| } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) { | |
| sLocationState.onActualStateChange(context, intent); | |
| } else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) { | |
| sSyncState.onActualStateChange(context, intent); | |
| } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { | |
| Uri data = intent.getData(); | |
| int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); | |
| if (buttonId == BUTTON_WIFI) { | |
| sWifiState.toggleState(context); | |
| } else if (buttonId == BUTTON_BRIGHTNESS) { | |
| toggleBrightness(context); | |
| } else if (buttonId == BUTTON_SYNC) { | |
| sSyncState.toggleState(context); | |
| } else if (buttonId == BUTTON_LOCATION) { | |
| sLocationState.toggleState(context); | |
| } else if (buttonId == BUTTON_BLUETOOTH) { | |
| sBluetoothState.toggleState(context); | |
| } | |
| } else { | |
| // Don't fall-through to updating the widget. The Intent | |
| // was something unrelated or that our super class took | |
| // care of. | |
| return; | |
| } | |
| // State changes fall through | |
| updateWidget(context); | |
| } | |
| /** | |
| * Gets brightness level. | |
| * | |
| * @param context | |
| * @return brightness level between 0 and 255. | |
| */ | |
| private static int getBrightness(Context context) { | |
| try { | |
| int brightness = Settings.System.getInt(context.getContentResolver(), | |
| Settings.System.SCREEN_BRIGHTNESS); | |
| return brightness; | |
| } catch (Exception e) { | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Gets state of brightness mode. | |
| * | |
| * @param context | |
| * @return true if auto brightness is on. | |
| */ | |
| private static boolean getBrightnessMode(Context context) { | |
| try { | |
| int brightnessMode = Settings.System.getInt(context.getContentResolver(), | |
| Settings.System.SCREEN_BRIGHTNESS_MODE); | |
| return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; | |
| } catch (Exception e) { | |
| Log.d(TAG, "getBrightnessMode: " + e); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Increases or decreases the brightness. | |
| * | |
| * @param context | |
| */ | |
| private void toggleBrightness(Context context) { | |
| try { | |
| IPowerManager power = IPowerManager.Stub.asInterface( | |
| ServiceManager.getService("power")); | |
| if (power != null) { | |
| PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); | |
| ContentResolver cr = context.getContentResolver(); | |
| int brightness = Settings.System.getInt(cr, | |
| Settings.System.SCREEN_BRIGHTNESS); | |
| int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; | |
| //Only get brightness setting if available | |
| if (context.getResources().getBoolean( | |
| com.android.internal.R.bool.config_automatic_brightness_available)) { | |
| brightnessMode = Settings.System.getInt(cr, | |
| Settings.System.SCREEN_BRIGHTNESS_MODE); | |
| } | |
| // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM | |
| // Technically, not a toggle... | |
| if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { | |
| brightness = pm.getMinimumScreenBrightnessSetting(); | |
| brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; | |
| } else if (brightness < pm.getDefaultScreenBrightnessSetting()) { | |
| brightness = pm.getDefaultScreenBrightnessSetting(); | |
| } else if (brightness < pm.getMaximumScreenBrightnessSetting()) { | |
| brightness = pm.getMaximumScreenBrightnessSetting(); | |
| } else { | |
| brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; | |
| brightness = pm.getMinimumScreenBrightnessSetting(); | |
| } | |
| if (context.getResources().getBoolean( | |
| com.android.internal.R.bool.config_automatic_brightness_available)) { | |
| // Set screen brightness mode (automatic or manual) | |
| Settings.System.putInt(context.getContentResolver(), | |
| Settings.System.SCREEN_BRIGHTNESS_MODE, | |
| brightnessMode); | |
| } else { | |
| // Make sure we set the brightness if automatic mode isn't available | |
| brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; | |
| } | |
| if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) { | |
| power.setTemporaryScreenBrightnessSettingOverride(brightness); | |
| Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); | |
| } | |
| } | |
| } catch (RemoteException e) { | |
| Log.d(TAG, "toggleBrightness: " + e); | |
| } catch (Settings.SettingNotFoundException e) { | |
| Log.d(TAG, "toggleBrightness: " + e); | |
| } | |
| } | |
| /** Observer to watch for changes to the BRIGHTNESS setting */ | |
| private static class SettingsObserver extends ContentObserver { | |
| private Context mContext; | |
| SettingsObserver(Handler handler, Context context) { | |
| super(handler); | |
| mContext = context; | |
| } | |
| void startObserving() { | |
| ContentResolver resolver = mContext.getContentResolver(); | |
| // Listen to brightness and brightness mode | |
| resolver.registerContentObserver(Settings.System | |
| .getUriFor(Settings.System.SCREEN_BRIGHTNESS), false, this); | |
| resolver.registerContentObserver(Settings.System | |
| .getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), false, this); | |
| resolver.registerContentObserver(Settings.System | |
| .getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ), false, this); | |
| } | |
| void stopObserving() { | |
| mContext.getContentResolver().unregisterContentObserver(this); | |
| } | |
| @Override | |
| public void onChange(boolean selfChange) { | |
| updateWidget(mContext); | |
| } | |
| } | |
| } |