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
17package com.android.server;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.media.AudioManager;
23import android.media.Ringtone;
24import android.media.RingtoneManager;
25import android.net.Uri;
26import android.os.Handler;
27import android.os.Message;
28import android.os.PowerManager;
29import android.os.SystemClock;
30import android.os.UEventObserver;
31import android.os.UserHandle;
32import android.provider.Settings;
33import android.util.Log;
34import android.util.Slog;
35
36import java.io.FileNotFoundException;
37import java.io.FileReader;
38
39/**
40 * <p>DockObserver monitors for a docking station.
41 */
42final class DockObserver extends UEventObserver {
43    private static final String TAG = DockObserver.class.getSimpleName();
44
45    private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
46    private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
47
48    private static final int MSG_DOCK_STATE_CHANGED = 0;
49
50    private final Object mLock = new Object();
51
52    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
53    private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
54
55    private boolean mSystemReady;
56
57    private final Context mContext;
58    private final PowerManager mPowerManager;
59    private final PowerManager.WakeLock mWakeLock;
60
61    public DockObserver(Context context) {
62        mContext = context;
63
64        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
65        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
66
67        init();  // set initial status
68        startObserving(DOCK_UEVENT_MATCH);
69    }
70
71    @Override
72    public void onUEvent(UEventObserver.UEvent event) {
73        if (Log.isLoggable(TAG, Log.VERBOSE)) {
74            Slog.v(TAG, "Dock UEVENT: " + event.toString());
75        }
76
77        synchronized (mLock) {
78            try {
79                int newState = Integer.parseInt(event.get("SWITCH_STATE"));
80                if (newState != mDockState) {
81                    mPreviousDockState = mDockState;
82                    mDockState = newState;
83                    if (mSystemReady) {
84                        // Wake up immediately when docked or undocked.
85                        mPowerManager.wakeUp(SystemClock.uptimeMillis());
86
87                        updateLocked();
88                    }
89                }
90            } catch (NumberFormatException e) {
91                Slog.e(TAG, "Could not parse switch state from event " + event);
92            }
93        }
94    }
95
96    private void init() {
97        synchronized (mLock) {
98            try {
99                char[] buffer = new char[1024];
100                FileReader file = new FileReader(DOCK_STATE_PATH);
101                try {
102                    int len = file.read(buffer, 0, 1024);
103                    mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
104                    mPreviousDockState = mDockState;
105                } finally {
106                    file.close();
107                }
108            } catch (FileNotFoundException e) {
109                Slog.w(TAG, "This kernel does not have dock station support");
110            } catch (Exception e) {
111                Slog.e(TAG, "" , e);
112            }
113        }
114    }
115
116    void systemReady() {
117        synchronized (mLock) {
118            // don't bother broadcasting undocked here
119            if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
120                updateLocked();
121            }
122            mSystemReady = true;
123        }
124    }
125
126    private void updateLocked() {
127        mWakeLock.acquire();
128        mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
129    }
130
131    private void handleDockStateChange() {
132        synchronized (mLock) {
133            Slog.i(TAG, "Dock state changed: " + mDockState);
134
135            // Skip the dock intent if not yet provisioned.
136            final ContentResolver cr = mContext.getContentResolver();
137            if (Settings.Global.getInt(cr,
138                    Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
139                Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
140                return;
141            }
142
143            // Pack up the values and broadcast them to everyone
144            Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
145            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
146            intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
147
148            // Play a sound to provide feedback to confirm dock connection.
149            // Particularly useful for flaky contact pins...
150            if (Settings.Global.getInt(cr,
151                    Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) {
152                String whichSound = null;
153                if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
154                    if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
155                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
156                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
157                        whichSound = Settings.Global.DESK_UNDOCK_SOUND;
158                    } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
159                        whichSound = Settings.Global.CAR_UNDOCK_SOUND;
160                    }
161                } else {
162                    if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
163                        (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
164                        (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
165                        whichSound = Settings.Global.DESK_DOCK_SOUND;
166                    } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
167                        whichSound = Settings.Global.CAR_DOCK_SOUND;
168                    }
169                }
170
171                if (whichSound != null) {
172                    final String soundPath = Settings.Global.getString(cr, whichSound);
173                    if (soundPath != null) {
174                        final Uri soundUri = Uri.parse("file://" + soundPath);
175                        if (soundUri != null) {
176                            final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
177                            if (sfx != null) {
178                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
179                                sfx.play();
180                            }
181                        }
182                    }
183                }
184            }
185
186            // Send the dock event intent.
187            // There are many components in the system watching for this so as to
188            // adjust audio routing, screen orientation, etc.
189            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
190
191            // Release the wake lock that was acquired when the message was posted.
192            mWakeLock.release();
193        }
194    }
195
196    private final Handler mHandler = new Handler(true /*async*/) {
197        @Override
198        public void handleMessage(Message msg) {
199            switch (msg.what) {
200                case MSG_DOCK_STATE_CHANGED:
201                    handleDockStateChange();
202                    break;
203            }
204        }
205    };
206}
207