blob: 122074ba4dac33982d266ac636e4644ac7782590 [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 Brown010e5612014-05-29 17:48:33 -070038import java.io.FileDescriptor;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050039import java.io.FileNotFoundException;
Jaikumar Ganesh3fbf7b62009-12-02 17:28:38 -080040import java.io.FileReader;
Jeff Brown010e5612014-05-29 17:48:33 -070041import java.io.PrintWriter;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050042
43/**
Jeff Brown010e5612014-05-29 17:48:33 -070044 * DockObserver monitors for a docking station.
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050045 */
Jeff Brown010e5612014-05-29 17:48:33 -070046final class DockObserver extends SystemService {
47 private static final String TAG = "DockObserver";
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050048
49 private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
50 private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
51
Jeff Brown008b1762012-08-20 20:15:34 -070052 private static final int MSG_DOCK_STATE_CHANGED = 0;
53
Jeff Brown62c82e42012-09-26 01:30:41 -070054 private final PowerManager mPowerManager;
55 private final PowerManager.WakeLock mWakeLock;
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050056
Jeff Brown010e5612014-05-29 17:48:33 -070057 private final Object mLock = new Object();
Tobias Haamel27b28b32010-02-09 23:09:17 +010058
Jeff Brown010e5612014-05-29 17:48:33 -070059 private boolean mSystemReady;
60
61 private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
62
63 private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
64 private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
65
66 private boolean mUpdatesStopped;
67
Bryce Lee584a4452014-10-21 15:55:55 -070068 private final boolean mAllowTheaterModeWakeFromDock;
69
Jeff Brown010e5612014-05-29 17:48:33 -070070 public DockObserver(Context context) {
71 super(context);
72
73 mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
Jeff Brown62c82e42012-09-26 01:30:41 -070074 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
Bryce Lee584a4452014-10-21 15:55:55 -070075 mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
76 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
Jeff Brown62c82e42012-09-26 01:30:41 -070077
78 init(); // set initial status
Jeff Brown010e5612014-05-29 17:48:33 -070079
80 mObserver.startObserving(DOCK_UEVENT_MATCH);
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050081 }
82
83 @Override
Jeff Brown010e5612014-05-29 17:48:33 -070084 public void onStart() {
85 publishBinderService(TAG, new BinderService());
86 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050087
Jeff Brown010e5612014-05-29 17:48:33 -070088 @Override
89 public void onBootPhase(int phase) {
90 if (phase == PHASE_ACTIVITY_MANAGER_READY) {
91 synchronized (mLock) {
92 mSystemReady = true;
Jeff Brown62c82e42012-09-26 01:30:41 -070093
Jeff Brown010e5612014-05-29 17:48:33 -070094 // don't bother broadcasting undocked here
95 if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
96 updateLocked();
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070097 }
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -070098 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -050099 }
100 }
101
Jeff Brown008b1762012-08-20 20:15:34 -0700102 private void init() {
103 synchronized (mLock) {
104 try {
105 char[] buffer = new char[1024];
106 FileReader file = new FileReader(DOCK_STATE_PATH);
107 try {
108 int len = file.read(buffer, 0, 1024);
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100109 setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
Jeff Brown010e5612014-05-29 17:48:33 -0700110 mPreviousDockState = mActualDockState;
Jeff Brown008b1762012-08-20 20:15:34 -0700111 } finally {
112 file.close();
113 }
114 } catch (FileNotFoundException e) {
115 Slog.w(TAG, "This kernel does not have dock station support");
116 } catch (Exception e) {
117 Slog.e(TAG, "" , e);
118 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500119 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500120 }
121
Jeff Brown010e5612014-05-29 17:48:33 -0700122 private void setActualDockStateLocked(int newState) {
123 mActualDockState = newState;
124 if (!mUpdatesStopped) {
125 setDockStateLocked(newState);
126 }
127 }
128
129 private void setDockStateLocked(int newState) {
130 if (newState != mReportedDockState) {
131 mReportedDockState = newState;
132 if (mSystemReady) {
Bryce Lee584a4452014-10-21 15:55:55 -0700133 // Wake up immediately when docked or undocked except in theater mode.
134 if (mAllowTheaterModeWakeFromDock
135 || Settings.Global.getInt(getContext().getContentResolver(),
136 Settings.Global.THEATER_MODE_ON, 0) == 0) {
Dianne Hackborn280a64e2015-07-13 14:48:08 -0700137 mPowerManager.wakeUp(SystemClock.uptimeMillis(),
138 "android.server:DOCK");
Bryce Lee584a4452014-10-21 15:55:55 -0700139 }
Jeff Brown008b1762012-08-20 20:15:34 -0700140 updateLocked();
Mike Lockwoodd0e82ce2009-08-27 16:19:07 -0700141 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500142 }
143 }
144
Jeff Brown008b1762012-08-20 20:15:34 -0700145 private void updateLocked() {
Jeff Brown62c82e42012-09-26 01:30:41 -0700146 mWakeLock.acquire();
Jeff Brown008b1762012-08-20 20:15:34 -0700147 mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
148 }
149
150 private void handleDockStateChange() {
151 synchronized (mLock) {
Jeff Brown010e5612014-05-29 17:48:33 -0700152 Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to "
153 + mReportedDockState);
154 final int previousDockState = mPreviousDockState;
155 mPreviousDockState = mReportedDockState;
Jeff Brown008b1762012-08-20 20:15:34 -0700156
Jeff Brown62c82e42012-09-26 01:30:41 -0700157 // Skip the dock intent if not yet provisioned.
Jeff Brown010e5612014-05-29 17:48:33 -0700158 final ContentResolver cr = getContext().getContentResolver();
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700159 if (Settings.Global.getInt(cr,
160 Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
Jeff Brown008b1762012-08-20 20:15:34 -0700161 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
162 return;
163 }
164
165 // Pack up the values and broadcast them to everyone
166 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
167 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
Jeff Brown010e5612014-05-29 17:48:33 -0700168 intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState);
Jeff Brown008b1762012-08-20 20:15:34 -0700169
Vinod Krishnancf11cea2016-10-20 22:57:02 -0700170 boolean dockSoundsEnabled = Settings.Global.getInt(cr,
171 Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1;
172 boolean dockSoundsEnabledWhenAccessibility = Settings.Global.getInt(cr,
173 Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY, 1) == 1;
174 boolean accessibilityEnabled = Settings.Secure.getInt(cr,
175 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
176
Jeff Brown62c82e42012-09-26 01:30:41 -0700177 // Play a sound to provide feedback to confirm dock connection.
178 // Particularly useful for flaky contact pins...
Vinod Krishnancf11cea2016-10-20 22:57:02 -0700179 if ((dockSoundsEnabled) ||
180 (accessibilityEnabled && dockSoundsEnabledWhenAccessibility)) {
Jeff Brown008b1762012-08-20 20:15:34 -0700181 String whichSound = null;
Jeff Brown010e5612014-05-29 17:48:33 -0700182 if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
183 if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
184 (previousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
185 (previousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700186 whichSound = Settings.Global.DESK_UNDOCK_SOUND;
Jeff Brown010e5612014-05-29 17:48:33 -0700187 } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700188 whichSound = Settings.Global.CAR_UNDOCK_SOUND;
Jeff Brown008b1762012-08-20 20:15:34 -0700189 }
190 } else {
Jeff Brown010e5612014-05-29 17:48:33 -0700191 if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
192 (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
193 (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700194 whichSound = Settings.Global.DESK_DOCK_SOUND;
Jeff Brown010e5612014-05-29 17:48:33 -0700195 } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700196 whichSound = Settings.Global.CAR_DOCK_SOUND;
Jeff Brown008b1762012-08-20 20:15:34 -0700197 }
198 }
199
200 if (whichSound != null) {
Jeff Brownbf6f6f92012-09-25 15:03:20 -0700201 final String soundPath = Settings.Global.getString(cr, whichSound);
Jeff Brown008b1762012-08-20 20:15:34 -0700202 if (soundPath != null) {
203 final Uri soundUri = Uri.parse("file://" + soundPath);
204 if (soundUri != null) {
Jeff Brown010e5612014-05-29 17:48:33 -0700205 final Ringtone sfx = RingtoneManager.getRingtone(
206 getContext(), soundUri);
Jeff Brown008b1762012-08-20 20:15:34 -0700207 if (sfx != null) {
208 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
209 sfx.play();
210 }
211 }
212 }
213 }
214 }
215
Jeff Brown62c82e42012-09-26 01:30:41 -0700216 // Send the dock event intent.
217 // There are many components in the system watching for this so as to
218 // adjust audio routing, screen orientation, etc.
Jeff Brown010e5612014-05-29 17:48:33 -0700219 getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
Jeff Brown008b1762012-08-20 20:15:34 -0700220 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500221 }
222
Jeff Browna2910d02012-08-25 12:29:46 -0700223 private final Handler mHandler = new Handler(true /*async*/) {
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500224 @Override
225 public void handleMessage(Message msg) {
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100226 switch (msg.what) {
Jeff Brown008b1762012-08-20 20:15:34 -0700227 case MSG_DOCK_STATE_CHANGED:
228 handleDockStateChange();
Prashant Malani74506142014-08-06 16:49:53 -0700229 mWakeLock.release();
Bernd Holzheybfca3a02010-02-10 17:39:51 +0100230 break;
231 }
232 }
Tobias Haamel27b28b32010-02-09 23:09:17 +0100233 };
Jeff Brown010e5612014-05-29 17:48:33 -0700234
235 private final UEventObserver mObserver = new UEventObserver() {
236 @Override
237 public void onUEvent(UEventObserver.UEvent event) {
238 if (Log.isLoggable(TAG, Log.VERBOSE)) {
239 Slog.v(TAG, "Dock UEVENT: " + event.toString());
240 }
241
242 try {
243 synchronized (mLock) {
244 setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
245 }
246 } catch (NumberFormatException e) {
247 Slog.e(TAG, "Could not parse switch state from event " + event);
248 }
249 }
250 };
251
252 private final class BinderService extends Binder {
253 @Override
254 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
255 if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
256 != PackageManager.PERMISSION_GRANTED) {
257 pw.println("Permission Denial: can't dump dock observer service from from pid="
258 + Binder.getCallingPid()
259 + ", uid=" + Binder.getCallingUid());
260 return;
261 }
262
263 final long ident = Binder.clearCallingIdentity();
264 try {
265 synchronized (mLock) {
266 if (args == null || args.length == 0 || "-a".equals(args[0])) {
267 pw.println("Current Dock Observer Service state:");
268 if (mUpdatesStopped) {
269 pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
270 }
271 pw.println(" reported state: " + mReportedDockState);
272 pw.println(" previous state: " + mPreviousDockState);
273 pw.println(" actual state: " + mActualDockState);
274 } else if (args.length == 3 && "set".equals(args[0])) {
275 String key = args[1];
276 String value = args[2];
277 try {
278 if ("state".equals(key)) {
279 mUpdatesStopped = true;
280 setDockStateLocked(Integer.parseInt(value));
281 } else {
282 pw.println("Unknown set option: " + key);
283 }
284 } catch (NumberFormatException ex) {
285 pw.println("Bad value: " + value);
286 }
287 } else if (args.length == 1 && "reset".equals(args[0])) {
288 mUpdatesStopped = false;
289 setDockStateLocked(mActualDockState);
290 } else {
291 pw.println("Dump current dock state, or:");
292 pw.println(" set state <value>");
293 pw.println(" reset");
294 }
295 }
296 } finally {
297 Binder.restoreCallingIdentity(ident);
298 }
299 }
300 }
Dan Murphyc9f4eaf2009-08-12 15:15:43 -0500301}