blob: b566fb7eb8a58bea7eb947cddba7cd326d8d8023 [file] [log] [blame]
Dan Murphyc9f4eaf2009-08-12 15:15:43 -05001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070019import android.app.Activity;
Tobias Haamel27b28b32010-02-09 23:09:17 +010020import android.app.ActivityManagerNative;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010021import android.app.AlarmManager;
Tobias Haamel27b28b32010-02-09 23:09:17 +010022import android.app.IActivityManager;
23import android.app.IUiModeManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -040024import android.app.KeyguardManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -050025import android.app.StatusBarManager;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010026import android.app.PendingIntent;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080027import android.bluetooth.BluetoothAdapter;
28import android.bluetooth.BluetoothDevice;
Mike Lockwood9092ab42009-09-16 13:01:32 -040029import android.content.ActivityNotFoundException;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070030import android.content.BroadcastReceiver;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050031import android.content.ContentResolver;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050032import android.content.Context;
33import android.content.Intent;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010034import android.content.IntentFilter;
Tobias Haamel27b28b32010-02-09 23:09:17 +010035import android.content.res.Configuration;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010036import android.location.Criteria;
37import android.location.Location;
38import android.location.LocationListener;
39import android.location.LocationManager;
40import android.location.LocationProvider;
Tobias Haamel27b28b32010-02-09 23:09:17 +010041import android.os.Binder;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050042import android.media.Ringtone;
43import android.media.RingtoneManager;
44import android.net.Uri;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010045import android.os.Bundle;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050046import android.os.Handler;
47import android.os.Message;
Tobias Haamel27b28b32010-02-09 23:09:17 +010048import android.os.RemoteException;
49import android.os.ServiceManager;
Ken Schultzf02c0742009-09-10 18:37:37 -050050import android.os.SystemClock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050051import android.os.UEventObserver;
Dianne Hackborn49493342009-10-02 10:44:41 -070052import android.provider.Settings;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080053import android.server.BluetoothService;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010054import android.text.format.DateUtils;
55import android.text.format.Time;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050056import android.util.Log;
57
Mike Lockwood733fdf32009-09-28 19:08:53 -040058import com.android.internal.widget.LockPatternUtils;
59
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050060import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080061import java.io.FileReader;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050062
63/**
64 * <p>DockObserver monitors for a docking station.
65 */
66class DockObserver extends UEventObserver {
67 private static final String TAG = DockObserver.class.getSimpleName();
68 private static final boolean LOG = false;
69
70 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
71 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
72
Bernd Holzheybfca3a02010-02-10 17:39:51 +010073 private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
74
75 private static final int MSG_DOCK_STATE = 0;
76 private static final int MSG_UPDATE_TWILIGHT = 1;
77 private static final int MSG_ENABLE_LOCATION_UPDATES = 2;
78
Tobias Haamel27b28b32010-02-09 23:09:17 +010079 public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
80 public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
81 public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
82
Bernd Holzheybfca3a02010-02-10 17:39:51 +010083 private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
84 private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
85 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
86 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
87 // velocity for estimating a potential movement: 150km/h
88 private static final float MAX_VELOCITY_M_MS = 150 / 3600;
89 private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
90
91 private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
92
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070093 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050094 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
95
Tobias Haamel27b28b32010-02-09 23:09:17 +010096 private int mNightMode = MODE_NIGHT_NO;
97 private boolean mCarModeEnabled = false;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050098
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070099 private boolean mSystemReady;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500100
101 private final Context mContext;
102
Ken Schultzf02c0742009-09-10 18:37:37 -0500103 private PowerManagerService mPowerManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -0400104
105 private KeyguardManager.KeyguardLock mKeyguardLock;
106 private boolean mKeyguardDisabled;
107 private LockPatternUtils mLockPatternUtils;
108
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100109 private AlarmManager mAlarmManager;
110
111 private LocationManager mLocationManager;
112 private Location mLocation;
113 private StatusBarManager mStatusBarManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -0500114
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700115 // The broadcast receiver which receives the result of the ordered broadcast sent when
116 // the dock state changes. The original ordered broadcast is sent with an initial result
117 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
118 // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
119 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
120 @Override
121 public void onReceive(Context context, Intent intent) {
122 if (getResultCode() != Activity.RESULT_OK) {
123 return;
124 }
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800125
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700126 // Launch a dock activity
127 String category;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100128 if (mCarModeEnabled || mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
129 category = Intent.CATEGORY_CAR_DOCK;
130 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
131 category = Intent.CATEGORY_DESK_DOCK;
132 } else {
133 category = null;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700134 }
135 if (category != null) {
136 intent = new Intent(Intent.ACTION_MAIN);
137 intent.addCategory(category);
Dianne Hackborn9bfb7072009-09-22 11:37:40 -0700138 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
139 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700140 try {
141 mContext.startActivity(intent);
142 } catch (ActivityNotFoundException e) {
143 Log.w(TAG, e.getCause());
144 }
145 }
146 }
147 };
Ken Schultzf02c0742009-09-10 18:37:37 -0500148
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100149 private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
150 @Override
151 public void onReceive(Context context, Intent intent) {
152 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
153 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
154 }
155 }
156 };
157
158 private final LocationListener mLocationListener = new LocationListener() {
159
160 public void onLocationChanged(Location location) {
161 updateLocation(location);
162 }
163
164 public void onProviderDisabled(String provider) {
165 }
166
167 public void onProviderEnabled(String provider) {
168 }
169
170 public void onStatusChanged(String provider, int status, Bundle extras) {
171 // If the network location is no longer available check for a GPS fix
172 // and try to update the location.
173 if (provider == LocationManager.NETWORK_PROVIDER &&
174 status != LocationProvider.AVAILABLE) {
175 updateLocation(mLocation);
176 }
177 }
178
179 private void updateLocation(Location location) {
180 location = DockObserver.chooseBestLocation(location,
181 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
182 if (hasMoved(location)) {
183 synchronized (this) {
184 mLocation = location;
185 }
186 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
187 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
188 }
189 }
190 }
191
192 /*
193 * The user has moved if the accuracy circles of the two locations
194 * don't overlap.
195 */
196 private boolean hasMoved(Location location) {
197 if (location == null) {
198 return false;
199 }
200 if (mLocation == null) {
201 return true;
202 }
203
204 /* if new location is older than the current one, the devices hasn't
205 * moved.
206 */
207 if (location.getTime() < mLocation.getTime()) {
208 return false;
209 }
210
211 /* Get the distance between the two points */
212 float distance = mLocation.distanceTo(location);
213
214 /* Get the total accuracy radius for both locations */
215 float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
216
217 /* If the distance is greater than the combined accuracy of the two
218 * points then they can't overlap and hence the user has moved.
219 */
220 return distance > totalAccuracy;
221 }
222 };
223
Ken Schultzf02c0742009-09-10 18:37:37 -0500224 public DockObserver(Context context, PowerManagerService pm) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500225 mContext = context;
Ken Schultzf02c0742009-09-10 18:37:37 -0500226 mPowerManager = pm;
Jim Miller31f90b62010-01-20 13:35:20 -0800227 mLockPatternUtils = new LockPatternUtils(context);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500228 init(); // set initial status
Tobias Haamel27b28b32010-02-09 23:09:17 +0100229
230 ServiceManager.addService("uimode", mBinder);
231
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100232 mAlarmManager =
233 (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
234 mLocationManager =
235 (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
236 mContext.registerReceiver(mTwilightUpdateReceiver,
237 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
238
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700239 startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500240 }
241
242 @Override
243 public void onUEvent(UEventObserver.UEvent event) {
244 if (Log.isLoggable(TAG, Log.VERBOSE)) {
245 Log.v(TAG, "Dock UEVENT: " + event.toString());
246 }
247
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700248 synchronized (this) {
249 try {
250 int newState = Integer.parseInt(event.get("SWITCH_STATE"));
251 if (newState != mDockState) {
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500252 mPreviousDockState = mDockState;
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700253 mDockState = newState;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100254 boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
255 if (mCarModeEnabled != carModeEnabled) {
256 try {
257 setCarMode(carModeEnabled);
258 } catch (RemoteException e1) {
259 Log.w(TAG, "Unable to change car mode.", e1);
260 }
261 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700262 if (mSystemReady) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500263 // Don't force screen on when undocking from the desk dock.
264 // The change in power state will do this anyway.
265 // FIXME - we should be configurable.
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500266 if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
267 mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500268 mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
269 false, true);
270 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700271 update();
272 }
273 }
274 } catch (NumberFormatException e) {
275 Log.e(TAG, "Could not parse switch state from event " + event);
276 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500277 }
278 }
279
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700280 private final void init() {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500281 char[] buffer = new char[1024];
282
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500283 try {
284 FileReader file = new FileReader(DOCK_STATE_PATH);
285 int len = file.read(buffer, 0, 1024);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500286 mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500287
288 } catch (FileNotFoundException e) {
289 Log.w(TAG, "This kernel does not have dock station support");
290 } catch (Exception e) {
291 Log.e(TAG, "" , e);
292 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500293 }
294
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700295 void systemReady() {
296 synchronized (this) {
Mike Lockwood733fdf32009-09-28 19:08:53 -0400297 KeyguardManager keyguardManager =
298 (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
299 mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
300
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700301 // don't bother broadcasting undocked here
302 if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
303 update();
304 }
305 mSystemReady = true;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100306 mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500307 }
308 }
309
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700310 private final void update() {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100311 mHandler.sendEmptyMessage(MSG_DOCK_STATE);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500312 }
313
314 private final Handler mHandler = new Handler() {
315 @Override
316 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100317 switch (msg.what) {
318 case MSG_DOCK_STATE:
319 synchronized (this) {
320 Log.i(TAG, "Dock state changed: " + mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500321
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100322 final ContentResolver cr = mContext.getContentResolver();
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500323
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100324 if (Settings.Secure.getInt(cr,
325 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
326 Log.i(TAG, "Device not provisioned, skipping dock broadcast");
327 return;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500328 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100329 // Pack up the values and broadcast them to everyone
330 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
331 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
332 if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
333 // Pretend to be in DOCK_STATE_CAR.
334 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
335 } else {
336 intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500337 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100338 intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
339
340 // Check if this is Bluetooth Dock
341 String address = BluetoothService.readDockBluetoothAddress();
342 if (address != null)
343 intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
344 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
345
346 // User feedback to confirm dock connection. Particularly
347 // useful for flaky contact pins...
348 if (Settings.System.getInt(cr,
349 Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
350 {
351 String whichSound = null;
352 if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
353 if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
354 whichSound = Settings.System.DESK_UNDOCK_SOUND;
355 } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
356 whichSound = Settings.System.CAR_UNDOCK_SOUND;
357 }
358 } else {
359 if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
360 whichSound = Settings.System.DESK_DOCK_SOUND;
361 } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
362 whichSound = Settings.System.CAR_DOCK_SOUND;
363 }
364 }
365
366 if (whichSound != null) {
367 final String soundPath = Settings.System.getString(cr, whichSound);
368 if (soundPath != null) {
369 final Uri soundUri = Uri.parse("file://" + soundPath);
370 if (soundUri != null) {
371 final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
372 if (sfx != null) sfx.play();
373 }
374 }
375 }
376 }
377
378 // Send the ordered broadcast; the result receiver will receive after all
379 // broadcasts have been sent. If any broadcast receiver changes the result
380 // code from the initial value of RESULT_OK, then the result receiver will
381 // not launch the corresponding dock application. This gives apps a chance
382 // to override the behavior and stay in their app even when the device is
383 // placed into a dock.
384 mContext.sendStickyOrderedBroadcast(
385 intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
386
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500387 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100388 break;
389 case MSG_UPDATE_TWILIGHT:
390 synchronized (this) {
391 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
392 try {
393 DockObserver.this.updateTwilight();
394 } catch (RemoteException e) {
395 Log.w(TAG, "Unable to change night mode.", e);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500396 }
397 }
398 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100399 break;
400 case MSG_ENABLE_LOCATION_UPDATES:
401 if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
402 mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
403 LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
404 retrieveLocation();
405 if (mLocation != null) {
406 try {
407 DockObserver.this.updateTwilight();
408 } catch (RemoteException e) {
409 Log.w(TAG, "Unable to change night mode.", e);
410 }
411 }
412 } else {
413 long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
414 interval *= 1.5;
415 if (interval == 0) {
416 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
417 } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
418 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
419 }
420 Bundle bundle = new Bundle();
421 bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
422 Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
423 newMsg.setData(bundle);
424 mHandler.sendMessageDelayed(newMsg, interval);
425 }
426 break;
427 }
428 }
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500429
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100430 private void retrieveLocation() {
431 final Location gpsLocation =
432 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
433 Location location;
434 Criteria criteria = new Criteria();
435 criteria.setSpeedRequired(false);
436 criteria.setAltitudeRequired(false);
437 criteria.setBearingRequired(false);
438 final String bestProvider = mLocationManager.getBestProvider(criteria, true);
439 if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
440 location = gpsLocation;
441 } else {
442 location = DockObserver.chooseBestLocation(gpsLocation,
443 mLocationManager.getLastKnownLocation(bestProvider));
444 }
445 // In the case there is no location available (e.g. GPS fix or network location
446 // is not available yet), the longitude of the location is estimated using the timezone,
447 // latitude and accuracy are set to get a good average.
448 if (location == null) {
449 Time currentTime = new Time();
450 currentTime.set(System.currentTimeMillis());
451 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * currentTime.gmtoff
452 - (currentTime.isDst > 0 ? 3600 : 0);
453 location = new Location("fake");
454 location.setLongitude(lngOffset);
455 location.setLatitude(59.95);
456 location.setAccuracy(417000.0f);
457 location.setTime(System.currentTimeMillis());
458 }
459 synchronized (this) {
460 mLocation = location;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500461 }
462 }
463 };
Tobias Haamel27b28b32010-02-09 23:09:17 +0100464
465 private void setCarMode(boolean enabled) throws RemoteException {
466 mCarModeEnabled = enabled;
467 if (enabled) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100468 if (mNightMode == MODE_NIGHT_AUTO) {
469 updateTwilight();
470 } else {
471 setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
472 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100473 } else {
474 // Disabling the car mode clears the night mode.
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100475 setMode(Configuration.UI_MODE_TYPE_NORMAL,
476 Configuration.UI_MODE_NIGHT_UNDEFINED);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100477 }
Daniel Sandlera0430a12010-02-11 23:35:49 -0500478
479 if (mStatusBarManager == null) {
480 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
481 }
482
483 // Fear not: StatusBarService manages a list of requests to disable
484 // features of the status bar; these are ORed together to form the
485 // active disabled list. So if (for example) the device is locked and
486 // the status bar should be totally disabled, the calls below will
487 // have no effect until the device is unlocked.
488 if (mStatusBarManager != null) {
Tobias Haamel1e84ac52010-02-16 08:36:54 -0800489 long ident = Binder.clearCallingIdentity();
490 mStatusBarManager.disable(enabled
Daniel Sandlera0430a12010-02-11 23:35:49 -0500491 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
492 : StatusBarManager.DISABLE_NONE);
Tobias Haamel1e84ac52010-02-16 08:36:54 -0800493 Binder.restoreCallingIdentity(ident);
Daniel Sandlera0430a12010-02-11 23:35:49 -0500494 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100495 }
496
497 private void setMode(int modeType, int modeNight) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100498 long ident = Binder.clearCallingIdentity();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100499 final IActivityManager am = ActivityManagerNative.getDefault();
500 Configuration config = am.getConfiguration();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100501 if (config.uiMode != (modeType | modeNight)) {
502 config.uiMode = modeType | modeNight;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100503 am.updateConfiguration(config);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100504 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100505 Binder.restoreCallingIdentity(ident);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100506 }
507
508 private void setNightMode(int mode) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100509 if (mNightMode != mode) {
510 mNightMode = mode;
511 switch (mode) {
512 case MODE_NIGHT_NO:
513 case MODE_NIGHT_YES:
514 setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
515 break;
516 case MODE_NIGHT_AUTO:
517 long ident = Binder.clearCallingIdentity();
518 updateTwilight();
519 Binder.restoreCallingIdentity(ident);
520 break;
521 default:
522 setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
523 break;
524 }
525 }
526 }
527
528 private void updateTwilight() throws RemoteException {
529 synchronized (this) {
530 if (mLocation == null) {
531 return;
532 }
533 final long currentTime = System.currentTimeMillis();
534 int nightMode;
535 // calculate current twilight
536 TwilightCalculator tw = new TwilightCalculator();
537 tw.calculateTwilight(currentTime,
538 mLocation.getLatitude(), mLocation.getLongitude());
539 if (tw.mState == TwilightCalculator.DAY) {
540 nightMode = MODE_NIGHT_NO;
541 } else {
542 nightMode = MODE_NIGHT_YES;
543 }
544
545 // schedule next update
546 final int mLastTwilightState = tw.mState;
547 // add some extra time to be on the save side.
548 long nextUpdate = DateUtils.MINUTE_IN_MILLIS;
549 if (currentTime > tw.mSunset) {
550 // next update should be on the following day
551 tw.calculateTwilight(currentTime
552 + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
553 mLocation.getLongitude());
554 }
555
556 if (mLastTwilightState == TwilightCalculator.NIGHT) {
557 nextUpdate += tw.mSunrise;
558 } else {
559 nextUpdate += tw.mSunset;
560 }
561
562 Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
563 PendingIntent pendingIntent =
564 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
565 mAlarmManager.cancel(pendingIntent);
566 mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
567
568 // set current mode
569 setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
570 }
571 }
572
573 /**
574 * Check which of two locations is better by comparing the distance a device
575 * could have cover since the last timestamp of the location.
576 *
577 * @param location first location
578 * @param otherLocation second location
579 * @return one of the two locations
580 */
581 protected static Location chooseBestLocation(Location location, Location otherLocation) {
582 if (location == null) {
583 return otherLocation;
584 }
585 if (otherLocation == null) {
586 return location;
587 }
588 final long currentTime = System.currentTimeMillis();
589 float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
590 + location.getAccuracy();
591 float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
592 + otherLocation.getAccuracy();
593 if (gpsPotentialMove < otherPotentialMove) {
594 return location;
595 } else {
596 return otherLocation;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100597 }
598 }
599
600 /**
601 * Wrapper class implementing the IUiModeManager interface.
602 */
603 private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() {
604
605 public void disableCarMode() throws RemoteException {
606 if (mCarModeEnabled) {
607 setCarMode(false);
608 update();
609 }
610 }
611
612 public void enableCarMode() throws RemoteException {
613 mContext.enforceCallingOrSelfPermission(
614 android.Manifest.permission.ENABLE_CAR_MODE,
615 "Need ENABLE_CAR_MODE permission");
616 if (!mCarModeEnabled) {
617 setCarMode(true);
618 update();
619 }
620 }
621
622 public void setNightMode(int mode) throws RemoteException {
623 if (mCarModeEnabled) {
624 DockObserver.this.setNightMode(mode);
625 }
626 }
627
628 public int getNightMode() throws RemoteException {
629 return mNightMode;
630 }
631 };
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500632}