1e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar/*
2e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * Copyright (C) 2012 The Android Open Source Project
3e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar *
4e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * Licensed under the Apache License, Version 2.0 (the "License");
5e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * you may not use this file except in compliance with the License.
6e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * You may obtain a copy of the License at
7e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar *
8e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar *      http://www.apache.org/licenses/LICENSE-2.0
9e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar *
10e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * Unless required by applicable law or agreed to in writing, software
11e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * distributed under the License is distributed on an "AS IS" BASIS,
12e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * See the License for the specific language governing permissions an
14e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar * limitations under the License.
15e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar */
16e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar
17e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbarpackage com.android.server.usb;
18e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbar
19e7bd886cb8b69b8f787e7aadd097663632153436Daniel Dunbarimport android.content.ActivityNotFoundException;
20import android.content.Context;
21import android.content.Intent;
22import android.net.LocalSocket;
23import android.net.LocalSocketAddress;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Environment;
27import android.os.FileUtils;
28import android.os.Looper;
29import android.os.Message;
30import android.os.Process;
31import android.os.SystemClock;
32import android.util.Slog;
33import android.util.Base64;
34
35import java.lang.Thread;
36import java.io.File;
37import java.io.FileDescriptor;
38import java.io.FileOutputStream;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.OutputStream;
42import java.io.PrintWriter;
43import java.security.MessageDigest;
44import java.util.Arrays;
45
46public class UsbDebuggingManager implements Runnable {
47    private static final String TAG = "UsbDebuggingManager";
48    private static final boolean DEBUG = false;
49
50    private final String ADBD_SOCKET = "adbd";
51    private final String ADB_DIRECTORY = "misc/adb";
52    private final String ADB_KEYS_FILE = "adb_keys";
53    private final int BUFFER_SIZE = 4096;
54
55    private final Context mContext;
56    private final Handler mHandler;
57    private final HandlerThread mHandlerThread;
58    private Thread mThread;
59    private boolean mAdbEnabled = false;
60    private String mFingerprints;
61    private LocalSocket mSocket = null;
62    private OutputStream mOutputStream = null;
63
64    public UsbDebuggingManager(Context context) {
65        mHandlerThread = new HandlerThread("UsbDebuggingHandler");
66        mHandlerThread.start();
67        mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper());
68        mContext = context;
69    }
70
71    private void listenToSocket() throws IOException {
72        try {
73            byte[] buffer = new byte[BUFFER_SIZE];
74            LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
75                                         LocalSocketAddress.Namespace.RESERVED);
76            InputStream inputStream = null;
77
78            mSocket = new LocalSocket();
79            mSocket.connect(address);
80
81            mOutputStream = mSocket.getOutputStream();
82            inputStream = mSocket.getInputStream();
83
84            while (true) {
85                int count = inputStream.read(buffer);
86                if (count < 0) {
87                    Slog.e(TAG, "got " + count + " reading");
88                    break;
89                }
90
91                if (buffer[0] == 'P' && buffer[1] == 'K') {
92                    String key = new String(Arrays.copyOfRange(buffer, 2, count));
93                    Slog.d(TAG, "Received public key: " + key);
94                    Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM);
95                    msg.obj = key;
96                    mHandler.sendMessage(msg);
97                }
98                else {
99                    Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2))));
100                    break;
101                }
102            }
103        } catch (IOException ex) {
104            Slog.e(TAG, "Communication error: ", ex);
105            throw ex;
106        } finally {
107            closeSocket();
108        }
109    }
110
111    @Override
112    public void run() {
113        while (mAdbEnabled) {
114            try {
115                listenToSocket();
116            } catch (Exception e) {
117                /* Don't loop too fast if adbd dies, before init restarts it */
118                SystemClock.sleep(1000);
119            }
120        }
121    }
122
123    private void closeSocket() {
124        try {
125            mOutputStream.close();
126        } catch (IOException e) {
127            Slog.e(TAG, "Failed closing output stream: " + e);
128        }
129
130        try {
131            mSocket.close();
132        } catch (IOException ex) {
133            Slog.e(TAG, "Failed closing socket: " + ex);
134        }
135    }
136
137    private void sendResponse(String msg) {
138        if (mOutputStream != null) {
139            try {
140                mOutputStream.write(msg.getBytes());
141            }
142            catch (IOException ex) {
143                Slog.e(TAG, "Failed to write response:", ex);
144            }
145        }
146    }
147
148    class UsbDebuggingHandler extends Handler {
149        private static final int MESSAGE_ADB_ENABLED = 1;
150        private static final int MESSAGE_ADB_DISABLED = 2;
151        private static final int MESSAGE_ADB_ALLOW = 3;
152        private static final int MESSAGE_ADB_DENY = 4;
153        private static final int MESSAGE_ADB_CONFIRM = 5;
154
155        public UsbDebuggingHandler(Looper looper) {
156            super(looper);
157        }
158
159        public void handleMessage(Message msg) {
160            switch (msg.what) {
161                case MESSAGE_ADB_ENABLED:
162                    if (mAdbEnabled)
163                        break;
164
165                    mAdbEnabled = true;
166
167                    mThread = new Thread(UsbDebuggingManager.this);
168                    mThread.start();
169
170                    break;
171
172                case MESSAGE_ADB_DISABLED:
173                    if (!mAdbEnabled)
174                        break;
175
176                    mAdbEnabled = false;
177                    closeSocket();
178
179                    try {
180                        mThread.join();
181                    } catch (Exception ex) {
182                    }
183
184                    mThread = null;
185                    mOutputStream = null;
186                    mSocket = null;
187                    break;
188
189                case MESSAGE_ADB_ALLOW: {
190                    String key = (String)msg.obj;
191                    String fingerprints = getFingerprints(key);
192
193                    if (!fingerprints.equals(mFingerprints)) {
194                        Slog.e(TAG, "Fingerprints do not match. Got "
195                                + fingerprints + ", expected " + mFingerprints);
196                        break;
197                    }
198
199                    if (msg.arg1 == 1) {
200                        writeKey(key);
201                    }
202
203                    sendResponse("OK");
204                    break;
205                }
206
207                case MESSAGE_ADB_DENY:
208                    sendResponse("NO");
209                    break;
210
211                case MESSAGE_ADB_CONFIRM: {
212                    String key = (String)msg.obj;
213                    mFingerprints = getFingerprints(key);
214                    showConfirmationDialog(key, mFingerprints);
215                    break;
216                }
217            }
218        }
219    }
220
221    private String getFingerprints(String key) {
222        String hex = "0123456789ABCDEF";
223        StringBuilder sb = new StringBuilder();
224        MessageDigest digester;
225
226        try {
227            digester = MessageDigest.getInstance("MD5");
228        } catch (Exception ex) {
229            Slog.e(TAG, "Error getting digester: " + ex);
230            return "";
231        }
232
233        byte[] base64_data = key.split("\\s+")[0].getBytes();
234        byte[] digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT));
235
236        for (int i = 0; i < digest.length; i++) {
237            sb.append(hex.charAt((digest[i] >> 4) & 0xf));
238            sb.append(hex.charAt(digest[i] & 0xf));
239            if (i < digest.length - 1)
240                sb.append(":");
241        }
242        return sb.toString();
243    }
244
245    private void showConfirmationDialog(String key, String fingerprints) {
246        Intent dialogIntent = new Intent();
247
248        dialogIntent.setClassName("com.android.systemui",
249                "com.android.systemui.usb.UsbDebuggingActivity");
250        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
251        dialogIntent.putExtra("key", key);
252        dialogIntent.putExtra("fingerprints", fingerprints);
253        try {
254            mContext.startActivity(dialogIntent);
255        } catch (ActivityNotFoundException e) {
256            Slog.e(TAG, "unable to start UsbDebuggingActivity");
257        }
258    }
259
260    private void writeKey(String key) {
261        File dataDir = Environment.getDataDirectory();
262        File adbDir = new File(dataDir, ADB_DIRECTORY);
263
264        if (!adbDir.exists()) {
265            Slog.e(TAG, "ADB data directory does not exist");
266            return;
267        }
268
269        try {
270            File keyFile = new File(adbDir, ADB_KEYS_FILE);
271
272            if (!keyFile.exists()) {
273                keyFile.createNewFile();
274                FileUtils.setPermissions(keyFile.toString(),
275                    FileUtils.S_IRUSR | FileUtils.S_IWUSR |
276                    FileUtils.S_IRGRP, -1, -1);
277            }
278
279            FileOutputStream fo = new FileOutputStream(keyFile, true);
280            fo.write(key.getBytes());
281            fo.write('\n');
282            fo.close();
283        }
284        catch (IOException ex) {
285            Slog.e(TAG, "Error writing key:" + ex);
286        }
287    }
288
289
290    public void setAdbEnabled(boolean enabled) {
291        mHandler.sendEmptyMessage(enabled ? UsbDebuggingHandler.MESSAGE_ADB_ENABLED
292                                          : UsbDebuggingHandler.MESSAGE_ADB_DISABLED);
293    }
294
295    public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
296        Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_ALLOW);
297        msg.arg1 = alwaysAllow ? 1 : 0;
298        msg.obj = publicKey;
299        mHandler.sendMessage(msg);
300    }
301
302    public void denyUsbDebugging() {
303        mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_DENY);
304    }
305
306
307    public void dump(FileDescriptor fd, PrintWriter pw) {
308        pw.println("  USB Debugging State:");
309        pw.println("    Connected to adbd: " + (mOutputStream != null));
310        pw.println("    Last key received: " + mFingerprints);
311        pw.println("    User keys:");
312        try {
313            pw.println(FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
314        } catch (IOException e) {
315            pw.println("IOException: " + e);
316        }
317        pw.println("    System keys:");
318        try {
319            pw.println(FileUtils.readTextFile(new File("/adb_keys"), 0, null));
320        } catch (IOException e) {
321            pw.println("IOException: " + e);
322        }
323    }
324}
325