blob: e5a7b4e4ee23dbcbacaebde40b63189e2b3da529 [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
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050019import android.content.ContentResolver;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050020import android.content.Context;
21import android.content.Intent;
Jeff Brown010e5612014-05-29 17:48:33 -070022import android.content.pm.PackageManager;
Daniel Sandlerec2c88d2010-02-20 01:04:57 -050023import android.media.AudioManager;
Daniel Sandler0e9d2af2010-01-25 11:33:03 -050024import android.media.Ringtone;
25import android.media.RingtoneManager;
26import android.net.Uri;
Jeff Brown010e5612014-05-29 17:48:33 -070027import android.os.Binder;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050028import android.os.Handler;
29import android.os.Message;
Jeff Brown96307042012-07-27 15:51:34 -070030import android.os.PowerManager;
Ken Schultzf02c0742009-09-10 18:37:37 -050031import android.os.SystemClock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050032import android.os.UEventObserver;
Dianne Hackborn5ac72a22012-08-29 18:32:08 -070033import android.os.UserHandle;
Dianne Hackborn49493342009-10-02 10:44:41 -070034import android.provider.Settings;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050035import android.util.Log;
Joe Onorato8a9b2202010-02-26 18:56:32 -080036import android.util.Slog;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050037
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060038import com.android.internal.util.DumpUtils;
39
Jeff Brown010e5612014-05-29 17:48:33 -070040import java.io.FileDescriptor;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050041import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080042import java.io.FileReader;
Jeff Brown010e5612014-05-29 17:48:33 -070043import java.io.PrintWriter;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050044
45/**
Jeff Brown010e5612014-05-29 17:48:33 -070046 * DockObserver monitors for a docking station.
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050047 */
Jeff Brown010e5612014-05-29 17:48:33 -070048final class DockObserver extends SystemService {
49 private static final String TAG = "DockObserver";
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050050
51 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
52 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
53
Jeff Brown008b1762012-08-20 20:15:34 -070054 private static final int MSG_DOCK_STATE_CHANGED = 0;
55
Jeff Brown62c82e42012-09-26 01:30:41 -070056 private final PowerManager mPowerManager;
57 private final PowerManager.WakeLock mWakeLock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050058
Jeff Brown010e5612014-05-29 17:48:33 -070059 private final Object mLock = new Object();
Tobias Haamel27b28b32010-02-09 23:09:17 +010060
Jeff Brown010e5612014-05-29 17:48:33 -070061 private boolean mSystemReady;
62
63 private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
64
65 private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
66 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
67
68 private boolean mUpdatesStopped;
69
Bryce Lee584a4452014-10-21 15:55:55 -070070 private final boolean mAllowTheaterModeWakeFromDock;
71
Jeff Brown010e5612014-05-29 17:48:33 -070072 public DockObserver(Context context) {
73 super(context);
74
75 mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Jeff Brown62c82e42012-09-26 01:30:41 -070076 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
Bryce Lee584a4452014-10-21 15:55:55 -070077 mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
78 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
Jeff Brown62c82e42012-09-26 01:30:41 -070079
80 init(); // set initial status
Jeff Brown010e5612014-05-29 17:48:33 -070081
82 mObserver.startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050083 }
84
85 @Override
Jeff Brown010e5612014-05-29 17:48:33 -070086 public void onStart() {
87 publishBinderService(TAG, new BinderService());
88 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050089
Jeff Brown010e5612014-05-29 17:48:33 -070090 @Override
91 public void onBootPhase(int phase) {
92 if (phase == PHASE_ACTIVITY_MANAGER_READY) {
93 synchronized (mLock) {
94 mSystemReady = true;
Jeff Brown62c82e42012-09-26 01:30:41 -070095
Jeff Brown010e5612014-05-29 17:48:33 -070096 // don't bother broadcasting undocked here
97 if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
98 updateLocked();
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070099 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700100 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500101 }
102 }
103
Jeff Brown008b1762012-08-20 20:15:34 -0700104 private void init() {
105 synchronized (mLock) {
106 try {
107 char[] buffer = new char[1024];
108 FileReader file = new FileReader(DOCK_STATE_PATH);
109 try {
110 int len = file.read(buffer, 0, 1024);
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100111 setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
Jeff Brown010e5612014-05-29 17:48:33 -0700112 mPreviousDockState = mActualDockState;
Jeff Brown008b1762012-08-20 20:15:34 -0700113 } finally {
114 file.close();
115 }
116 } catch (FileNotFoundException e) {
117 Slog.w(TAG, "This kernel does not have dock station support");
118 } catch (Exception e) {
119 Slog.e(TAG, "" , e);
120 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500121 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500122 }
123
Jeff Brown010e5612014-05-29 17:48:33 -0700124 private void setActualDockStateLocked(int newState) {
125 mActualDockState = newState;
126 if (!mUpdatesStopped) {
127 setDockStateLocked(newState);
128 }
129 }
130
131 private void setDockStateLocked(int newState) {
132 if (newState != mReportedDockState) {
133 mReportedDockState = newState;
134 if (mSystemReady) {
Bryce Lee584a4452014-10-21 15:55:55 -0700135 // Wake up immediately when docked or undocked except in theater mode.
136 if (mAllowTheaterModeWakeFromDock
137 || Settings.Global.getInt(getContext().getContentResolver(),
138 Settings.Global.THEATER_MODE_ON, 0) == 0) {
Dianne Hackborn280a64e2015-07-13 14:48:08 -0700139 mPowerManager.wakeUp(SystemClock.uptimeMillis(),
140 "android.server:DOCK");
Bryce Lee584a4452014-10-21 15:55:55 -0700141 }
Jeff Brown008b1762012-08-20 20:15:34 -0700142 updateLocked();
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700143 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500144 }
145 }
146
Jeff Brown008b1762012-08-20 20:15:34 -0700147 private void updateLocked() {
Jeff Brown62c82e42012-09-26 01:30:41 -0700148 mWakeLock.acquire();
Jeff Brown008b1762012-08-20 20:15:34 -0700149 mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
150 }
151
152 private void handleDockStateChange() {
153 synchronized (mLock) {
Jeff Brown010e5612014-05-29 17:48:33 -0700154 Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to "
155 + mReportedDockState);
156 final int previousDockState = mPreviousDockState;
157 mPreviousDockState = mReportedDockState;
Jeff Brown008b1762012-08-20 20:15:34 -0700158
Jeff Brown62c82e42012-09-26 01:30:41 -0700159 // Skip the dock intent if not yet provisioned.
Jeff Brown010e5612014-05-29 17:48:33 -0700160 final ContentResolver cr = getContext().getContentResolver();
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700161 if (Settings.Global.getInt(cr,
162 Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
Jeff Brown008b1762012-08-20 20:15:34 -0700163 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
164 return;
165 }
166
167 // Pack up the values and broadcast them to everyone
168 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
169 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
Jeff Brown010e5612014-05-29 17:48:33 -0700170 intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState);
Jeff Brown008b1762012-08-20 20:15:34 -0700171
Vinod Krishnancf11cea2016-10-20 22:57:02 -0700172 boolean dockSoundsEnabled = Settings.Global.getInt(cr,
173 Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1;
174 boolean dockSoundsEnabledWhenAccessibility = Settings.Global.getInt(cr,
175 Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY, 1) == 1;
176 boolean accessibilityEnabled = Settings.Secure.getInt(cr,
177 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
178
Jeff Brown62c82e42012-09-26 01:30:41 -0700179 // Play a sound to provide feedback to confirm dock connection.
180 // Particularly useful for flaky contact pins...
Vinod Krishnancf11cea2016-10-20 22:57:02 -0700181 if ((dockSoundsEnabled) ||
182 (accessibilityEnabled && dockSoundsEnabledWhenAccessibility)) {
Jeff Brown008b1762012-08-20 20:15:34 -0700183 String whichSound = null;
Jeff Brown010e5612014-05-29 17:48:33 -0700184 if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
185 if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
186 (previousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
187 (previousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700188 whichSound = Settings.Global.DESK_UNDOCK_SOUND;
Jeff Brown010e5612014-05-29 17:48:33 -0700189 } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700190 whichSound = Settings.Global.CAR_UNDOCK_SOUND;
Jeff Brown008b1762012-08-20 20:15:34 -0700191 }
192 } else {
Jeff Brown010e5612014-05-29 17:48:33 -0700193 if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
194 (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
195 (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700196 whichSound = Settings.Global.DESK_DOCK_SOUND;
Jeff Brown010e5612014-05-29 17:48:33 -0700197 } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700198 whichSound = Settings.Global.CAR_DOCK_SOUND;
Jeff Brown008b1762012-08-20 20:15:34 -0700199 }
200 }
201
202 if (whichSound != null) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700203 final String soundPath = Settings.Global.getString(cr, whichSound);
Jeff Brown008b1762012-08-20 20:15:34 -0700204 if (soundPath != null) {
205 final Uri soundUri = Uri.parse("file://" + soundPath);
206 if (soundUri != null) {
Jeff Brown010e5612014-05-29 17:48:33 -0700207 final Ringtone sfx = RingtoneManager.getRingtone(
208 getContext(), soundUri);
Jeff Brown008b1762012-08-20 20:15:34 -0700209 if (sfx != null) {
210 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
211 sfx.play();
212 }
213 }
214 }
215 }
216 }
217
Jeff Brown62c82e42012-09-26 01:30:41 -0700218 // Send the dock event intent.
219 // There are many components in the system watching for this so as to
220 // adjust audio routing, screen orientation, etc.
Jeff Brown010e5612014-05-29 17:48:33 -0700221 getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
Jeff Brown008b1762012-08-20 20:15:34 -0700222 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500223 }
224
Jeff Browna2910d02012-08-25 12:29:46 -0700225 private final Handler mHandler = new Handler(true /*async*/) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500226 @Override
227 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100228 switch (msg.what) {
Jeff Brown008b1762012-08-20 20:15:34 -0700229 case MSG_DOCK_STATE_CHANGED:
230 handleDockStateChange();
Prashant Malani74506142014-08-06 16:49:53 -0700231 mWakeLock.release();
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100232 break;
233 }
234 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100235 };
Jeff Brown010e5612014-05-29 17:48:33 -0700236
237 private final UEventObserver mObserver = new UEventObserver() {
238 @Override
239 public void onUEvent(UEventObserver.UEvent event) {
240 if (Log.isLoggable(TAG, Log.VERBOSE)) {
241 Slog.v(TAG, "Dock UEVENT: " + event.toString());
242 }
243
244 try {
245 synchronized (mLock) {
246 setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
247 }
248 } catch (NumberFormatException e) {
249 Slog.e(TAG, "Could not parse switch state from event " + event);
250 }
251 }
252 };
253
254 private final class BinderService extends Binder {
255 @Override
256 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600257 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
Jeff Brown010e5612014-05-29 17:48:33 -0700258 final long ident = Binder.clearCallingIdentity();
259 try {
260 synchronized (mLock) {
261 if (args == null || args.length == 0 || "-a".equals(args[0])) {
262 pw.println("Current Dock Observer Service state:");
263 if (mUpdatesStopped) {
264 pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
265 }
266 pw.println(" reported state: " + mReportedDockState);
267 pw.println(" previous state: " + mPreviousDockState);
268 pw.println(" actual state: " + mActualDockState);
269 } else if (args.length == 3 && "set".equals(args[0])) {
270 String key = args[1];
271 String value = args[2];
272 try {
273 if ("state".equals(key)) {
274 mUpdatesStopped = true;
275 setDockStateLocked(Integer.parseInt(value));
276 } else {
277 pw.println("Unknown set option: " + key);
278 }
279 } catch (NumberFormatException ex) {
280 pw.println("Bad value: " + value);
281 }
282 } else if (args.length == 1 && "reset".equals(args[0])) {
283 mUpdatesStopped = false;
284 setDockStateLocked(mActualDockState);
285 } else {
286 pw.println("Dump current dock state, or:");
287 pw.println(" set state <value>");
288 pw.println(" reset");
289 }
290 }
291 } finally {
292 Binder.restoreCallingIdentity(ident);
293 }
294 }
295 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500296}