blob: 4791718aad9d871a3b7c124ecc4b4764149c637e [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;
Tobias Haamel154f7a12010-02-17 11:56:39 -080025import android.app.Notification;
26import android.app.NotificationManager;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010027import android.app.PendingIntent;
Tobias Haamel154f7a12010-02-17 11:56:39 -080028import android.app.StatusBarManager;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080029import android.bluetooth.BluetoothAdapter;
30import android.bluetooth.BluetoothDevice;
Mike Lockwood9092ab42009-09-16 13:01:32 -040031import android.content.ActivityNotFoundException;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070032import android.content.BroadcastReceiver;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050033import android.content.ContentResolver;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050034import android.content.Context;
35import android.content.Intent;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010036import android.content.IntentFilter;
Tobias Haamel27b28b32010-02-09 23:09:17 +010037import android.content.res.Configuration;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010038import android.location.Criteria;
39import android.location.Location;
40import android.location.LocationListener;
41import android.location.LocationManager;
42import android.location.LocationProvider;
Tobias Haamel27b28b32010-02-09 23:09:17 +010043import android.os.Binder;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050044import android.media.Ringtone;
45import android.media.RingtoneManager;
46import android.net.Uri;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010047import android.os.Bundle;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050048import android.os.Handler;
49import android.os.Message;
Tobias Haamel27b28b32010-02-09 23:09:17 +010050import android.os.RemoteException;
51import android.os.ServiceManager;
Ken Schultzf02c0742009-09-10 18:37:37 -050052import android.os.SystemClock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050053import android.os.UEventObserver;
Dianne Hackborn49493342009-10-02 10:44:41 -070054import android.provider.Settings;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080055import android.server.BluetoothService;
Bernd Holzheybfca3a02010-02-10 17:39:51 +010056import android.text.format.DateUtils;
57import android.text.format.Time;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050058import android.util.Log;
59
Tobias Haamel154f7a12010-02-17 11:56:39 -080060import com.android.internal.R;
61import com.android.internal.app.DisableCarModeActivity;
Mike Lockwood733fdf32009-09-28 19:08:53 -040062import com.android.internal.widget.LockPatternUtils;
63
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050064import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080065import java.io.FileReader;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050066
67/**
68 * <p>DockObserver monitors for a docking station.
69 */
70class DockObserver extends UEventObserver {
71 private static final String TAG = DockObserver.class.getSimpleName();
72 private static final boolean LOG = false;
73
74 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
75 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
76
Bernd Holzheybfca3a02010-02-10 17:39:51 +010077 private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL";
78
79 private static final int MSG_DOCK_STATE = 0;
80 private static final int MSG_UPDATE_TWILIGHT = 1;
81 private static final int MSG_ENABLE_LOCATION_UPDATES = 2;
82
Tobias Haamel27b28b32010-02-09 23:09:17 +010083 public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
84 public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
85 public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
86
Bernd Holzheybfca3a02010-02-10 17:39:51 +010087 private static final long LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
88 private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
89 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
90 private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 5 * DateUtils.MINUTE_IN_MILLIS;
91 // velocity for estimating a potential movement: 150km/h
92 private static final float MAX_VELOCITY_M_MS = 150 / 3600;
93 private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
94
95 private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE";
96
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070097 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050098 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
99
Tobias Haamel27b28b32010-02-09 23:09:17 +0100100 private int mNightMode = MODE_NIGHT_NO;
101 private boolean mCarModeEnabled = false;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500102
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700103 private boolean mSystemReady;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500104
105 private final Context mContext;
106
Ken Schultzf02c0742009-09-10 18:37:37 -0500107 private PowerManagerService mPowerManager;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800108 private NotificationManager mNotificationManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -0400109
110 private KeyguardManager.KeyguardLock mKeyguardLock;
111 private boolean mKeyguardDisabled;
112 private LockPatternUtils mLockPatternUtils;
113
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100114 private AlarmManager mAlarmManager;
115
116 private LocationManager mLocationManager;
117 private Location mLocation;
118 private StatusBarManager mStatusBarManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -0500119
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700120 // The broadcast receiver which receives the result of the ordered broadcast sent when
121 // the dock state changes. The original ordered broadcast is sent with an initial result
122 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
123 // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
124 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
125 @Override
126 public void onReceive(Context context, Intent intent) {
127 if (getResultCode() != Activity.RESULT_OK) {
128 return;
129 }
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800130
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700131 // Launch a dock activity
132 String category;
Tobias Haamel154f7a12010-02-17 11:56:39 -0800133 if (mCarModeEnabled) {
134 // Only launch car home when car mode is enabled.
Tobias Haamel27b28b32010-02-09 23:09:17 +0100135 category = Intent.CATEGORY_CAR_DOCK;
136 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
137 category = Intent.CATEGORY_DESK_DOCK;
138 } else {
139 category = null;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700140 }
141 if (category != null) {
142 intent = new Intent(Intent.ACTION_MAIN);
143 intent.addCategory(category);
Dianne Hackborn9bfb7072009-09-22 11:37:40 -0700144 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
145 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700146 try {
147 mContext.startActivity(intent);
148 } catch (ActivityNotFoundException e) {
149 Log.w(TAG, e.getCause());
150 }
151 }
152 }
153 };
Ken Schultzf02c0742009-09-10 18:37:37 -0500154
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100155 private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() {
156 @Override
157 public void onReceive(Context context, Intent intent) {
158 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
159 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
160 }
161 }
162 };
163
164 private final LocationListener mLocationListener = new LocationListener() {
165
166 public void onLocationChanged(Location location) {
167 updateLocation(location);
168 }
169
170 public void onProviderDisabled(String provider) {
171 }
172
173 public void onProviderEnabled(String provider) {
174 }
175
176 public void onStatusChanged(String provider, int status, Bundle extras) {
177 // If the network location is no longer available check for a GPS fix
178 // and try to update the location.
179 if (provider == LocationManager.NETWORK_PROVIDER &&
180 status != LocationProvider.AVAILABLE) {
181 updateLocation(mLocation);
182 }
183 }
184
185 private void updateLocation(Location location) {
186 location = DockObserver.chooseBestLocation(location,
187 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER));
188 if (hasMoved(location)) {
189 synchronized (this) {
190 mLocation = location;
191 }
192 if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
193 mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT);
194 }
195 }
196 }
197
198 /*
199 * The user has moved if the accuracy circles of the two locations
200 * don't overlap.
201 */
202 private boolean hasMoved(Location location) {
203 if (location == null) {
204 return false;
205 }
206 if (mLocation == null) {
207 return true;
208 }
209
210 /* if new location is older than the current one, the devices hasn't
211 * moved.
212 */
213 if (location.getTime() < mLocation.getTime()) {
214 return false;
215 }
216
217 /* Get the distance between the two points */
218 float distance = mLocation.distanceTo(location);
219
220 /* Get the total accuracy radius for both locations */
221 float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy();
222
223 /* If the distance is greater than the combined accuracy of the two
224 * points then they can't overlap and hence the user has moved.
225 */
226 return distance > totalAccuracy;
227 }
228 };
229
Ken Schultzf02c0742009-09-10 18:37:37 -0500230 public DockObserver(Context context, PowerManagerService pm) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500231 mContext = context;
Ken Schultzf02c0742009-09-10 18:37:37 -0500232 mPowerManager = pm;
Jim Miller31f90b62010-01-20 13:35:20 -0800233 mLockPatternUtils = new LockPatternUtils(context);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500234 init(); // set initial status
Tobias Haamel27b28b32010-02-09 23:09:17 +0100235
236 ServiceManager.addService("uimode", mBinder);
237
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100238 mAlarmManager =
239 (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
240 mLocationManager =
241 (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
242 mContext.registerReceiver(mTwilightUpdateReceiver,
243 new IntentFilter(ACTION_UPDATE_NIGHT_MODE));
244
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700245 startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500246 }
247
248 @Override
249 public void onUEvent(UEventObserver.UEvent event) {
250 if (Log.isLoggable(TAG, Log.VERBOSE)) {
251 Log.v(TAG, "Dock UEVENT: " + event.toString());
252 }
253
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700254 synchronized (this) {
255 try {
256 int newState = Integer.parseInt(event.get("SWITCH_STATE"));
257 if (newState != mDockState) {
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500258 mPreviousDockState = mDockState;
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700259 mDockState = newState;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100260 boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
261 if (mCarModeEnabled != carModeEnabled) {
262 try {
263 setCarMode(carModeEnabled);
264 } catch (RemoteException e1) {
265 Log.w(TAG, "Unable to change car mode.", e1);
266 }
267 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700268 if (mSystemReady) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500269 // Don't force screen on when undocking from the desk dock.
270 // The change in power state will do this anyway.
271 // FIXME - we should be configurable.
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500272 if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
273 mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500274 mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
275 false, true);
276 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700277 update();
278 }
279 }
280 } catch (NumberFormatException e) {
281 Log.e(TAG, "Could not parse switch state from event " + event);
282 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500283 }
284 }
285
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700286 private final void init() {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500287 char[] buffer = new char[1024];
288
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500289 try {
290 FileReader file = new FileReader(DOCK_STATE_PATH);
291 int len = file.read(buffer, 0, 1024);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500292 mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500293
294 } catch (FileNotFoundException e) {
295 Log.w(TAG, "This kernel does not have dock station support");
296 } catch (Exception e) {
297 Log.e(TAG, "" , e);
298 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500299 }
300
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700301 void systemReady() {
302 synchronized (this) {
Mike Lockwood733fdf32009-09-28 19:08:53 -0400303 KeyguardManager keyguardManager =
304 (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
305 mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
306
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700307 // don't bother broadcasting undocked here
308 if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
309 update();
310 }
311 mSystemReady = true;
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100312 mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500313 }
314 }
315
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700316 private final void update() {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100317 mHandler.sendEmptyMessage(MSG_DOCK_STATE);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500318 }
319
320 private final Handler mHandler = new Handler() {
321 @Override
322 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100323 switch (msg.what) {
324 case MSG_DOCK_STATE:
325 synchronized (this) {
326 Log.i(TAG, "Dock state changed: " + mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500327
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100328 final ContentResolver cr = mContext.getContentResolver();
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500329
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100330 if (Settings.Secure.getInt(cr,
331 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
332 Log.i(TAG, "Device not provisioned, skipping dock broadcast");
333 return;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500334 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100335 // Pack up the values and broadcast them to everyone
336 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
337 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
338 if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
339 // Pretend to be in DOCK_STATE_CAR.
340 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
Tobias Haamel154f7a12010-02-17 11:56:39 -0800341 } else if (!mCarModeEnabled && mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
342 // Pretend to be in DOCK_STATE_UNDOCKED.
343 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100344 } else {
345 intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500346 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800347 intent.putExtra(Intent.EXTRA_PHYSICAL_DOCK_STATE, mDockState);
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100348 intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
349
350 // Check if this is Bluetooth Dock
351 String address = BluetoothService.readDockBluetoothAddress();
352 if (address != null)
353 intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
354 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
355
356 // User feedback to confirm dock connection. Particularly
357 // useful for flaky contact pins...
358 if (Settings.System.getInt(cr,
359 Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
360 {
361 String whichSound = null;
362 if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
363 if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
364 whichSound = Settings.System.DESK_UNDOCK_SOUND;
365 } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
366 whichSound = Settings.System.CAR_UNDOCK_SOUND;
367 }
368 } else {
369 if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
370 whichSound = Settings.System.DESK_DOCK_SOUND;
371 } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
372 whichSound = Settings.System.CAR_DOCK_SOUND;
373 }
374 }
375
376 if (whichSound != null) {
377 final String soundPath = Settings.System.getString(cr, whichSound);
378 if (soundPath != null) {
379 final Uri soundUri = Uri.parse("file://" + soundPath);
380 if (soundUri != null) {
381 final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
382 if (sfx != null) sfx.play();
383 }
384 }
385 }
386 }
387
388 // Send the ordered broadcast; the result receiver will receive after all
389 // broadcasts have been sent. If any broadcast receiver changes the result
390 // code from the initial value of RESULT_OK, then the result receiver will
391 // not launch the corresponding dock application. This gives apps a chance
392 // to override the behavior and stay in their app even when the device is
393 // placed into a dock.
394 mContext.sendStickyOrderedBroadcast(
395 intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
396
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500397 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100398 break;
399 case MSG_UPDATE_TWILIGHT:
400 synchronized (this) {
401 if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
402 try {
403 DockObserver.this.updateTwilight();
404 } catch (RemoteException e) {
405 Log.w(TAG, "Unable to change night mode.", e);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500406 }
407 }
408 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100409 break;
410 case MSG_ENABLE_LOCATION_UPDATES:
411 if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
412 mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
413 LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
414 retrieveLocation();
415 if (mLocation != null) {
416 try {
417 DockObserver.this.updateTwilight();
418 } catch (RemoteException e) {
419 Log.w(TAG, "Unable to change night mode.", e);
420 }
421 }
422 } else {
423 long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL);
424 interval *= 1.5;
425 if (interval == 0) {
426 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
427 } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
428 interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
429 }
430 Bundle bundle = new Bundle();
431 bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval);
432 Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES);
433 newMsg.setData(bundle);
434 mHandler.sendMessageDelayed(newMsg, interval);
435 }
436 break;
437 }
438 }
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500439
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100440 private void retrieveLocation() {
441 final Location gpsLocation =
442 mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
443 Location location;
444 Criteria criteria = new Criteria();
445 criteria.setSpeedRequired(false);
446 criteria.setAltitudeRequired(false);
447 criteria.setBearingRequired(false);
448 final String bestProvider = mLocationManager.getBestProvider(criteria, true);
449 if (LocationManager.GPS_PROVIDER.equals(bestProvider)) {
450 location = gpsLocation;
451 } else {
452 location = DockObserver.chooseBestLocation(gpsLocation,
453 mLocationManager.getLastKnownLocation(bestProvider));
454 }
455 // In the case there is no location available (e.g. GPS fix or network location
456 // is not available yet), the longitude of the location is estimated using the timezone,
457 // latitude and accuracy are set to get a good average.
458 if (location == null) {
459 Time currentTime = new Time();
460 currentTime.set(System.currentTimeMillis());
461 double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * currentTime.gmtoff
462 - (currentTime.isDst > 0 ? 3600 : 0);
463 location = new Location("fake");
464 location.setLongitude(lngOffset);
465 location.setLatitude(59.95);
466 location.setAccuracy(417000.0f);
467 location.setTime(System.currentTimeMillis());
468 }
469 synchronized (this) {
470 mLocation = location;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500471 }
472 }
473 };
Tobias Haamel27b28b32010-02-09 23:09:17 +0100474
Tobias Haamel154f7a12010-02-17 11:56:39 -0800475 private void adjustStatusBarCarMode() {
476 if (mStatusBarManager == null) {
477 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
478 }
479
480 // Fear not: StatusBarService manages a list of requests to disable
481 // features of the status bar; these are ORed together to form the
482 // active disabled list. So if (for example) the device is locked and
483 // the status bar should be totally disabled, the calls below will
484 // have no effect until the device is unlocked.
485 if (mStatusBarManager != null) {
486 long ident = Binder.clearCallingIdentity();
487 mStatusBarManager.disable(mCarModeEnabled
488 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
489 : StatusBarManager.DISABLE_NONE);
490 Binder.restoreCallingIdentity(ident);
491 }
492
493 if (mNotificationManager == null) {
494 mNotificationManager = (NotificationManager)
495 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
496 }
497
498 if (mNotificationManager != null) {
499 long ident = Binder.clearCallingIdentity();
500 if (mCarModeEnabled) {
501 Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
502
503 Notification n = new Notification();
504 n.icon = R.drawable.stat_notify_car_mode;
505 n.defaults = Notification.DEFAULT_LIGHTS;
506 n.flags = Notification.FLAG_ONGOING_EVENT;
507 n.when = 0;
508 n.setLatestEventInfo(
509 mContext,
510 mContext.getString(R.string.car_mode_disable_notification_title),
511 mContext.getString(R.string.car_mode_disable_notification_message),
512 PendingIntent.getActivity(mContext, 0, carModeOffIntent, 0));
513 mNotificationManager.notify(0, n);
514 } else {
515 mNotificationManager.cancel(0);
516 }
517 Binder.restoreCallingIdentity(ident);
518 }
519 }
520
Tobias Haamel27b28b32010-02-09 23:09:17 +0100521 private void setCarMode(boolean enabled) throws RemoteException {
522 mCarModeEnabled = enabled;
523 if (enabled) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100524 if (mNightMode == MODE_NIGHT_AUTO) {
525 updateTwilight();
526 } else {
527 setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode << 4);
528 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100529 } else {
530 // Disabling the car mode clears the night mode.
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100531 setMode(Configuration.UI_MODE_TYPE_NORMAL,
532 Configuration.UI_MODE_NIGHT_UNDEFINED);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100533 }
Tobias Haamel154f7a12010-02-17 11:56:39 -0800534 adjustStatusBarCarMode();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100535 }
536
537 private void setMode(int modeType, int modeNight) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100538 long ident = Binder.clearCallingIdentity();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100539 final IActivityManager am = ActivityManagerNative.getDefault();
540 Configuration config = am.getConfiguration();
Tobias Haamel27b28b32010-02-09 23:09:17 +0100541 if (config.uiMode != (modeType | modeNight)) {
542 config.uiMode = modeType | modeNight;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100543 am.updateConfiguration(config);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100544 }
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100545 Binder.restoreCallingIdentity(ident);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100546 }
547
548 private void setNightMode(int mode) throws RemoteException {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100549 if (mNightMode != mode) {
550 mNightMode = mode;
551 switch (mode) {
552 case MODE_NIGHT_NO:
553 case MODE_NIGHT_YES:
554 setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
555 break;
556 case MODE_NIGHT_AUTO:
557 long ident = Binder.clearCallingIdentity();
558 updateTwilight();
559 Binder.restoreCallingIdentity(ident);
560 break;
561 default:
562 setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
563 break;
564 }
565 }
566 }
567
568 private void updateTwilight() throws RemoteException {
569 synchronized (this) {
570 if (mLocation == null) {
571 return;
572 }
573 final long currentTime = System.currentTimeMillis();
574 int nightMode;
575 // calculate current twilight
576 TwilightCalculator tw = new TwilightCalculator();
577 tw.calculateTwilight(currentTime,
578 mLocation.getLatitude(), mLocation.getLongitude());
579 if (tw.mState == TwilightCalculator.DAY) {
580 nightMode = MODE_NIGHT_NO;
581 } else {
582 nightMode = MODE_NIGHT_YES;
583 }
584
585 // schedule next update
586 final int mLastTwilightState = tw.mState;
587 // add some extra time to be on the save side.
588 long nextUpdate = DateUtils.MINUTE_IN_MILLIS;
589 if (currentTime > tw.mSunset) {
590 // next update should be on the following day
591 tw.calculateTwilight(currentTime
592 + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
593 mLocation.getLongitude());
594 }
595
596 if (mLastTwilightState == TwilightCalculator.NIGHT) {
597 nextUpdate += tw.mSunrise;
598 } else {
599 nextUpdate += tw.mSunset;
600 }
601
602 Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
603 PendingIntent pendingIntent =
604 PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
605 mAlarmManager.cancel(pendingIntent);
606 mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
607
608 // set current mode
609 setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
610 }
611 }
612
613 /**
614 * Check which of two locations is better by comparing the distance a device
615 * could have cover since the last timestamp of the location.
616 *
617 * @param location first location
618 * @param otherLocation second location
619 * @return one of the two locations
620 */
621 protected static Location chooseBestLocation(Location location, Location otherLocation) {
622 if (location == null) {
623 return otherLocation;
624 }
625 if (otherLocation == null) {
626 return location;
627 }
628 final long currentTime = System.currentTimeMillis();
629 float gpsPotentialMove = MAX_VELOCITY_M_MS * (currentTime - location.getTime())
630 + location.getAccuracy();
631 float otherPotentialMove = MAX_VELOCITY_M_MS * (currentTime - otherLocation.getTime())
632 + otherLocation.getAccuracy();
633 if (gpsPotentialMove < otherPotentialMove) {
634 return location;
635 } else {
636 return otherLocation;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100637 }
638 }
639
640 /**
641 * Wrapper class implementing the IUiModeManager interface.
642 */
643 private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() {
644
645 public void disableCarMode() throws RemoteException {
646 if (mCarModeEnabled) {
647 setCarMode(false);
648 update();
649 }
650 }
651
652 public void enableCarMode() throws RemoteException {
653 mContext.enforceCallingOrSelfPermission(
654 android.Manifest.permission.ENABLE_CAR_MODE,
655 "Need ENABLE_CAR_MODE permission");
656 if (!mCarModeEnabled) {
657 setCarMode(true);
658 update();
659 }
660 }
661
662 public void setNightMode(int mode) throws RemoteException {
663 if (mCarModeEnabled) {
664 DockObserver.this.setNightMode(mode);
665 }
666 }
667
668 public int getNightMode() throws RemoteException {
669 return mNightMode;
670 }
671 };
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500672}