1c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy/*
2c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * Copyright (C) 2008 The Android Open Source Project
3c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy *
4c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * Licensed under the Apache License, Version 2.0 (the "License");
5c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * you may not use this file except in compliance with the License.
6c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * You may obtain a copy of the License at
7c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy *
8c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy *      http://www.apache.org/licenses/LICENSE-2.0
9c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy *
10c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * Unless required by applicable law or agreed to in writing, software
11c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * distributed under the License is distributed on an "AS IS" BASIS,
12c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * See the License for the specific language governing permissions and
14c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * limitations under the License.
15c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy */
16c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
17c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphypackage com.android.server;
18c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
190e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandlerimport android.content.ContentResolver;
20c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.content.Context;
21c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.content.Intent;
22ec2c88d835dac156be0d14bd2b73cf3825e2e164Daniel Sandlerimport android.media.AudioManager;
230e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandlerimport android.media.Ringtone;
240e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandlerimport android.media.RingtoneManager;
250e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandlerimport android.net.Uri;
26c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.os.Handler;
27c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.os.Message;
289630704ed3b265f008a8f64ec60a33cf9dcd3345Jeff Brownimport android.os.PowerManager;
29f02c0740d3039977149773604a229c9f76c8121dKen Schultzimport android.os.SystemClock;
30c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.os.UEventObserver;
315ac72a29593ab9a20337a2225df52bdf4754be02Dianne Hackbornimport android.os.UserHandle;
324949334d2064b32118b04c231dd622804a9df1cbDianne Hackbornimport android.provider.Settings;
33c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport android.util.Log;
348a9b22056b13477f59df934928c00c58b5871c95Joe Onoratoimport android.util.Slog;
35c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
36c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphyimport java.io.FileNotFoundException;
373fbf7b62bb48b10316353087d09cc3720af00642Jaikumar Ganeshimport java.io.FileReader;
38c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
39c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy/**
40c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy * <p>DockObserver monitors for a docking station.
41c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy */
42008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brownfinal class DockObserver extends UEventObserver {
43c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    private static final String TAG = DockObserver.class.getSimpleName();
44c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
45c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
46c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
47c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
48008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    private static final int MSG_DOCK_STATE_CHANGED = 0;
49008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown
50008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    private final Object mLock = new Object();
51bfca3a0056a42bde672c8ade83f13f139149f756Bernd Holzhey
52d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
530e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler    private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
540e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler
55d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood    private boolean mSystemReady;
56c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
57c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    private final Context mContext;
5862c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown    private final PowerManager mPowerManager;
5962c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown    private final PowerManager.WakeLock mWakeLock;
60c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
619630704ed3b265f008a8f64ec60a33cf9dcd3345Jeff Brown    public DockObserver(Context context) {
62c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        mContext = context;
6327b28b3f62bd3b54fa13acd5d035940b9be464f3Tobias Haamel
6462c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
6562c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
6662c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown
6762c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown        init();  // set initial status
68d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood        startObserving(DOCK_UEVENT_MATCH);
69c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    }
70c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
71c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    @Override
72c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    public void onUEvent(UEventObserver.UEvent event) {
73c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        if (Log.isLoggable(TAG, Log.VERBOSE)) {
748a9b22056b13477f59df934928c00c58b5871c95Joe Onorato            Slog.v(TAG, "Dock UEVENT: " + event.toString());
75c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        }
76c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
77008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        synchronized (mLock) {
78d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            try {
79d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                int newState = Integer.parseInt(event.get("SWITCH_STATE"));
80d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                if (newState != mDockState) {
810e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler                    mPreviousDockState = mDockState;
82d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                    mDockState = newState;
83d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                    if (mSystemReady) {
8462c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown                        // Wake up immediately when docked or undocked.
8562c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown                        mPowerManager.wakeUp(SystemClock.uptimeMillis());
8662c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown
87008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        updateLocked();
88d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                    }
89d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood                }
90d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            } catch (NumberFormatException e) {
918a9b22056b13477f59df934928c00c58b5871c95Joe Onorato                Slog.e(TAG, "Could not parse switch state from event " + event);
92d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            }
93c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        }
94c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    }
95c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
96008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    private void init() {
97008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        synchronized (mLock) {
98008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            try {
99008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                char[] buffer = new char[1024];
100008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                FileReader file = new FileReader(DOCK_STATE_PATH);
101008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                try {
102008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    int len = file.read(buffer, 0, 1024);
103008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
104008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    mPreviousDockState = mDockState;
105008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                } finally {
106008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    file.close();
107008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                }
108008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            } catch (FileNotFoundException e) {
109008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                Slog.w(TAG, "This kernel does not have dock station support");
110008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            } catch (Exception e) {
111008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                Slog.e(TAG, "" , e);
112008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            }
113c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        }
114c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    }
115c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
116d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood    void systemReady() {
117008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        synchronized (mLock) {
118d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            // don't bother broadcasting undocked here
119d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
120008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                updateLocked();
121d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            }
122d0e82cec71442485d9cc952e5e16165d9d98b16eMike Lockwood            mSystemReady = true;
123c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy        }
124c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    }
125c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
126008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    private void updateLocked() {
12762c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown        mWakeLock.acquire();
128008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
129c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy    }
130c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy
131008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    private void handleDockStateChange() {
132008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        synchronized (mLock) {
133008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            Slog.i(TAG, "Dock state changed: " + mDockState);
134786546eaccfc7a284b90b077f505cf42206c6f5fJohn Spurlock
13562c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // Skip the dock intent if not yet provisioned.
136008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            final ContentResolver cr = mContext.getContentResolver();
137bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown            if (Settings.Global.getInt(cr,
138bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                    Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
139008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
140008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                return;
141008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            }
1420e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler
143008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            // Pack up the values and broadcast them to everyone
144008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
145008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
146008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
147008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown
14862c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // Play a sound to provide feedback to confirm dock connection.
14962c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // Particularly useful for flaky contact pins...
150bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown            if (Settings.Global.getInt(cr,
151bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                    Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) {
152008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                String whichSound = null;
153008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
154008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
155008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
156008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
157bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                        whichSound = Settings.Global.DESK_UNDOCK_SOUND;
158008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
159bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                        whichSound = Settings.Global.CAR_UNDOCK_SOUND;
160008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    }
161008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                } else {
162008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
163008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
164008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
165bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                        whichSound = Settings.Global.DESK_DOCK_SOUND;
166008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
167bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                        whichSound = Settings.Global.CAR_DOCK_SOUND;
168008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    }
169008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                }
1700e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler
171008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                if (whichSound != null) {
172bf6f6f9de72c9fd15e6bda9f228c05a9b37d6324Jeff Brown                    final String soundPath = Settings.Global.getString(cr, whichSound);
173008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    if (soundPath != null) {
174008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        final Uri soundUri = Uri.parse("file://" + soundPath);
175008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                        if (soundUri != null) {
176008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                            final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
177008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                            if (sfx != null) {
178008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
179008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                                sfx.play();
1800e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler                            }
1810e9d2af2d60b381ba52d0c25e583b3d2a9906051Daniel Sandler                        }
182008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    }
183008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                }
184008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            }
185bfca3a0056a42bde672c8ade83f13f139149f756Bernd Holzhey
18662c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // Send the dock event intent.
18762c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // There are many components in the system watching for this so as to
18862c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // adjust audio routing, screen orientation, etc.
18962c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
190008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown
19162c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            // Release the wake lock that was acquired when the message was posted.
19262c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown            mWakeLock.release();
19362c82e4d92cc0b856059f905d81885f7808a0e7dJeff Brown        }
194008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown    }
195008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown
196a2910d0abbbe18ba1710dfd4a31af45769632255Jeff Brown    private final Handler mHandler = new Handler(true /*async*/) {
197008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        @Override
198008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown        public void handleMessage(Message msg) {
199008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown            switch (msg.what) {
200008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                case MSG_DOCK_STATE_CHANGED:
201008b1762a8d5c908281a832ff90817ade6c7f9f6Jeff Brown                    handleDockStateChange();
202bfca3a0056a42bde672c8ade83f13f139149f756Bernd Holzhey                    break;
203bfca3a0056a42bde672c8ade83f13f139149f756Bernd Holzhey            }
204bfca3a0056a42bde672c8ade83f13f139149f756Bernd Holzhey        }
20527b28b3f62bd3b54fa13acd5d035940b9be464f3Tobias Haamel    };
206c9f4eaf4388087f23aa97e55349850f0902a19d3Dan Murphy}
207