2009-08-12 15:15:43 -05:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2009-09-19 18:06:52 -07:00
|
|
|
import android.app.Activity;
|
2010-02-09 23:09:17 +01:00
|
|
|
import android.app.ActivityManagerNative;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.app.AlarmManager;
|
2010-02-09 23:09:17 +01:00
|
|
|
import android.app.IActivityManager;
|
|
|
|
import android.app.IUiModeManager;
|
2009-09-28 19:08:53 -04:00
|
|
|
import android.app.KeyguardManager;
|
2010-02-11 23:35:49 -05:00
|
|
|
import android.app.StatusBarManager;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.app.PendingIntent;
|
2009-12-02 17:28:38 -08:00
|
|
|
import android.bluetooth.BluetoothAdapter;
|
|
|
|
import android.bluetooth.BluetoothDevice;
|
2009-09-16 13:01:32 -04:00
|
|
|
import android.content.ActivityNotFoundException;
|
2009-09-19 18:06:52 -07:00
|
|
|
import android.content.BroadcastReceiver;
|
2010-01-25 11:33:03 -05:00
|
|
|
import android.content.ContentResolver;
|
2009-08-12 15:15:43 -05:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.content.IntentFilter;
|
2010-02-09 23:09:17 +01:00
|
|
|
import android.content.res.Configuration;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.location.Criteria;
|
|
|
|
import android.location.Location;
|
|
|
|
import android.location.LocationListener;
|
|
|
|
import android.location.LocationManager;
|
|
|
|
import android.location.LocationProvider;
|
2010-02-09 23:09:17 +01:00
|
|
|
import android.os.Binder;
|
2010-01-25 11:33:03 -05:00
|
|
|
import android.media.Ringtone;
|
|
|
|
import android.media.RingtoneManager;
|
|
|
|
import android.net.Uri;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.os.Bundle;
|
2009-08-12 15:15:43 -05:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Message;
|
2010-02-09 23:09:17 +01:00
|
|
|
import android.os.RemoteException;
|
|
|
|
import android.os.ServiceManager;
|
2009-09-10 18:37:37 -05:00
|
|
|
import android.os.SystemClock;
|
2009-08-12 15:15:43 -05:00
|
|
|
import android.os.UEventObserver;
|
2009-10-02 10:44:41 -07:00
|
|
|
import android.provider.Settings;
|
2009-12-02 17:28:38 -08:00
|
|
|
import android.server.BluetoothService;
|
2010-02-10 17:39:51 +01:00
|
|
|
import android.text.format.DateUtils;
|
|
|
|
import android.text.format.Time;
|
2009-08-12 15:15:43 -05:00
|
|
|
import android.util.Log;
|
|
|
|
|
2009-09-28 19:08:53 -04:00
|
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
|
|
|
2009-08-12 15:15:43 -05:00
|
|
|
import java.io.FileNotFoundException;
|
2009-12-02 17:28:38 -08:00
|
|
|
import java.io.FileReader;
|
2009-08-12 15:15:43 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* <p>DockObserver monitors for a docking station.
|
|
|
|
*/
|
|
|
|
class DockObserver extends UEventObserver {
|
|
|
|
private static final String TAG = DockObserver.class.getSimpleName();
|
|
|
|
private static final boolean LOG = false;
|
|
|
|
|
|
|
|
private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
|
|
|
|
private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
|
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
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;
|
|
|
|
|
2010-02-09 23:09:17 +01:00
|
|
|
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;
|
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
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;
|
|
|
|
// velocity for estimating a potential movement: 150km/h
|
|
|
|
private static final float MAX_VELOCITY_M_MS = 150 / 3600;
|
|
|
|
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";
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
|
2010-01-25 11:33:03 -05:00
|
|
|
private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
|
|
|
|
|
2010-02-09 23:09:17 +01:00
|
|
|
private int mNightMode = MODE_NIGHT_NO;
|
|
|
|
private boolean mCarModeEnabled = false;
|
2010-01-25 11:33:03 -05:00
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
private boolean mSystemReady;
|
2009-08-12 15:15:43 -05:00
|
|
|
|
|
|
|
private final Context mContext;
|
|
|
|
|
2009-09-10 18:37:37 -05:00
|
|
|
private PowerManagerService mPowerManager;
|
2009-09-28 19:08:53 -04:00
|
|
|
|
|
|
|
private KeyguardManager.KeyguardLock mKeyguardLock;
|
|
|
|
private boolean mKeyguardDisabled;
|
|
|
|
private LockPatternUtils mLockPatternUtils;
|
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
private AlarmManager mAlarmManager;
|
|
|
|
|
|
|
|
private LocationManager mLocationManager;
|
|
|
|
private Location mLocation;
|
|
|
|
private StatusBarManager mStatusBarManager;
|
2010-02-11 23:35:49 -05:00
|
|
|
|
2009-09-19 18:06:52 -07:00
|
|
|
// 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;
|
|
|
|
}
|
2009-12-02 17:28:38 -08:00
|
|
|
|
2009-09-19 18:06:52 -07:00
|
|
|
// Launch a dock activity
|
|
|
|
String category;
|
2010-02-09 23:09:17 +01:00
|
|
|
if (mCarModeEnabled || mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
|
|
|
|
category = Intent.CATEGORY_CAR_DOCK;
|
|
|
|
} else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
|
|
|
|
category = Intent.CATEGORY_DESK_DOCK;
|
|
|
|
} else {
|
|
|
|
category = null;
|
2009-09-19 18:06:52 -07:00
|
|
|
}
|
|
|
|
if (category != null) {
|
|
|
|
intent = new Intent(Intent.ACTION_MAIN);
|
|
|
|
intent.addCategory(category);
|
2009-09-22 11:37:40 -07:00
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
2009-09-19 18:06:52 -07:00
|
|
|
try {
|
|
|
|
mContext.startActivity(intent);
|
|
|
|
} catch (ActivityNotFoundException e) {
|
|
|
|
Log.w(TAG, e.getCause());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2009-09-10 18:37:37 -05:00
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private final LocationListener mLocationListener = new LocationListener() {
|
|
|
|
|
|
|
|
public void onLocationChanged(Location location) {
|
|
|
|
updateLocation(location);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onProviderDisabled(String provider) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onProviderEnabled(String provider) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
|
|
|
// If the network location is no longer available check for a GPS fix
|
|
|
|
// and try to update the location.
|
|
|
|
if (provider == LocationManager.NETWORK_PROVIDER &&
|
|
|
|
status != LocationProvider.AVAILABLE) {
|
|
|
|
updateLocation(mLocation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateLocation(Location location) {
|
|
|
|
location = DockObserver.chooseBestLocation(location,
|
|
|
|
mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
|
|
|
|
if (hasMoved(location)) {
|
|
|
|
synchronized (this) {
|
|
|
|
mLocation = location;
|
|
|
|
}
|
|
|
|
if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
|
|
|
|
mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2009-09-10 18:37:37 -05:00
|
|
|
public DockObserver(Context context, PowerManagerService pm) {
|
2009-08-12 15:15:43 -05:00
|
|
|
mContext = context;
|
2009-09-10 18:37:37 -05:00
|
|
|
mPowerManager = pm;
|
2010-01-20 13:35:20 -08:00
|
|
|
mLockPatternUtils = new LockPatternUtils(context);
|
2009-08-12 15:15:43 -05:00
|
|
|
init(); // set initial status
|
2010-02-09 23:09:17 +01:00
|
|
|
|
|
|
|
ServiceManager.addService("uimode", mBinder);
|
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
mAlarmManager =
|
|
|
|
(AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
|
|
|
|
mLocationManager =
|
|
|
|
(LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
|
|
|
|
mContext.registerReceiver(mTwilightUpdateReceiver,
|
|
|
|
new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
startObserving(DOCK_UEVENT_MATCH);
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onUEvent(UEventObserver.UEvent event) {
|
|
|
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
|
|
|
Log.v(TAG, "Dock UEVENT: " + event.toString());
|
|
|
|
}
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
synchronized (this) {
|
|
|
|
try {
|
|
|
|
int newState = Integer.parseInt(event.get("SWITCH_STATE"));
|
|
|
|
if (newState != mDockState) {
|
2010-01-25 11:33:03 -05:00
|
|
|
mPreviousDockState = mDockState;
|
2009-08-27 16:19:07 -07:00
|
|
|
mDockState = newState;
|
2010-02-09 23:09:17 +01:00
|
|
|
boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
|
|
|
|
if (mCarModeEnabled != carModeEnabled) {
|
|
|
|
try {
|
|
|
|
setCarMode(carModeEnabled);
|
|
|
|
} catch (RemoteException e1) {
|
|
|
|
Log.w(TAG, "Unable to change car mode.", e1);
|
|
|
|
}
|
|
|
|
}
|
2009-08-27 16:19:07 -07:00
|
|
|
if (mSystemReady) {
|
2009-11-11 18:09:25 -05:00
|
|
|
// Don't force screen on when undocking from the desk dock.
|
|
|
|
// The change in power state will do this anyway.
|
|
|
|
// FIXME - we should be configurable.
|
2010-01-25 11:33:03 -05:00
|
|
|
if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
|
|
|
|
mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
|
2009-11-11 18:09:25 -05:00
|
|
|
mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
|
|
|
|
false, true);
|
|
|
|
}
|
2009-08-27 16:19:07 -07:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
Log.e(TAG, "Could not parse switch state from event " + event);
|
|
|
|
}
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
private final void init() {
|
2009-08-12 15:15:43 -05:00
|
|
|
char[] buffer = new char[1024];
|
|
|
|
|
|
|
|
try {
|
|
|
|
FileReader file = new FileReader(DOCK_STATE_PATH);
|
|
|
|
int len = file.read(buffer, 0, 1024);
|
2010-01-25 11:33:03 -05:00
|
|
|
mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
|
2009-08-12 15:15:43 -05:00
|
|
|
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
Log.w(TAG, "This kernel does not have dock station support");
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.e(TAG, "" , e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
void systemReady() {
|
|
|
|
synchronized (this) {
|
2009-09-28 19:08:53 -04:00
|
|
|
KeyguardManager keyguardManager =
|
|
|
|
(KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
|
|
|
|
mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
// don't bother broadcasting undocked here
|
|
|
|
if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
mSystemReady = true;
|
2010-02-10 17:39:51 +01:00
|
|
|
mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-27 16:19:07 -07:00
|
|
|
private final void update() {
|
2010-02-10 17:39:51 +01:00
|
|
|
mHandler.sendEmptyMessage(MSG_DOCK_STATE);
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
private final Handler mHandler = new Handler() {
|
|
|
|
@Override
|
|
|
|
public void handleMessage(Message msg) {
|
2010-02-10 17:39:51 +01:00
|
|
|
switch (msg.what) {
|
|
|
|
case MSG_DOCK_STATE:
|
|
|
|
synchronized (this) {
|
|
|
|
Log.i(TAG, "Dock state changed: " + mDockState);
|
2010-01-25 11:33:03 -05:00
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
final ContentResolver cr = mContext.getContentResolver();
|
2010-01-25 11:33:03 -05:00
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
if (Settings.Secure.getInt(cr,
|
|
|
|
Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
|
|
|
|
Log.i(TAG, "Device not provisioned, skipping dock broadcast");
|
|
|
|
return;
|
2010-01-25 11:33:03 -05:00
|
|
|
}
|
2010-02-10 17:39:51 +01:00
|
|
|
// 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 {
|
|
|
|
intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
|
2010-01-25 11:33:03 -05:00
|
|
|
}
|
2010-02-10 17:39:51 +01:00
|
|
|
intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
|
|
|
|
|
|
|
|
// Check if this is Bluetooth Dock
|
|
|
|
String address = BluetoothService.readDockBluetoothAddress();
|
|
|
|
if (address != null)
|
|
|
|
intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
|
|
|
|
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
|
|
|
|
|
|
|
|
// User feedback to confirm dock connection. Particularly
|
|
|
|
// useful for flaky contact pins...
|
|
|
|
if (Settings.System.getInt(cr,
|
|
|
|
Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
|
|
|
|
{
|
|
|
|
String whichSound = null;
|
|
|
|
if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
|
|
|
|
if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
|
|
|
|
whichSound = Settings.System.DESK_UNDOCK_SOUND;
|
|
|
|
} else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
|
|
|
|
whichSound = Settings.System.CAR_UNDOCK_SOUND;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
|
|
|
|
whichSound = Settings.System.DESK_DOCK_SOUND;
|
|
|
|
} else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
|
|
|
|
whichSound = Settings.System.CAR_DOCK_SOUND;
|
|
|
|
}
|
|
|
|
}
|
2010-01-25 11:33:03 -05:00
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
if (whichSound != null) {
|
|
|
|
final String soundPath = Settings.System.getString(cr, whichSound);
|
|
|
|
if (soundPath != null) {
|
|
|
|
final Uri soundUri = Uri.parse("file://" + soundPath);
|
|
|
|
if (soundUri != null) {
|
|
|
|
final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
|
|
|
|
if (sfx != null) sfx.play();
|
|
|
|
}
|
|
|
|
}
|
2010-01-25 11:33:03 -05:00
|
|
|
}
|
|
|
|
}
|
2010-02-10 17:39:51 +01:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2010-01-25 11:33:03 -05:00
|
|
|
}
|
2010-02-10 17:39:51 +01:00
|
|
|
break;
|
|
|
|
case MSG_UPDATE_TWILIGHT:
|
|
|
|
synchronized (this) {
|
|
|
|
if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
|
|
|
|
try {
|
|
|
|
DockObserver.this.updateTwilight();
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
Log.w(TAG, "Unable to change night mode.", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MSG_ENABLE_LOCATION_UPDATES:
|
|
|
|
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
|
|
|
|
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
|
|
|
|
LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
|
|
|
|
retrieveLocation();
|
|
|
|
if (mLocation != null) {
|
|
|
|
try {
|
|
|
|
DockObserver.this.updateTwilight();
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
Log.w(TAG, "Unable to change night mode.", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2010-01-25 11:33:03 -05:00
|
|
|
|
2010-02-10 17:39:51 +01:00
|
|
|
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);
|
|
|
|
final String bestProvider = mLocationManager.getBestProvider(criteria, true);
|
|
|
|
if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
|
|
|
|
location = gpsLocation;
|
|
|
|
} else {
|
|
|
|
location = DockObserver.chooseBestLocation(gpsLocation,
|
|
|
|
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(59.95);
|
|
|
|
location.setAccuracy(417000.0f);
|
|
|
|
location.setTime(System.currentTimeMillis());
|
|
|
|
}
|
|
|
|
synchronized (this) {
|
|
|
|
mLocation = location;
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2010-02-09 23:09:17 +01:00
|
|
|
|
|
|
|
private void setCarMode(boolean enabled) throws RemoteException {
|
|
|
|
mCarModeEnabled = enabled;
|
|
|
|
if (enabled) {
|
2010-02-10 17:39:51 +01:00
|
|
|
if (mNightMode == MODE_NIGHT_AUTO) {
|
|
|
|
updateTwilight();
|
|
|
|
} else {
|
|
|
|
setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
|
|
|
|
}
|
2010-02-09 23:09:17 +01:00
|
|
|
} else {
|
|
|
|
// Disabling the car mode clears the night mode.
|
2010-02-10 17:39:51 +01:00
|
|
|
setMode(Configuration.UI_MODE_TYPE_NORMAL,
|
|
|
|
Configuration.UI_MODE_NIGHT_UNDEFINED);
|
2010-02-09 23:09:17 +01:00
|
|
|
}
|
2010-02-11 23:35:49 -05:00
|
|
|
|
|
|
|
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) {
|
2010-02-16 08:36:54 -08:00
|
|
|
long ident = Binder.clearCallingIdentity();
|
|
|
|
mStatusBarManager.disable(enabled
|
2010-02-11 23:35:49 -05:00
|
|
|
? StatusBarManager.DISABLE_NOTIFICATION_TICKER
|
|
|
|
: StatusBarManager.DISABLE_NONE);
|
2010-02-16 08:36:54 -08:00
|
|
|
Binder.restoreCallingIdentity(ident);
|
2010-02-11 23:35:49 -05:00
|
|
|
}
|
2010-02-09 23:09:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void setMode(int modeType, int modeNight) throws RemoteException {
|
2010-02-10 17:39:51 +01:00
|
|
|
long ident = Binder.clearCallingIdentity();
|
2010-02-09 23:09:17 +01:00
|
|
|
final IActivityManager am = ActivityManagerNative.getDefault();
|
|
|
|
Configuration config = am.getConfiguration();
|
|
|
|
if (config.uiMode != (modeType | modeNight)) {
|
|
|
|
config.uiMode = modeType | modeNight;
|
|
|
|
am.updateConfiguration(config);
|
|
|
|
}
|
2010-02-10 17:39:51 +01:00
|
|
|
Binder.restoreCallingIdentity(ident);
|
2010-02-09 23:09:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void setNightMode(int mode) throws RemoteException {
|
2010-02-10 17:39:51 +01:00
|
|
|
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
|
|
|
|
final int mLastTwilightState = tw.mState;
|
|
|
|
// add some extra time to be on the save side.
|
|
|
|
long 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);
|
|
|
|
|
|
|
|
// set current mode
|
|
|
|
setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check which of two locations is better by comparing the distance a device
|
|
|
|
* could have cover since the last timestamp of the location.
|
|
|
|
*
|
|
|
|
* @param location first location
|
|
|
|
* @param otherLocation second location
|
|
|
|
* @return one of the two locations
|
|
|
|
*/
|
|
|
|
protected static Location chooseBestLocation(Location location, Location otherLocation) {
|
|
|
|
if (location == null) {
|
|
|
|
return otherLocation;
|
|
|
|
}
|
|
|
|
if (otherLocation == null) {
|
|
|
|
return location;
|
|
|
|
}
|
|
|
|
final long currentTime = System.currentTimeMillis();
|
|
|
|
float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
|
|
|
|
+ location.getAccuracy();
|
|
|
|
float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
|
|
|
|
+ otherLocation.getAccuracy();
|
|
|
|
if (gpsPotentialMove < otherPotentialMove) {
|
|
|
|
return location;
|
|
|
|
} else {
|
|
|
|
return otherLocation;
|
2010-02-09 23:09:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
};
|
2009-08-12 15:15:43 -05:00
|
|
|
}
|