blob: d2804750059b92fae4185030902b323acdb0454e [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;
21import android.app.IActivityManager;
22import android.app.IUiModeManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -040023import android.app.KeyguardManager;
Daniel Sandlera0430a12010-02-11 23:35:49 -050024import android.app.StatusBarManager;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080025import android.bluetooth.BluetoothAdapter;
26import android.bluetooth.BluetoothDevice;
Mike Lockwood9092ab42009-09-16 13:01:32 -040027import android.content.ActivityNotFoundException;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070028import android.content.BroadcastReceiver;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050029import android.content.ContentResolver;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050030import android.content.Context;
31import android.content.Intent;
Tobias Haamel27b28b32010-02-09 23:09:17 +010032import android.content.res.Configuration;
33import android.os.Binder;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050034import android.media.Ringtone;
35import android.media.RingtoneManager;
36import android.net.Uri;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050037import android.os.Handler;
38import android.os.Message;
Tobias Haamel27b28b32010-02-09 23:09:17 +010039import android.os.RemoteException;
40import android.os.ServiceManager;
Ken Schultzf02c0742009-09-10 18:37:37 -050041import android.os.SystemClock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050042import android.os.UEventObserver;
Dianne Hackborn49493342009-10-02 10:44:41 -070043import android.provider.Settings;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080044import android.server.BluetoothService;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050045import android.util.Log;
46
Mike Lockwood733fdf32009-09-28 19:08:53 -040047import com.android.internal.widget.LockPatternUtils;
48
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050049import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080050import java.io.FileReader;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050051
52/**
53 * <p>DockObserver monitors for a docking station.
54 */
55class DockObserver extends UEventObserver {
56 private static final String TAG = DockObserver.class.getSimpleName();
57 private static final boolean LOG = false;
58
59 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
60 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
61
Tobias Haamel27b28b32010-02-09 23:09:17 +010062 public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_MASK >> 4;
63 public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
64 public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
65
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070066 private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050067 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
68
Tobias Haamel27b28b32010-02-09 23:09:17 +010069 private int mNightMode = MODE_NIGHT_NO;
70 private boolean mCarModeEnabled = false;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050071
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070072 private boolean mSystemReady;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050073
74 private final Context mContext;
75
Ken Schultzf02c0742009-09-10 18:37:37 -050076 private PowerManagerService mPowerManager;
Mike Lockwood733fdf32009-09-28 19:08:53 -040077
78 private KeyguardManager.KeyguardLock mKeyguardLock;
79 private boolean mKeyguardDisabled;
80 private LockPatternUtils mLockPatternUtils;
81
Daniel Sandlera0430a12010-02-11 23:35:49 -050082 private StatusBarManager mStatusBarManager;
83
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070084 // The broadcast receiver which receives the result of the ordered broadcast sent when
85 // the dock state changes. The original ordered broadcast is sent with an initial result
86 // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
87 // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
88 private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
89 @Override
90 public void onReceive(Context context, Intent intent) {
91 if (getResultCode() != Activity.RESULT_OK) {
92 return;
93 }
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080094
Mike LeBeau1f6c7e62009-09-19 18:06:52 -070095 // Launch a dock activity
96 String category;
Tobias Haamel27b28b32010-02-09 23:09:17 +010097 if (mCarModeEnabled || mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
98 category = Intent.CATEGORY_CAR_DOCK;
99 } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
100 category = Intent.CATEGORY_DESK_DOCK;
101 } else {
102 category = null;
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700103 }
104 if (category != null) {
105 intent = new Intent(Intent.ACTION_MAIN);
106 intent.addCategory(category);
Dianne Hackborn9bfb7072009-09-22 11:37:40 -0700107 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
108 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700109 try {
110 mContext.startActivity(intent);
111 } catch (ActivityNotFoundException e) {
112 Log.w(TAG, e.getCause());
113 }
114 }
115 }
116 };
Ken Schultzf02c0742009-09-10 18:37:37 -0500117
118 public DockObserver(Context context, PowerManagerService pm) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500119 mContext = context;
Ken Schultzf02c0742009-09-10 18:37:37 -0500120 mPowerManager = pm;
Jim Miller31f90b62010-01-20 13:35:20 -0800121 mLockPatternUtils = new LockPatternUtils(context);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500122 init(); // set initial status
Tobias Haamel27b28b32010-02-09 23:09:17 +0100123
124 ServiceManager.addService("uimode", mBinder);
125
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700126 startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500127 }
128
129 @Override
130 public void onUEvent(UEventObserver.UEvent event) {
131 if (Log.isLoggable(TAG, Log.VERBOSE)) {
132 Log.v(TAG, "Dock UEVENT: " + event.toString());
133 }
134
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700135 synchronized (this) {
136 try {
137 int newState = Integer.parseInt(event.get("SWITCH_STATE"));
138 if (newState != mDockState) {
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500139 mPreviousDockState = mDockState;
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700140 mDockState = newState;
Tobias Haamel27b28b32010-02-09 23:09:17 +0100141 boolean carModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
142 if (mCarModeEnabled != carModeEnabled) {
143 try {
144 setCarMode(carModeEnabled);
145 } catch (RemoteException e1) {
146 Log.w(TAG, "Unable to change car mode.", e1);
147 }
148 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700149 if (mSystemReady) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500150 // Don't force screen on when undocking from the desk dock.
151 // The change in power state will do this anyway.
152 // FIXME - we should be configurable.
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500153 if (mPreviousDockState != Intent.EXTRA_DOCK_STATE_DESK ||
154 mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
Mike Lockwood1d069922009-11-11 18:09:25 -0500155 mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(),
156 false, true);
157 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700158 update();
159 }
160 }
161 } catch (NumberFormatException e) {
162 Log.e(TAG, "Could not parse switch state from event " + event);
163 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500164 }
165 }
166
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700167 private final void init() {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500168 char[] buffer = new char[1024];
169
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500170 try {
171 FileReader file = new FileReader(DOCK_STATE_PATH);
172 int len = file.read(buffer, 0, 1024);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500173 mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500174
175 } catch (FileNotFoundException e) {
176 Log.w(TAG, "This kernel does not have dock station support");
177 } catch (Exception e) {
178 Log.e(TAG, "" , e);
179 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500180 }
181
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700182 void systemReady() {
183 synchronized (this) {
Mike Lockwood733fdf32009-09-28 19:08:53 -0400184 KeyguardManager keyguardManager =
185 (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
186 mKeyguardLock = keyguardManager.newKeyguardLock(TAG);
187
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700188 // don't bother broadcasting undocked here
189 if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
190 update();
191 }
192 mSystemReady = true;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500193 }
194 }
195
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700196 private final void update() {
197 mHandler.sendEmptyMessage(0);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500198 }
199
200 private final Handler mHandler = new Handler() {
201 @Override
202 public void handleMessage(Message msg) {
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700203 synchronized (this) {
Dianne Hackborn49493342009-10-02 10:44:41 -0700204 Log.i(TAG, "Dock state changed: " + mDockState);
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500205
206 final ContentResolver cr = mContext.getContentResolver();
207
208 if (Settings.Secure.getInt(cr,
Dianne Hackborn49493342009-10-02 10:44:41 -0700209 Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
210 Log.i(TAG, "Device not provisioned, skipping dock broadcast");
211 return;
212 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700213 // Pack up the values and broadcast them to everyone
214 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
Dianne Hackborn1c633fc2009-12-08 19:45:14 -0800215 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
Tobias Haamel27b28b32010-02-09 23:09:17 +0100216 if (mCarModeEnabled && mDockState != Intent.EXTRA_DOCK_STATE_CAR) {
217 // Pretend to be in DOCK_STATE_CAR.
218 intent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR);
219 } else {
220 intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
221 }
222 intent.putExtra(Intent.EXTRA_CAR_MODE_ENABLED, mCarModeEnabled);
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -0800223
224 // Check if this is Bluetooth Dock
225 String address = BluetoothService.readDockBluetoothAddress();
226 if (address != null)
227 intent.putExtra(BluetoothDevice.EXTRA_DEVICE,
228 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address));
229
Daniel Sandler0e9d2af2010-01-25 11:33:03 -0500230 // User feedback to confirm dock connection. Particularly
231 // useful for flaky contact pins...
232 if (Settings.System.getInt(cr,
233 Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1)
234 {
235 String whichSound = null;
236 if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
237 if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
238 whichSound = Settings.System.DESK_UNDOCK_SOUND;
239 } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
240 whichSound = Settings.System.CAR_UNDOCK_SOUND;
241 }
242 } else {
243 if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
244 whichSound = Settings.System.DESK_DOCK_SOUND;
245 } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
246 whichSound = Settings.System.CAR_DOCK_SOUND;
247 }
248 }
249
250 if (whichSound != null) {
251 final String soundPath = Settings.System.getString(cr, whichSound);
252 if (soundPath != null) {
253 final Uri soundUri = Uri.parse("file://" + soundPath);
254 if (soundUri != null) {
255 final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
256 if (sfx != null) sfx.play();
257 }
258 }
259 }
260 }
261
Mike LeBeau1f6c7e62009-09-19 18:06:52 -0700262 // Send the ordered broadcast; the result receiver will receive after all
263 // broadcasts have been sent. If any broadcast receiver changes the result
264 // code from the initial value of RESULT_OK, then the result receiver will
265 // not launch the corresponding dock application. This gives apps a chance
266 // to override the behavior and stay in their app even when the device is
267 // placed into a dock.
268 mContext.sendStickyOrderedBroadcast(
269 intent, mResultReceiver, null, Activity.RESULT_OK, null, null);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500270 }
271 }
272 };
Tobias Haamel27b28b32010-02-09 23:09:17 +0100273
274 private void setCarMode(boolean enabled) throws RemoteException {
275 mCarModeEnabled = enabled;
276 if (enabled) {
277 setMode(Configuration.UI_MODE_TYPE_CAR, mNightMode);
278 } else {
279 // Disabling the car mode clears the night mode.
280 setMode(Configuration.UI_MODE_TYPE_NORMAL, MODE_NIGHT_NO);
281 }
Daniel Sandlera0430a12010-02-11 23:35:49 -0500282
283 if (mStatusBarManager == null) {
284 mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE);
285 }
286
287 // Fear not: StatusBarService manages a list of requests to disable
288 // features of the status bar; these are ORed together to form the
289 // active disabled list. So if (for example) the device is locked and
290 // the status bar should be totally disabled, the calls below will
291 // have no effect until the device is unlocked.
292 if (mStatusBarManager != null) {
293 mStatusBarManager.disable(enabled
294 ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
295 : StatusBarManager.DISABLE_NONE);
296 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100297 }
298
299 private void setMode(int modeType, int modeNight) throws RemoteException {
300 final IActivityManager am = ActivityManagerNative.getDefault();
301 Configuration config = am.getConfiguration();
302
303 if (config.uiMode != (modeType | modeNight)) {
304 config.uiMode = modeType | modeNight;
305 long ident = Binder.clearCallingIdentity();
306 am.updateConfiguration(config);
307 Binder.restoreCallingIdentity(ident);
308 }
309 }
310
311 private void setNightMode(int mode) throws RemoteException {
312 mNightMode = mode;
313 switch (mode) {
314 case MODE_NIGHT_NO:
315 case MODE_NIGHT_YES:
316 setMode(Configuration.UI_MODE_TYPE_CAR, mode << 4);
317 break;
318 case MODE_NIGHT_AUTO:
319 // FIXME: not yet supported, this functionality will be
320 // added in a separate change.
321 break;
322 default:
323 setMode(Configuration.UI_MODE_TYPE_CAR, MODE_NIGHT_NO << 4);
324 break;
325 }
326 }
327
328 /**
329 * Wrapper class implementing the IUiModeManager interface.
330 */
331 private final IUiModeManager.Stub mBinder = new IUiModeManager.Stub() {
332
333 public void disableCarMode() throws RemoteException {
334 if (mCarModeEnabled) {
335 setCarMode(false);
336 update();
337 }
338 }
339
340 public void enableCarMode() throws RemoteException {
341 mContext.enforceCallingOrSelfPermission(
342 android.Manifest.permission.ENABLE_CAR_MODE,
343 "Need ENABLE_CAR_MODE permission");
344 if (!mCarModeEnabled) {
345 setCarMode(true);
346 update();
347 }
348 }
349
350 public void setNightMode(int mode) throws RemoteException {
351 if (mCarModeEnabled) {
352 DockObserver.this.setNightMode(mode);
353 }
354 }
355
356 public int getNightMode() throws RemoteException {
357 return mNightMode;
358 }
359 };
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500360}