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 com.android.internal.util.DumpUtils; 39 40 import java.io.FileDescriptor; 41 import java.io.FileNotFoundException; 42 import java.io.FileReader; 43 import java.io.PrintWriter; 44 45 /** 46 * DockObserver monitors for a docking station. 47 */ 48 final class DockObserver extends SystemService { 49 private static final String TAG = "DockObserver"; 50 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 54 private static final int MSG_DOCK_STATE_CHANGED = 0; 55 56 private final PowerManager mPowerManager; 57 private final PowerManager.WakeLock mWakeLock; 58 59 private final Object mLock = new Object(); 60 61 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 70 private final boolean mAllowTheaterModeWakeFromDock; 71 72 public DockObserver(Context context) { 73 super(context); 74 75 mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 76 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 77 mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( 78 com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); 79 80 init(); // set initial status 81 82 mObserver.startObserving(DOCK_UEVENT_MATCH); 83 } 84 85 @Override 86 public void onStart() { 87 publishBinderService(TAG, new BinderService()); 88 } 89 90 @Override 91 public void onBootPhase(int phase) { 92 if (phase == PHASE_ACTIVITY_MANAGER_READY) { 93 synchronized (mLock) { 94 mSystemReady = true; 95 96 // don't bother broadcasting undocked here 97 if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 98 updateLocked(); 99 } 100 } 101 } 102 } 103 104 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); 111 setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim())); 112 mPreviousDockState = mActualDockState; 113 } 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 } 121 } 122 } 123 124 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) { 135 // 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) { 139 mPowerManager.wakeUp(SystemClock.uptimeMillis(), 140 "android.server:DOCK"); 141 } 142 updateLocked(); 143 } 144 } 145 } 146 147 private void updateLocked() { 148 mWakeLock.acquire(); 149 mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); 150 } 151 152 private void handleDockStateChange() { 153 synchronized (mLock) { 154 Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to " 155 + mReportedDockState); 156 final int previousDockState = mPreviousDockState; 157 mPreviousDockState = mReportedDockState; 158 159 // Skip the dock intent if not yet provisioned. 160 final ContentResolver cr = getContext().getContentResolver(); 161 if (Settings.Global.getInt(cr, 162 Settings.Global.DEVICE_PROVISIONED, 0) == 0) { 163 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); 170 intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState); 171 172 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 179 // Play a sound to provide feedback to confirm dock connection. 180 // Particularly useful for flaky contact pins... 181 if ((dockSoundsEnabled) || 182 (accessibilityEnabled && dockSoundsEnabledWhenAccessibility)) { 183 String whichSound = null; 184 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)) { 188 whichSound = Settings.Global.DESK_UNDOCK_SOUND; 189 } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) { 190 whichSound = Settings.Global.CAR_UNDOCK_SOUND; 191 } 192 } else { 193 if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) || 194 (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || 195 (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { 196 whichSound = Settings.Global.DESK_DOCK_SOUND; 197 } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) { 198 whichSound = Settings.Global.CAR_DOCK_SOUND; 199 } 200 } 201 202 if (whichSound != null) { 203 final String soundPath = Settings.Global.getString(cr, whichSound); 204 if (soundPath != null) { 205 final Uri soundUri = Uri.parse("file://" + soundPath); 206 if (soundUri != null) { 207 final Ringtone sfx = RingtoneManager.getRingtone( 208 getContext(), soundUri); 209 if (sfx != null) { 210 sfx.setStreamType(AudioManager.STREAM_SYSTEM); 211 sfx.play(); 212 } 213 } 214 } 215 } 216 } 217 218 // 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. 221 getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); 222 } 223 } 224 225 private final Handler mHandler = new Handler(true /*async*/) { 226 @Override 227 public void handleMessage(Message msg) { 228 switch (msg.what) { 229 case MSG_DOCK_STATE_CHANGED: 230 handleDockStateChange(); 231 mWakeLock.release(); 232 break; 233 } 234 } 235 }; 236 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) { 257 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; 258 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 } 296 } 297