1 /* 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 17 package com.android.server; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.media.AudioManager; 24 import android.media.Ringtone; 25 import android.media.RingtoneManager; 26 import android.net.Uri; 27 import android.os.Binder; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.PowerManager; 31 import android.os.SystemClock; 32 import android.os.UEventObserver; 33 import android.os.UserHandle; 34 import android.provider.Settings; 35 import android.util.Log; 36 import android.util.Slog; 37 38 import java.io.FileDescriptor; 39 import java.io.FileNotFoundException; 40 import java.io.FileReader; 41 import java.io.PrintWriter; 42 43 /** 44 * DockObserver monitors for a docking station. 45 */ 46 final class DockObserver extends SystemService { 47 private static final String TAG = "DockObserver"; 48 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 52 private static final int MSG_DOCK_STATE_CHANGED = 0; 53 54 private final PowerManager mPowerManager; 55 private final PowerManager.WakeLock mWakeLock; 56 57 private final Object mLock = new Object(); 58 59 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 68 private final boolean mAllowTheaterModeWakeFromDock; 69 70 public DockObserver(Context context) { 71 super(context); 72 73 mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 74 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 75 mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( 76 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); 77 78 init(); // set initial status 79 80 mObserver.startObserving(DOCK_UEVENT_MATCH); 81 } 82 83 @Override 84 public void onStart() { 85 publishBinderService(TAG, new BinderService()); 86 } 87 88 @Override 89 public void onBootPhase(int phase) { 90 if (phase == PHASE_ACTIVITY_MANAGER_READY) { 91 synchronized (mLock) { 92 mSystemReady = true; 93 94 // don't bother broadcasting undocked here 95 if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 96 updateLocked(); 97 } 98 } 99 } 100 } 101 102 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); 109 setActualDockStateLocked(Integer.valueOf((new String(buffer, 0, len)).trim())); 110 mPreviousDockState = mActualDockState; 111 } 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 } 119 } 120 } 121 122 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) { 133 // 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) { 137 mPowerManager.wakeUp(SystemClock.uptimeMillis()); 138 } 139 updateLocked(); 140 } 141 } 142 } 143 144 private void updateLocked() { 145 mWakeLock.acquire(); 146 mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); 147 } 148 149 private void handleDockStateChange() { 150 synchronized (mLock) { 151 Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to " 152 + mReportedDockState); 153 final int previousDockState = mPreviousDockState; 154 mPreviousDockState = mReportedDockState; 155 156 // Skip the dock intent if not yet provisioned. 157 final ContentResolver cr = getContext().getContentResolver(); 158 if (Settings.Global.getInt(cr, 159 Settings.Global.DEVICE_PROVISIONED, 0) == 0) { 160 Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); 161 return; 162 } 163 164 // Pack up the values and broadcast them to everyone 165 Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); 166 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 167 intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState); 168 169 // Play a sound to provide feedback to confirm dock connection. 170 // Particularly useful for flaky contact pins... 171 if (Settings.Global.getInt(cr, 172 Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) { 173 String whichSound = null; 174 if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { 175 if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) || 176 (previousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || 177 (previousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { 178 whichSound = Settings.Global.DESK_UNDOCK_SOUND; 179 } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) { 180 whichSound = Settings.Global.CAR_UNDOCK_SOUND; 181 } 182 } else { 183 if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) || 184 (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || 185 (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { 186 whichSound = Settings.Global.DESK_DOCK_SOUND; 187 } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) { 188 whichSound = Settings.Global.CAR_DOCK_SOUND; 189 } 190 } 191 192 if (whichSound != null) { 193 final String soundPath = Settings.Global.getString(cr, whichSound); 194 if (soundPath != null) { 195 final Uri soundUri = Uri.parse("file://" + soundPath); 196 if (soundUri != null) { 197 final Ringtone sfx = RingtoneManager.getRingtone( 198 getContext(), soundUri); 199 if (sfx != null) { 200 sfx.setStreamType(AudioManager.STREAM_SYSTEM); 201 sfx.play(); 202 } 203 } 204 } 205 } 206 } 207 208 // Send the dock event intent. 209 // There are many components in the system watching for this so as to 210 // adjust audio routing, screen orientation, etc. 211 getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); 212 } 213 } 214 215 private final Handler mHandler = new Handler(true /*async*/) { 216 @Override 217 public void handleMessage(Message msg) { 218 switch (msg.what) { 219 case MSG_DOCK_STATE_CHANGED: 220 handleDockStateChange(); 221 mWakeLock.release(); 222 break; 223 } 224 } 225 }; 226 227 private final UEventObserver mObserver = new UEventObserver() { 228 @Override 229 public void onUEvent(UEventObserver.UEvent event) { 230 if (Log.isLoggable(TAG, Log.VERBOSE)) { 231 Slog.v(TAG, "Dock UEVENT: " + event.toString()); 232 } 233 234 try { 235 synchronized (mLock) { 236 setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE"))); 237 } 238 } catch (NumberFormatException e) { 239 Slog.e(TAG, "Could not parse switch state from event " + event); 240 } 241 } 242 }; 243 244 private final class BinderService extends Binder { 245 @Override 246 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 247 if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 248 != PackageManager.PERMISSION_GRANTED) { 249 pw.println("Permission Denial: can't dump dock observer service from from pid=" 250 + Binder.getCallingPid() 251 + ", uid=" + Binder.getCallingUid()); 252 return; 253 } 254 255 final long ident = Binder.clearCallingIdentity(); 256 try { 257 synchronized (mLock) { 258 if (args == null || args.length == 0 || "-a".equals(args[0])) { 259 pw.println("Current Dock Observer Service state:"); 260 if (mUpdatesStopped) { 261 pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); 262 } 263 pw.println(" reported state: " + mReportedDockState); 264 pw.println(" previous state: " + mPreviousDockState); 265 pw.println(" actual state: " + mActualDockState); 266 } else if (args.length == 3 && "set".equals(args[0])) { 267 String key = args[1]; 268 String value = args[2]; 269 try { 270 if ("state".equals(key)) { 271 mUpdatesStopped = true; 272 setDockStateLocked(Integer.parseInt(value)); 273 } else { 274 pw.println("Unknown set option: " + key); 275 } 276 } catch (NumberFormatException ex) { 277 pw.println("Bad value: " + value); 278 } 279 } else if (args.length == 1 && "reset".equals(args[0])) { 280 mUpdatesStopped = false; 281 setDockStateLocked(mActualDockState); 282 } else { 283 pw.println("Dump current dock state, or:"); 284 pw.println(" set state <value>"); 285 pw.println(" reset"); 286 } 287 } 288 } finally { 289 Binder.restoreCallingIdentity(ident); 290 } 291 } 292 } 293 } 294