diff --git a/api/current.xml b/api/current.xml index 3e8b9ac3c531a..7f0fb94165ddb 100644 --- a/api/current.xml +++ b/api/current.xml @@ -25564,6 +25564,17 @@ visibility="public" > + + - + + + + + + + + - - - + + - - - - + +These facilities are built on top of the underlying + * {@link android.content.Intent#ACTION_DOCK_EVENT} broadcasts that are sent when the user + * physical places the device into and out of a dock. When that happens, + * the UiModeManager switches the system {@link android.content.res.Configuration} + * to the appropriate UI mode, sends broadcasts about the mode switch, and + * starts the corresponding mode activity if appropriate. See the + * broadcasts {@link #ACTION_ENTER_CAR_MODE} and + * {@link #ACTION_ENTER_DESK_MODE} for more information. + * + *

In addition, the user may manually switch the system to car mode without + * physically being in a dock. While in car mode -- whether by manual action + * from the user or being physically placed in a dock -- a notification is + * displayed allowing the user to exit dock mode. Thus the dock mode + * represented here may be different than the current state of the underlying + * dock event broadcast. * *

You do not instantiate this class directly; instead, retrieve it through * {@link android.content.Context#getSystemService @@ -17,19 +35,72 @@ import android.util.Log; public class UiModeManager { private static final String TAG = "UiModeManager"; - public static final int MODE_NOTNIGHT = 1; - public static final int MODE_NIGHT = 2; - public static final int MODE_AUTO = 3; + /** + * Broadcast sent when the device's UI has switched to car mode, either + * by being placed in a car dock or explicit action of the user. After + * sending the broadcast, the system will start the intent + * {@link android.content.Intent#ACTION_MAIN} with category + * {@link android.content.Intent#CATEGORY_CAR_DOCK} + * to display the car UI, which typically what an application would + * implement to provide their own interface. However, applications can + * also monitor this Intent in order to be informed of mode changes or + * prevent the normal car UI from being displayed by setting the result + * of the broadcast to {@link Activity#RESULT_CANCELED}. + */ + public static String ACTION_ENTER_CAR_MODE = "android.app.action.ENTER_CAR_MODE"; + + /** + * Broadcast sent when the device's UI has switch away from car mode back + * to normal mode. Typically used by a car mode app, to dismiss itself + * when the user exits car mode. + */ + public static String ACTION_EXIT_CAR_MODE = "android.app.action.EXIT_CAR_MODE"; + + /** + * Broadcast sent when the device's UI has switched to desk mode, + * by being placed in a desk dock. After + * sending the broadcast, the system will start the intent + * {@link android.content.Intent#ACTION_MAIN} with category + * {@link android.content.Intent#CATEGORY_DESK_DOCK} + * to display the desk UI, which typically what an application would + * implement to provide their own interface. However, applications can + * also monitor this Intent in order to be informed of mode changes or + * prevent the normal desk UI from being displayed by setting the result + * of the broadcast to {@link Activity#RESULT_CANCELED}. + */ + public static String ACTION_ENTER_DESK_MODE = "android.app.action.ENTER_DESK_MODE"; + + /** + * Broadcast sent when the device's UI has switch away from car mode back + * to normal mode. Typically used by a car mode app, to dismiss itself + * when the user exits car mode. + */ + public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE"; + + /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: + * automatically switch night mode on and off based on the time. + */ + public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4; + + /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: + * never run in night mode. + */ + public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4; + + /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}: + * always run in night mode. + */ + public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4; private IUiModeManager mService; /*package*/ UiModeManager() { mService = IUiModeManager.Stub.asInterface( - ServiceManager.getService("uimode")); + ServiceManager.getService(Context.UI_MODE_SERVICE)); } /** - * Disables the car mode. + * Turn off special mode if currently in car mode. */ public void disableCarMode() { if (mService != null) { @@ -41,18 +112,36 @@ public class UiModeManager { } } + /** + * Return the current running mode type. May be one of + * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, + * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or + * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, + */ + public int getCurrentModeType() { + if (mService != null) { + try { + return mService.getCurrentModeType(); + } catch (RemoteException e) { + Log.e(TAG, "getCurrentModeType: RemoteException", e); + } + } + return Configuration.UI_MODE_TYPE_NORMAL; + } + /** * Sets the night mode. Changes to the night mode are only effective when - * the car mode is enabled on a device. + * the car or desk mode is enabled on a device. * *

The mode can be one of: *

*/ public void setNightMode(int mode) { if (mService != null) { @@ -67,8 +156,8 @@ public class UiModeManager { /** * Returns the currently configured night mode. * - * @return {@link #MODE_NOTNIGHT}, {@link #MODE_NIGHT} or {@link #MODE_AUTO} - * When an error occurred -1 is returned. + * @return {@link #MODE_NIGHT_NO}, {@link #MODE_NIGHT_YES}, or + * {@link #MODE_NIGHT_AUTO}. When an error occurred -1 is returned. */ public int getNightMode() { if (mService != null) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e63851f0a6036..fd5591d674be0 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1838,21 +1838,17 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.REBOOT"; /** - * Broadcast Action: A sticky broadcast indicating the phone was docked - * or undocked. + * Broadcast Action: A sticky broadcast for changes in the physical + * docking state of the device. * *

The intent will have the following extra values: *

    *
  • {@link #EXTRA_DOCK_STATE} - the current dock - * state, which depends on the state of the car mode.
  • - *
  • {@link #EXTRA_PHYSICAL_DOCK_STATE} - the physical dock - * state.
  • - *
  • {@link #EXTRA_CAR_MODE_ENABLED} - a boolean indicating the - * state of the car mode.
  • + * state, indicating which dock the device is physically in. *
- *

This is intended for monitoring the current dock state. - * To launch an activity from a dock state change, use {@link #CATEGORY_CAR_DOCK} - * or {@link #CATEGORY_DESK_DOCK} instead. + *

This is intended for monitoring the current physical dock state. + * See {@link android.app.UiModeManager} for the normal API dealing with + * dock mode changes. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DOCK_EVENT = @@ -2014,15 +2010,15 @@ public class Intent implements Parcelable, Cloneable { "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"; /** * An activity to run when device is inserted into a car dock. - * Used with {@link #ACTION_MAIN} to launch an activity. - * To monitor dock state, use {@link #ACTION_DOCK_EVENT} instead. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK"; /** * An activity to run when device is inserted into a car dock. - * Used with {@link #ACTION_MAIN} to launch an activity. - * To monitor dock state, use {@link #ACTION_DOCK_EVENT} instead. + * Used with {@link #ACTION_MAIN} to launch an activity. For more + * information, see {@link android.app.UiModeManager}. */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK"; @@ -2190,22 +2186,6 @@ public class Intent implements Parcelable, Cloneable { */ public static final int EXTRA_DOCK_STATE_CAR = 2; - /** - * Used as an int extra field in {@link android.content.Intent#ACTION_DOCK_EVENT} - * intents to request the physical dock state. Possible values are - * {@link android.content.Intent#EXTRA_DOCK_STATE_UNDOCKED}, - * {@link android.content.Intent#EXTRA_DOCK_STATE_DESK}, or - * {@link android.content.Intent#EXTRA_DOCK_STATE_CAR}. - */ - public static final String EXTRA_PHYSICAL_DOCK_STATE = - "android.intent.extra.PHYSICAL_DOCK_STATE"; - - /** - * Used as an boolean extra field in {@link android.content.Intent#ACTION_DOCK_EVENT} - * intents to indicate that the car mode is enabled or not. - */ - public static final String EXTRA_CAR_MODE_ENABLED = "android.intent.extra.CAR_MODE_ENABLED"; - /** * Boolean that can be supplied as meta-data with a dock activity, to * indicate that the dock should take over the home key when it is active. diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index a737283cf4ecf..61e3004c327e4 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -165,7 +165,8 @@ public final class Configuration implements Parcelable, ComparableThe {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the - * device. They may be one of - * {@link #UI_MODE_TYPE_NORMAL} or {@link #UI_MODE_TYPE_CAR}. + * device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED}, + * {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK}, + * or {@link #UI_MODE_TYPE_CAR}. * *

The {@link #UI_MODE_NIGHT_MASK} defines whether the screen - * is in a special mode. They may be one of + * is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED}, * {@link #UI_MODE_NIGHT_NO} or {@link #UI_MODE_NIGHT_YES}. */ public int uiMode; @@ -272,7 +274,7 @@ public final class Configuration implements Parcelable, Comparable + + + + + + diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index cbcef4e1af774..0e796dc561f1e 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -946,7 +946,8 @@ struct ResTable_config MASK_UI_MODE_TYPE = 0x0f, UI_MODE_TYPE_ANY = 0x00, UI_MODE_TYPE_NORMAL = 0x01, - UI_MODE_TYPE_CAR = 0x02, + UI_MODE_TYPE_DESK = 0x02, + UI_MODE_TYPE_CAR = 0x03, // uiMode bits for the night switch. MASK_UI_MODE_NIGHT = 0x30, diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index 28236f00b4ab9..bee8872ede0af 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -16,53 +16,24 @@ package com.android.server; -import android.app.Activity; -import android.app.ActivityManagerNative; -import android.app.AlarmManager; -import android.app.IActivityManager; -import android.app.IUiModeManager; -import android.app.KeyguardManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.StatusBarManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Binder; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.UEventObserver; import android.provider.Settings; import android.server.BluetoothService; -import android.text.format.DateUtils; -import android.text.format.Time; import android.util.Log; import android.util.Slog; -import com.android.internal.R; -import com.android.internal.app.DisableCarModeActivity; -import com.android.internal.widget.LockPatternUtils; - import java.io.FileNotFoundException; import java.io.FileReader; @@ -76,187 +47,22 @@ class DockObserver extends UEventObserver { private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; - private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL"; - private static final int MSG_DOCK_STATE = 0; - private static final int MSG_UPDATE_TWILIGHT = 1; - private static final int MSG_ENABLE_LOCATION_UPDATES = 2; - - public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4; - public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4; - public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4; - - private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; - private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS; - private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; - - private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE"; private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; - private int mNightMode = MODE_NIGHT_NO; - private boolean mCarModeEnabled = false; - private boolean mSystemReady; private final Context mContext; private PowerManagerService mPowerManager; - private NotificationManager mNotificationManager; - - private KeyguardManager.KeyguardLock mKeyguardLock; - private boolean mKeyguardDisabled; - private LockPatternUtils mLockPatternUtils; - - private AlarmManager mAlarmManager; - - private LocationManager mLocationManager; - private Location mLocation; - private StatusBarManager mStatusBarManager; - - // The broadcast receiver which receives the result of the ordered broadcast sent when - // the dock state changes. The original ordered broadcast is sent with an initial result - // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., - // to RESULT_CANCELED, then the intent to start a dock app will not be sent. - private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (getResultCode() != Activity.RESULT_OK) { - return; - } - - // Launch a dock activity - String category; - if (mCarModeEnabled) { - // Only launch car home when car mode is enabled. - category = Intent.CATEGORY_CAR_DOCK; - } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { - category = Intent.CATEGORY_DESK_DOCK; - } else { - category = null; - } - if (category != null) { - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(category); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - Slog.w(TAG, e.getCause()); - } - } - } - }; - - private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) { - mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); - } - } - }; - - // A LocationListener to initialize the network location provider. The location updates - // are handled through the passive location provider. - private final LocationListener mEmptyLocationListener = new LocationListener() { - public void onLocationChanged(Location location) { - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - - private final LocationListener mLocationListener = new LocationListener() { - - public void onLocationChanged(Location location) { - final boolean hasMoved = hasMoved(location); - if (hasMoved || hasBetterAccuracy(location)) { - synchronized (this) { - mLocation = location; - } - if (hasMoved && mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) { - mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); - } - } - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - private boolean hasBetterAccuracy(Location location) { - if (location == null) { - return false; - } - if (mLocation == null) { - return true; - } - return location.getAccuracy() < mLocation.getAccuracy(); - } - - /* - * The user has moved if the accuracy circles of the two locations - * don't overlap. - */ - private boolean hasMoved(Location location) { - if (location == null) { - return false; - } - if (mLocation == null) { - return true; - } - - /* if new location is older than the current one, the devices hasn't - * moved. - */ - if (location.getTime() < mLocation.getTime()) { - return false; - } - - /* Get the distance between the two points */ - float distance = mLocation.distanceTo(location); - - /* Get the total accuracy radius for both locations */ - float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy(); - - /* If the distance is greater than the combined accuracy of the two - * points then they can't overlap and hence the user has moved. - */ - return distance >= totalAccuracy; - } - }; public DockObserver(Context context, PowerManagerService pm) { mContext = context; mPowerManager = pm; - mLockPatternUtils = new LockPatternUtils(context); init(); // set initial status - ServiceManager.addService("uimode", mBinder); - - mAlarmManager = - (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); - mLocationManager = - (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); - mContext.registerReceiver(mTwilightUpdateReceiver, - new IntentFilter(ACTION_UPDATE_NIGHT_MODE)); - startObserving(DOCK_UEVENT_MATCH); } @@ -272,14 +78,6 @@ class DockObserver extends UEventObserver { if (newState != mDockState) { mPreviousDockState = mDockState; mDockState = newState; - boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; - if (mCarModeEnabled != carModeEnabled) { - try { - setCarMode(carModeEnabled); - } catch (RemoteException e1) { - Slog.w(TAG, "Unable to change car mode.", e1); - } - } if (mSystemReady) { // Don't force screen on when undocking from the desk dock. // The change in power state will do this anyway. @@ -315,24 +113,11 @@ class DockObserver extends UEventObserver { void systemReady() { synchronized (this) { - KeyguardManager keyguardManager = - (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE); - mKeyguardLock = keyguardManager.newKeyguardLock(TAG); - - final boolean enableCarMode = mDockState == Intent.EXTRA_DOCK_STATE_CAR; - if (enableCarMode) { - try { - setCarMode(enableCarMode); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to change car mode.", e); - } - } // don't bother broadcasting undocked here if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { update(); } mSystemReady = true; - mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); } } @@ -341,10 +126,6 @@ class DockObserver extends UEventObserver { } private final Handler mHandler = new Handler() { - - boolean mPassiveListenerEnabled; - boolean mNetworkListenerEnabled; - @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -362,17 +143,7 @@ class DockObserver extends UEventObserver { // Pack up the values and broadcast them to everyone Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) { - // Pretend to be in DOCK_STATE_CAR. - intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR); - } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) { - // Pretend to be in DOCK_STATE_UNDOCKED. - intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - } else { - intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); - } - intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState); - intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled); + intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); // Check if this is Bluetooth Dock String address = BluetoothService.readDockBluetoothAddress(); @@ -415,296 +186,10 @@ class DockObserver extends UEventObserver { } } - // Send the ordered broadcast; the result receiver will receive after all - // broadcasts have been sent. If any broadcast receiver changes the result - // code from the initial value of RESULT_OK, then the result receiver will - // not launch the corresponding dock application. This gives apps a chance - // to override the behavior and stay in their app even when the device is - // placed into a dock. - mContext.sendStickyOrderedBroadcast( - intent, mResultReceiver, null, Activity.RESULT_OK, null, null); - - } - break; - case MSG_UPDATE_TWILIGHT: - synchronized (this) { - if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) { - try { - DockObserver.this.updateTwilight(); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to change night mode.", e); - } - } - } - break; - case MSG_ENABLE_LOCATION_UPDATES: - // enable passive provider to receive updates from location fixes (gps - // and network). - boolean passiveLocationEnabled; - try { - passiveLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if passive location provider - // does not exist or is not yet installed. - passiveLocationEnabled = false; - } - if (!mPassiveListenerEnabled && passiveLocationEnabled) { - mPassiveListenerEnabled = true; - mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, - 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); - } - // enable network provider to receive at least location updates for a given - // distance. - boolean networkLocationEnabled; - try { - networkLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if network location provider - // does not exist or is not yet installed. - networkLocationEnabled = false; - } - if (!mNetworkListenerEnabled && networkLocationEnabled) { - mNetworkListenerEnabled = true; - mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - LOCATION_UPDATE_MS, 0, mEmptyLocationListener); - - retrieveLocation(); - if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) { - try { - DockObserver.this.updateTwilight(); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to change night mode.", e); - } - } - } - if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { - long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL); - interval *= 1.5; - if (interval == 0) { - interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; - } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { - interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; - } - Bundle bundle = new Bundle(); - bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval); - Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES); - newMsg.setData(bundle); - mHandler.sendMessageDelayed(newMsg, interval); + mContext.sendStickyBroadcast(intent); } break; } } - - private void retrieveLocation() { - final Location gpsLocation = - mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - Location location; - Criteria criteria = new Criteria(); - criteria.setSpeedRequired(false); - criteria.setAltitudeRequired(false); - criteria.setBearingRequired(false); - criteria.setAccuracy(Criteria.ACCURACY_FINE); - final String bestProvider = mLocationManager.getBestProvider(criteria, true); - location = mLocationManager.getLastKnownLocation(bestProvider); - // In the case there is no location available (e.g. GPS fix or network location - // is not available yet), the longitude of the location is estimated using the timezone, - // latitude and accuracy are set to get a good average. - if (location == null) { - Time currentTime = new Time(); - currentTime.set(System.currentTimeMillis()); - double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * - (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); - location = new Location("fake"); - location.setLongitude(lngOffset); - location.setLatitude(0); - location.setAccuracy(417000.0f); - location.setTime(System.currentTimeMillis()); - } - synchronized (this) { - mLocation = location; - } - } - }; - - private void adjustStatusBarCarMode() { - if (mStatusBarManager == null) { - mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); - } - - // Fear not: StatusBarService manages a list of requests to disable - // features of the status bar; these are ORed together to form the - // active disabled list. So if (for example) the device is locked and - // the status bar should be totally disabled, the calls below will - // have no effect until the device is unlocked. - if (mStatusBarManager != null) { - long ident = Binder.clearCallingIdentity(); - mStatusBarManager.disable(mCarModeEnabled - ? StatusBarManager.DISABLE_NOTIFICATION_TICKER - : StatusBarManager.DISABLE_NONE); - Binder.restoreCallingIdentity(ident); - } - - if (mNotificationManager == null) { - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - if (mNotificationManager != null) { - long ident = Binder.clearCallingIdentity(); - if (mCarModeEnabled) { - Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class); - - Notification n = new Notification(); - n.icon = R.drawable.stat_notify_car_mode; - n.defaults = Notification.DEFAULT_LIGHTS; - n.flags = Notification.FLAG_ONGOING_EVENT; - n.when = 0; - n.setLatestEventInfo( - mContext, - mContext.getString(R.string.car_mode_disable_notification_title), - mContext.getString(R.string.car_mode_disable_notification_message), - PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0)); - mNotificationManager.notify(0, n); - } else { - mNotificationManager.cancel(0); - } - Binder.restoreCallingIdentity(ident); - } - } - - private void setCarMode(boolean enabled) throws RemoteException { - mCarModeEnabled = enabled; - if (enabled) { - if (mNightMode == MODE_NIGHT_AUTO) { - updateTwilight(); - } else { - setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4); - } - } else { - // Disabling the car mode clears the night mode. - setMode(Configuration.UI_MODE_TYPE_NORMAL, - Configuration.UI_MODE_NIGHT_UNDEFINED); - } - adjustStatusBarCarMode(); - } - - private void setMode(int modeType, int modeNight) throws RemoteException { - long ident = Binder.clearCallingIdentity(); - final IActivityManager am = ActivityManagerNative.getDefault(); - Configuration config = am.getConfiguration(); - if (config.uiMode != (modeType | modeNight)) { - config.uiMode = modeType | modeNight; - am.updateConfiguration(config); - } - Binder.restoreCallingIdentity(ident); - } - - private void setNightMode(int mode) throws RemoteException { - if (mNightMode != mode) { - mNightMode = mode; - switch (mode) { - case MODE_NIGHT_NO: - case MODE_NIGHT_YES: - setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4); - break; - case MODE_NIGHT_AUTO: - long ident = Binder.clearCallingIdentity(); - updateTwilight(); - Binder.restoreCallingIdentity(ident); - break; - default: - setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4); - break; - } - } - } - - private void updateTwilight() throws RemoteException { - synchronized (this) { - if (mLocation == null) { - return; - } - final long currentTime = System.currentTimeMillis(); - int nightMode; - // calculate current twilight - TwilightCalculator tw = new TwilightCalculator(); - tw.calculateTwilight(currentTime, - mLocation.getLatitude(), mLocation.getLongitude()); - if (tw.mState == TwilightCalculator.DAY) { - nightMode = MODE_NIGHT_NO; - } else { - nightMode = MODE_NIGHT_YES; - } - - // schedule next update - long nextUpdate = 0; - if (tw.mSunrise == -1 || tw.mSunset == -1) { - // In the case the day or night never ends the update is scheduled 12 hours later. - nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS; - } else { - final int mLastTwilightState = tw.mState; - // add some extra time to be on the save side. - nextUpdate += DateUtils.MINUTE_IN_MILLIS; - if (currentTime > tw.mSunset) { - // next update should be on the following day - tw.calculateTwilight(currentTime - + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(), - mLocation.getLongitude()); - } - - if (mLastTwilightState == TwilightCalculator.NIGHT) { - nextUpdate += tw.mSunrise; - } else { - nextUpdate += tw.mSunset; - } - } - - Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE); - PendingIntent pendingIntent = - PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); - mAlarmManager.cancel(pendingIntent); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); - - // Make sure that we really set the new mode only if we're in car mode and - // automatic switching is enables. - if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) { - setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4); - } - } - } - - /** - * Wrapper class implementing the IUiModeManager interface. - */ - private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() { - - public void disableCarMode() throws RemoteException { - if (mCarModeEnabled) { - setCarMode(false); - update(); - } - } - - public void enableCarMode() throws RemoteException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ENABLE_CAR_MODE, - "Need ENABLE_CAR_MODE permission"); - if (!mCarModeEnabled) { - setCarMode(true); - update(); - } - } - - public void setNightMode(int mode) throws RemoteException { - if (mCarModeEnabled) { - DockObserver.this.setNightMode(mode); - } - } - - public int getNightMode() throws RemoteException { - return mNightMode; - } }; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index deee7f3762211..b023958101da2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -97,6 +97,7 @@ class ServerThread extends Thread { BluetoothA2dpService bluetoothA2dp = null; HeadsetObserver headset = null; DockObserver dock = null; + UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; // Critical services... @@ -362,6 +363,14 @@ class ServerThread extends Thread { Slog.e(TAG, "Failure starting DockObserver", e); } + try { + Slog.i(TAG, "UI Mode Manager Service"); + // Listen for dock station changes + uiMode = new UiModeManagerService(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting UiModeManagerService", e); + } + try { Slog.i(TAG, "Backup Service"); ServiceManager.addService(Context.BACKUP_SERVICE, @@ -441,6 +450,7 @@ class ServerThread extends Thread { final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; + final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; final WallpaperManagerService wallpaperF = wallpaper; final InputMethodManagerService immF = imm; @@ -460,6 +470,7 @@ class ServerThread extends Thread { if (batteryF != null) batteryF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); + if (uiModeF != null) uiModeF.systemReady(); if (recognitionF != null) recognitionF.systemReady(); Watchdog.getInstance().start(); diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java new file mode 100644 index 0000000000000..e225edb7c2801 --- /dev/null +++ b/services/java/com/android/server/UiModeManagerService.java @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2008 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.server; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.AlarmManager; +import android.app.IActivityManager; +import android.app.IUiModeManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.app.UiModeManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.R; +import com.android.internal.app.DisableCarModeActivity; + +class UiModeManagerService extends IUiModeManager.Stub { + private static final String TAG = UiModeManager.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL"; + + private static final int MSG_UPDATE_TWILIGHT = 0; + private static final int MSG_ENABLE_LOCATION_UPDATES = 1; + + private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; + private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS; + private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; + + private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE"; + + private final Context mContext; + + final Object mLock = new Object(); + + private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + private int mNightMode = UiModeManager.MODE_NIGHT_NO; + private boolean mCarModeEnabled = false; + + private boolean mComputedNightMode; + private int mCurUiMode = 0; + + private Configuration mConfiguration = new Configuration(); + + private boolean mSystemReady; + + private NotificationManager mNotificationManager; + + private AlarmManager mAlarmManager; + + private LocationManager mLocationManager; + private Location mLocation; + private StatusBarManager mStatusBarManager; + + // The broadcast receiver which receives the result of the ordered broadcast sent when + // the dock state changes. The original ordered broadcast is sent with an initial result + // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., + // to RESULT_CANCELED, then the intent to start a dock app will not be sent. + private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (getResultCode() != Activity.RESULT_OK) { + return; + } + + // Launch a dock activity + String category; + if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { + // Only launch car home when car mode is enabled. + category = Intent.CATEGORY_CAR_DOCK; + } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) { + category = Intent.CATEGORY_DESK_DOCK; + } else { + category = null; + } + if (category != null) { + intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e) { + Slog.w(TAG, e.getCause()); + } + } + } + }; + + private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); + } + } + }; + + private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + updateDockState(state); + } + }; + + // A LocationListener to initialize the network location provider. The location updates + // are handled through the passive location provider. + private final LocationListener mEmptyLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + private final LocationListener mLocationListener = new LocationListener() { + + public void onLocationChanged(Location location) { + final boolean hasMoved = hasMoved(location); + final boolean hasBetterAccuracy = mLocation == null + || location.getAccuracy() < mLocation.getAccuracy(); + if (hasMoved || hasBetterAccuracy) { + synchronized (mLock) { + mLocation = location; + if (hasMoved && isDoingNightMode() + && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); + } + } + } + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + /* + * The user has moved if the accuracy circles of the two locations + * don't overlap. + */ + private boolean hasMoved(Location location) { + if (location == null) { + return false; + } + if (mLocation == null) { + return true; + } + + /* if new location is older than the current one, the devices hasn't + * moved. + */ + if (location.getTime() < mLocation.getTime()) { + return false; + } + + /* Get the distance between the two points */ + float distance = mLocation.distanceTo(location); + + /* Get the total accuracy radius for both locations */ + float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy(); + + /* If the distance is greater than the combined accuracy of the two + * points then they can't overlap and hence the user has moved. + */ + return distance >= totalAccuracy; + } + }; + + public UiModeManagerService(Context context) { + mContext = context; + + ServiceManager.addService(Context.UI_MODE_SERVICE, this); + + mAlarmManager = + (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mLocationManager = + (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); + mContext.registerReceiver(mTwilightUpdateReceiver, + new IntentFilter(ACTION_UPDATE_NIGHT_MODE)); + mContext.registerReceiver(mDockModeReceiver, + new IntentFilter(Intent.ACTION_DOCK_EVENT)); + + mConfiguration.setToDefaults(); + } + + public void disableCarMode() { + synchronized (mLock) { + setCarModeLocked(false); + } + } + + public void enableCarMode() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ENABLE_CAR_MODE, + "Need ENABLE_CAR_MODE permission"); + synchronized (mLock) { + setCarModeLocked(true); + } + } + + public int getCurrentModeType() { + synchronized (mLock) { + return mCurUiMode & Configuration.UI_MODE_TYPE_MASK; + } + } + + public void setNightMode(int mode) throws RemoteException { + synchronized (mLock) { + switch (mode) { + case UiModeManager.MODE_NIGHT_NO: + case UiModeManager.MODE_NIGHT_YES: + case UiModeManager.MODE_NIGHT_AUTO: + break; + default: + throw new IllegalArgumentException("Unknown mode: " + mode); + } + if (!isDoingNightMode()) { + return; + } + + if (mNightMode != mode) { + mNightMode = mode; + updateLocked(); + } + } + } + + public int getNightMode() throws RemoteException { + return mNightMode; + } + + void systemReady() { + synchronized (mLock) { + mSystemReady = true; + mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; + updateLocked(); + mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); + } + } + + boolean isDoingNightMode() { + return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED; + } + + void setCarModeLocked(boolean enabled) { + if (mCarModeEnabled != enabled) { + mCarModeEnabled = enabled; + updateLocked(); + } + } + + void updateDockState(int newState) { + synchronized (mLock) { + if (newState != mDockState) { + mDockState = newState; + mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; + if (mSystemReady) { + updateLocked(); + } + } + } + } + + final void updateLocked() { + long ident = Binder.clearCallingIdentity(); + + try { + int uiMode = 0; + if (mCarModeEnabled) { + uiMode = Configuration.UI_MODE_TYPE_CAR; + } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { + uiMode = Configuration.UI_MODE_TYPE_DESK; + } + if (uiMode != 0) { + if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + updateTwilightLocked(); + uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES + : Configuration.UI_MODE_NIGHT_NO; + } else { + uiMode |= mNightMode << 4; + } + } else { + // Disabling the car mode clears the night mode. + uiMode = Configuration.UI_MODE_TYPE_NORMAL | + Configuration.UI_MODE_NIGHT_NO; + } + + if (uiMode != mCurUiMode) { + mCurUiMode = uiMode; + + try { + final IActivityManager am = ActivityManagerNative.getDefault(); + mConfiguration.uiMode = uiMode; + am.updateConfiguration(mConfiguration); + } catch (RemoteException e) { + Slog.w(TAG, "Failure communicating with activity manager", e); + } + } + + String action = null; + String oldAction = null; + if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { + oldAction = UiModeManager.ACTION_EXIT_CAR_MODE; + } else if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_DESK) { + oldAction = UiModeManager.ACTION_EXIT_DESK_MODE; + } + + if (mCarModeEnabled) { + if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) { + adjustStatusBarCarModeLocked(); + + if (oldAction != null) { + mContext.sendBroadcast(new Intent(oldAction)); + } + mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR; + action = UiModeManager.ACTION_ENTER_CAR_MODE; + } + } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_DESK) { + if (oldAction != null) { + mContext.sendBroadcast(new Intent(oldAction)); + } + mLastBroadcastState = Intent.EXTRA_DOCK_STATE_DESK; + action = UiModeManager.ACTION_ENTER_DESK_MODE; + } + } else { + if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { + adjustStatusBarCarModeLocked(); + } + + mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + action = oldAction; + } + + if (action != null) { + // Send the ordered broadcast; the result receiver will receive after all + // broadcasts have been sent. If any broadcast receiver changes the result + // code from the initial value of RESULT_OK, then the result receiver will + // not launch the corresponding dock application. This gives apps a chance + // to override the behavior and stay in their app even when the device is + // placed into a dock. + mContext.sendOrderedBroadcast(new Intent(action), null, + mResultReceiver, null, Activity.RESULT_OK, null, null); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void adjustStatusBarCarModeLocked() { + if (mStatusBarManager == null) { + mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); + } + + // Fear not: StatusBarService manages a list of requests to disable + // features of the status bar; these are ORed together to form the + // active disabled list. So if (for example) the device is locked and + // the status bar should be totally disabled, the calls below will + // have no effect until the device is unlocked. + if (mStatusBarManager != null) { + mStatusBarManager.disable(mCarModeEnabled + ? StatusBarManager.DISABLE_NOTIFICATION_TICKER + : StatusBarManager.DISABLE_NONE); + } + + if (mNotificationManager == null) { + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + if (mNotificationManager != null) { + if (mCarModeEnabled) { + Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class); + + Notification n = new Notification(); + n.icon = R.drawable.stat_notify_car_mode; + n.defaults = Notification.DEFAULT_LIGHTS; + n.flags = Notification.FLAG_ONGOING_EVENT; + n.when = 0; + n.setLatestEventInfo( + mContext, + mContext.getString(R.string.car_mode_disable_notification_title), + mContext.getString(R.string.car_mode_disable_notification_message), + PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0)); + mNotificationManager.notify(0, n); + } else { + mNotificationManager.cancel(0); + } + } + } + + private final Handler mHandler = new Handler() { + + boolean mPassiveListenerEnabled; + boolean mNetworkListenerEnabled; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_TWILIGHT: + synchronized (mLock) { + if (isDoingNightMode() && mLocation != null + && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + updateTwilightLocked(); + updateLocked(); + } + } + break; + case MSG_ENABLE_LOCATION_UPDATES: + // enable network provider to receive at least location updates for a given + // distance. + boolean networkLocationEnabled; + try { + networkLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if network location provider + // does not exist or is not yet installed. + networkLocationEnabled = false; + } + if (!mNetworkListenerEnabled && networkLocationEnabled) { + mNetworkListenerEnabled = true; + mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, + LOCATION_UPDATE_MS, 0, mEmptyLocationListener); + + if (mLocation == null) { + retrieveLocation(); + } + synchronized (mLock) { + if (isDoingNightMode() && mLocation != null + && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + updateTwilightLocked(); + updateLocked(); + } + } + } + // enable passive provider to receive updates from location fixes (gps + // and network). + boolean passiveLocationEnabled; + try { + passiveLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if passive location provider + // does not exist or is not yet installed. + passiveLocationEnabled = false; + } + if (!mPassiveListenerEnabled && passiveLocationEnabled) { + mPassiveListenerEnabled = true; + mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); + } + if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { + long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL); + interval *= 1.5; + if (interval == 0) { + interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; + } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { + interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; + } + Bundle bundle = new Bundle(); + bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval); + Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES); + newMsg.setData(bundle); + mHandler.sendMessageDelayed(newMsg, interval); + } + break; + } + } + + private void retrieveLocation() { + Location location; + Criteria criteria = new Criteria(); + criteria.setSpeedRequired(false); + criteria.setAltitudeRequired(false); + criteria.setBearingRequired(false); + criteria.setAccuracy(Criteria.ACCURACY_FINE); + final String bestProvider = mLocationManager.getBestProvider(criteria, true); + location = mLocationManager.getLastKnownLocation(bestProvider); + // In the case there is no location available (e.g. GPS fix or network location + // is not available yet), the longitude of the location is estimated using the timezone, + // latitude and accuracy are set to get a good average. + if (location == null) { + Time currentTime = new Time(); + currentTime.set(System.currentTimeMillis()); + double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * + (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); + location = new Location("fake"); + location.setLongitude(lngOffset); + location.setLatitude(0); + location.setAccuracy(417000.0f); + location.setTime(System.currentTimeMillis()); + } + synchronized (mLock) { + mLocation = location; + } + } + }; + + void updateTwilightLocked() { + if (mLocation == null) { + return; + } + final long currentTime = System.currentTimeMillis(); + boolean nightMode; + // calculate current twilight + TwilightCalculator tw = new TwilightCalculator(); + tw.calculateTwilight(currentTime, + mLocation.getLatitude(), mLocation.getLongitude()); + if (tw.mState == TwilightCalculator.DAY) { + nightMode = false; + } else { + nightMode = true; + } + + // schedule next update + long nextUpdate = 0; + if (tw.mSunrise == -1 || tw.mSunset == -1) { + // In the case the day or night never ends the update is scheduled 12 hours later. + nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS; + } else { + final int mLastTwilightState = tw.mState; + // add some extra time to be on the save side. + nextUpdate += DateUtils.MINUTE_IN_MILLIS; + if (currentTime > tw.mSunset) { + // next update should be on the following day + tw.calculateTwilight(currentTime + + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(), + mLocation.getLongitude()); + } + + if (mLastTwilightState == TwilightCalculator.NIGHT) { + nextUpdate += tw.mSunrise; + } else { + nextUpdate += tw.mSunset; + } + } + + Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE); + PendingIntent pendingIntent = + PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); + mAlarmManager.cancel(pendingIntent); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); + + mComputedNightMode = nightMode; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump uimode service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mLock) { + pw.println("Current UI Mode Service state:"); + pw.print(" mDockState="); pw.print(mDockState); + pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); + pw.print(" mNightMode="); pw.print(mNightMode); + pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); + pw.print(" mComputedNightMode="); pw.println(mComputedNightMode); + pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); + pw.print(" mSystemReady="); pw.println(mSystemReady); + if (mLocation != null) { + pw.print(" mLocation="); pw.println(mLocation); + } + } + } +} diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 6e7a66da75ba6..fc655a7abbb7e 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -820,7 +820,12 @@ bool AaptGroupEntry::getUiModeTypeName(const char* name, if (strcmp(name, kWildcardName) == 0) { if (out) out->uiMode = (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_NORMAL; + | ResTable_config::UI_MODE_TYPE_ANY; + return true; + } else if (strcmp(name, "desk") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_DESK; return true; } else if (strcmp(name, "car") == 0) { if (out) out->uiMode =