1 /*
2  * Copyright (C) 2018 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.adb;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.debug.AdbManager;
32 import android.debug.IAdbManager;
33 import android.os.ServiceManager;
34 import android.provider.Settings;
35 import android.util.Log;
36 
37 import androidx.test.InstrumentationRegistry;
38 
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 
45 import java.io.BufferedReader;
46 import java.io.File;
47 import java.io.FileOutputStream;
48 import java.io.FileReader;
49 import java.nio.file.Files;
50 import java.nio.file.Path;
51 import java.nio.file.StandardCopyOption;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.concurrent.ArrayBlockingQueue;
55 import java.util.concurrent.BlockingQueue;
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.TimeUnit;
58 
59 @RunWith(JUnit4.class)
60 public final class AdbDebuggingManagerTest {
61 
62     private static final String TAG = "AdbDebuggingManagerTest";
63 
64     // This component is passed to the AdbDebuggingManager to act as the activity that can confirm
65     // unknown adb keys.  An overlay package was first attempted to override the
66     // config_customAdbPublicKeyConfirmationComponent config, but the value from that package was
67     // not being read.
68     private static final String ADB_CONFIRM_COMPONENT =
69             "com.android.frameworks.servicestests/"
70                     + "com.android.server.adb.AdbDebuggingManagerTestActivity";
71 
72     // The base64 encoding of the values 'test key 1' and 'test key 2'.
73     private static final String TEST_KEY_1 = "dGVzdCBrZXkgMQo= test@android.com";
74     private static final String TEST_KEY_2 = "dGVzdCBrZXkgMgo= test@android.com";
75 
76     // This response is received from the AdbDebuggingHandler when the key is allowed to connect
77     private static final String RESPONSE_KEY_ALLOWED = "OK";
78     // This response is received from the AdbDebuggingHandler when the key is not allowed to connect
79     private static final String RESPONSE_KEY_DENIED = "NO";
80 
81     // wait up to 5 seconds for any blocking queries
82     private static final long TIMEOUT = 5000;
83     private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS;
84 
85     private Context mContext;
86     private AdbDebuggingManager mManager;
87     private AdbDebuggingManager.AdbDebuggingThread mThread;
88     private AdbDebuggingManager.AdbDebuggingHandler mHandler;
89     private AdbDebuggingManager.AdbKeyStore mKeyStore;
90     private BlockingQueue<TestResult> mBlockingQueue;
91     private long mOriginalAllowedConnectionTime;
92     private File mAdbKeyXmlFile;
93     private File mAdbKeyFile;
94     private FakeTicker mFakeTicker;
95 
96     @Before
setUp()97     public void setUp() throws Exception {
98         mContext = InstrumentationRegistry.getContext();
99         mAdbKeyFile = new File(mContext.getFilesDir(), "adb_keys");
100         if (mAdbKeyFile.exists()) {
101             mAdbKeyFile.delete();
102         }
103         mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
104         if (mAdbKeyXmlFile.exists()) {
105             mAdbKeyXmlFile.delete();
106         }
107 
108         mFakeTicker = new FakeTicker();
109         // Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released)
110         mFakeTicker.advance(1224658800L);
111 
112         mThread = new AdbDebuggingThreadTest();
113         mManager = new AdbDebuggingManager(
114                 mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker);
115 
116         mHandler = mManager.mHandler;
117         mThread.setHandler(mHandler);
118 
119         mHandler.initKeyStore();
120         mKeyStore = mHandler.mAdbKeyStore;
121 
122         mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
123         mBlockingQueue = new ArrayBlockingQueue<>(1);
124     }
125 
126     @After
tearDown()127     public void tearDown() throws Exception {
128         mKeyStore.deleteKeyStore();
129         setAllowedConnectionTime(mOriginalAllowedConnectionTime);
130         dropShellPermissionIdentity();
131     }
132 
133     /**
134      * Sets the allowed connection time within which a subsequent connection from a key for which
135      * the user selected the 'Always allow' option will be allowed without user interaction.
136      */
setAllowedConnectionTime(long connectionTime)137     private void setAllowedConnectionTime(long connectionTime) {
138         Settings.Global.putLong(mContext.getContentResolver(),
139                 Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
140     }
141 
142     @Test
testAllowNewKeyOnce()143     public void testAllowNewKeyOnce() throws Exception {
144         // Verifies the behavior when a new key first attempts to connect to a device. During the
145         // first connection the ADB confirmation activity should be launched to prompt the user to
146         // allow the connection with an option to always allow connections from this key.
147 
148         // Verify if the user allows the key but does not select the option to 'always
149         // allow' that the connection is allowed but the key is not stored.
150         runAdbTest(TEST_KEY_1, true, false, false);
151 
152         // Persist the keystore to ensure that the key is not written to the adb_keys file.
153         persistKeyStore();
154         assertFalse(
155                 "A key for which the 'always allow' option is not selected must not be written "
156                         + "to the adb_keys file",
157                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
158     }
159 
160     @Test
testDenyNewKey()161     public void testDenyNewKey() throws Exception {
162         // Verifies if the user does not allow the key then the connection is not allowed and the
163         // key is not stored.
164         runAdbTest(TEST_KEY_1, false, false, false);
165     }
166 
167     @Test
testDisconnectAlwaysAllowKey()168     public void testDisconnectAlwaysAllowKey() throws Exception {
169         // When a key is disconnected from a device ADB should send a disconnect message; this
170         // message should trigger an update of the last connection time for the currently connected
171         // key.
172 
173         // Allow a connection from a new key with the 'Always allow' option selected.
174         runAdbTest(TEST_KEY_1, true, true, false);
175 
176         // Advance the clock by 10ms to ensure there's a difference
177         mFakeTicker.advance(10 * 1_000_000);
178 
179         // Send the disconnect message for the currently connected key to trigger an update of the
180         // last connection time.
181         disconnectKey(TEST_KEY_1);
182         assertEquals(
183                 "The last connection time was not updated after the disconnect",
184                 mFakeTicker.currentTimeMillis(),
185                 mKeyStore.getLastConnectionTime(TEST_KEY_1));
186     }
187 
188     @Test
testDisconnectAllowedOnceKey()189     public void testDisconnectAllowedOnceKey() throws Exception {
190         // When a key is disconnected ADB should send a disconnect message; this message should
191         // essentially result in a noop for keys that the user only allows once since the last
192         // connection time is not maintained for these keys.
193 
194         // Allow a connection from a new key with the 'Always allow' option set to false
195         runAdbTest(TEST_KEY_1, true, false, false);
196 
197         // Send the disconnect message for the currently connected key.
198         disconnectKey(TEST_KEY_1);
199 
200         // Verify that the disconnected key is not automatically allowed on a subsequent connection.
201         runAdbTest(TEST_KEY_1, true, false, false);
202     }
203 
204     @Test
testAlwaysAllowConnectionFromKey()205     public void testAlwaysAllowConnectionFromKey() throws Exception {
206         // Verifies when the user selects the 'Always allow' option for the current key that
207         // subsequent connection attempts from that key are allowed.
208 
209         // Allow a connection from a new key with the 'Always allow' option selected.
210         runAdbTest(TEST_KEY_1, true, true, false);
211 
212         // Send a persist keystore message to force the key to be written to the adb_keys file
213         persistKeyStore();
214 
215         // Verify the key is in the adb_keys file to ensure subsequent connections are allowed by
216         // adbd.
217         assertTrue("The key was not in the adb_keys file after persisting the keystore",
218                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
219     }
220 
221     @Test
testOriginalAlwaysAllowBehavior()222     public void testOriginalAlwaysAllowBehavior() throws Exception {
223         // If the Settings.Global.ADB_ALLOWED_CONNECTION_TIME setting is set to 0 then the original
224         // behavior of 'Always allow' should be restored.
225 
226         // Accept the test key with the 'Always allow' option selected.
227         runAdbTest(TEST_KEY_1, true, true, false);
228 
229         // Set the connection time to 0 to restore the original behavior.
230         setAllowedConnectionTime(0);
231 
232         // Set the last connection time to the test key to a very small value to ensure it would
233         // fail the new test but would be allowed with the original behavior.
234         mKeyStore.setLastConnectionTime(TEST_KEY_1, 1);
235 
236         // Verify that the key is in the adb_keys file to ensure subsequent connections are
237         // automatically allowed by adbd.
238         persistKeyStore();
239         assertTrue("The key was not in the adb_keys file after persisting the keystore",
240                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
241     }
242 
243     @Test
testLastConnectionTimeUpdatedByScheduledJob()244     public void testLastConnectionTimeUpdatedByScheduledJob() throws Exception {
245         // If a development device is left connected to a system beyond the allowed connection time
246         // a reboot of the device while connected could make it appear as though the last connection
247         // time is beyond the allowed window. A scheduled job runs daily while a key is connected
248         // to update the last connection time to the current time; this ensures if the device is
249         // rebooted while connected to a system the last connection time should be within 24 hours.
250 
251         // Allow the key to connect with the 'Always allow' option selected
252         runAdbTest(TEST_KEY_1, true, true, false);
253 
254         // Get the current last connection time for comparison after the scheduled job is run
255         long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
256 
257         // Advance a small amount of time to ensure that the updated connection time changes
258         mFakeTicker.advance(10);
259 
260         // Send a message to the handler to update the last connection time for the active key
261         updateKeyStore();
262         assertNotEquals(
263                 "The last connection time of the key was not updated after the update key "
264                         + "connection time message",
265                 lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
266     }
267 
268     @Test
testKeystorePersisted()269     public void testKeystorePersisted() throws Exception {
270         // After any updates are made to the key store a message should be sent to persist the
271         // key store. This test verifies that a key that is always allowed is persisted in the key
272         // store along with its last connection time.
273 
274         // Allow the key to connect with the 'Always allow' option selected
275         runAdbTest(TEST_KEY_1, true, true, false);
276 
277         // Send a message to the handler to persist the updated keystore and verify a new key store
278         // backed by the XML file contains the key.
279         persistKeyStore();
280         assertTrue(
281                 "The key with the 'Always allow' option selected was not persisted in the keystore",
282                 mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1));
283 
284         // Get the current last connection time to ensure it is updated in the persisted keystore.
285         long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
286 
287         // Advance a small amount of time to ensure the last connection time is updated.
288         mFakeTicker.advance(10);
289 
290         // Send a message to the handler to update the last connection time for the active key.
291         updateKeyStore();
292 
293         // Persist the updated last connection time and verify a new key store backed by the XML
294         // file contains the updated connection time.
295         persistKeyStore();
296         assertNotEquals(
297                 "The last connection time in the key file was not updated after the update "
298                         + "connection time message", lastConnectionTime,
299                 mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1));
300         // Verify that the key is in the adb_keys file
301         assertTrue("The key was not in the adb_keys file after persisting the keystore",
302                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
303     }
304 
305     @Test
testAdbClearRemovesActiveKey()306     public void testAdbClearRemovesActiveKey() throws Exception {
307         // If the user selects the option to 'Revoke USB debugging authorizations' while an 'Always
308         // allow' key is connected that key should be deleted as well.
309 
310         // Allow the key to connect with the 'Always allow' option selected
311         runAdbTest(TEST_KEY_1, true, true, false);
312 
313         // Send a message to the handler to clear the adb authorizations.
314         clearKeyStore();
315 
316         // Send a message to disconnect the currently connected key
317         disconnectKey(TEST_KEY_1);
318         assertFalse(
319                 "The currently connected 'always allow' key must not be authorized after an adb"
320                         + " clear message.",
321                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
322 
323         // The key should not be in the adb_keys file after clearing the authorizations.
324         assertFalse("The key must not be in the adb_keys file after clearing authorizations",
325                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
326     }
327 
328     @Test
testAdbGrantRevokedIfLastConnectionBeyondAllowedTime()329     public void testAdbGrantRevokedIfLastConnectionBeyondAllowedTime() throws Exception {
330         // If the user selects the 'Always allow' option then subsequent connections from the key
331         // will be allowed as long as the connection is within the allowed window. Once the last
332         // connection time is beyond this window the user should be prompted to allow the key again.
333 
334         // Allow the key to connect with the 'Always allow' option selected
335         runAdbTest(TEST_KEY_1, true, true, false);
336 
337         // Set the allowed window to a small value to ensure the time is beyond the allowed window.
338         setAllowedConnectionTime(1);
339 
340         // Advance a small amount of time to exceed the allowed window.
341         mFakeTicker.advance(10);
342 
343         // The AdbKeyStore has a method to get the time of the next key expiration to ensure the
344         // scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs
345         // first.
346         assertEquals("The time of the next key expiration must be 0.", 0,
347                 mKeyStore.getNextExpirationTime());
348 
349         // Persist the key store and verify that the key is no longer in the adb_keys file.
350         persistKeyStore();
351         assertFalse(
352                 "The key must not be in the adb_keys file after the allowed time has elapsed.",
353                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
354     }
355 
356     @Test
testLastConnectionTimeCannotBeSetBack()357     public void testLastConnectionTimeCannotBeSetBack() throws Exception {
358         // When a device is first booted there is a possibility that the system time will be set to
359         // the build time of the system image. If a device is connected to a system during a reboot
360         // this could cause the connection time to be set in the past; if the device time is not
361         // corrected before the device is disconnected then a subsequent connection with the time
362         // corrected would appear as though the last connection time was beyond the allowed window,
363         // and the user would be required to authorize the connection again. This test verifies that
364         // the AdbKeyStore does not update the last connection time if it is less than the
365         // previously written connection time.
366 
367         // Allow the key to connect with the 'Always allow' option selected
368         runAdbTest(TEST_KEY_1, true, true, false);
369 
370         // Get the last connection time that was written to the key store.
371         long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
372 
373         // Attempt to set the last connection time to 1970
374         mKeyStore.setLastConnectionTime(TEST_KEY_1, 0);
375         assertEquals(
376                 "The last connection time in the adb key store must not be set to a value less "
377                         + "than the previous connection time",
378                 lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
379 
380         // Attempt to set the last connection time just beyond the allowed window.
381         mKeyStore.setLastConnectionTime(TEST_KEY_1,
382                 Math.max(0, lastConnectionTime - (mKeyStore.getAllowedConnectionTime() + 1)));
383         assertEquals(
384                 "The last connection time in the adb key store must not be set to a value less "
385                         + "than the previous connection time",
386                 lastConnectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
387     }
388 
389     @Test
testAdbKeyRemovedByScheduledJob()390     public void testAdbKeyRemovedByScheduledJob() throws Exception {
391         // When a key is automatically allowed it should be stored in the adb_keys file. A job is
392         // then scheduled daily to update the connection time of the currently connected key, and if
393         // no connected key exists the key store is updated to purge expired keys. This test
394         // verifies that after a key's expiration time has been reached that it is no longer
395         // in the key store nor the adb_keys file
396 
397         // Set the allowed time to the default to ensure that any modification to this value do not
398         // impact this test.
399         setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
400 
401         // Allow both test keys to connect with the 'always allow' option selected.
402         runAdbTest(TEST_KEY_1, true, true, false);
403         runAdbTest(TEST_KEY_2, true, true, false);
404         disconnectKey(TEST_KEY_1);
405         disconnectKey(TEST_KEY_2);
406 
407         // Persist the key store and verify that both keys are in the key store and adb_keys file.
408         persistKeyStore();
409         assertTrue(
410                 "Test key 1 must be in the adb_keys file after selecting the 'always allow' "
411                         + "option",
412                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
413         assertTrue(
414                 "Test key 1 must be in the adb key store after selecting the 'always allow' "
415                         + "option",
416                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
417         assertTrue(
418                 "Test key 2 must be in the adb_keys file after selecting the 'always allow' "
419                         + "option",
420                 isKeyInFile(TEST_KEY_2, mAdbKeyFile));
421         assertTrue(
422                 "Test key 2 must be in the adb key store after selecting the 'always allow' option",
423                 mKeyStore.isKeyAuthorized(TEST_KEY_2));
424 
425         // Set test key 1's last connection time to a small value and persist the keystore to ensure
426         // it is cleared out after the next key store update.
427         mKeyStore.setLastConnectionTime(TEST_KEY_1, 1, true);
428         updateKeyStore();
429         assertFalse(
430                 "Test key 1 must no longer be in the adb_keys file after its timeout period is "
431                         + "reached",
432                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
433         assertFalse(
434                 "Test key 1 must no longer be in the adb key store after its timeout period is "
435                         + "reached",
436                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
437         assertTrue(
438                 "Test key 2 must still be in the adb_keys file after test key 1's timeout "
439                         + "period is reached",
440                 isKeyInFile(TEST_KEY_2, mAdbKeyFile));
441         assertTrue(
442                 "Test key 2 must still be in the adb key store after test key 1's timeout period "
443                         + "is reached",
444                 mKeyStore.isKeyAuthorized(TEST_KEY_2));
445     }
446 
447     @Test
testKeystoreExpirationTimes()448     public void testKeystoreExpirationTimes() throws Exception {
449         // When one or more keys are always allowed a daily job is scheduled to update the
450         // connection time of the connected key and to purge any expired keys. The keystore provides
451         // a method to obtain the expiration time of the next key to expire to ensure that a
452         // scheduled job can run at the time of the next expiration if it is before the daily job
453         // would run. This test verifies that this method returns the expected values depending on
454         // when the key should expire and also verifies that the method to schedule the next job to
455         // update the keystore is the expected value based on the time of the next expiration.
456 
457         final long epsilon = 5000;
458 
459         // Ensure the allowed time is set to the default.
460         setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
461 
462         // If there are no keys in the keystore the expiration time should be -1.
463         assertEquals("The expiration time must be -1 when there are no keys in the keystore", -1,
464                 mKeyStore.getNextExpirationTime());
465 
466         // Allow the test key to connect with the 'always allow' option.
467         runAdbTest(TEST_KEY_1, true, true, false);
468 
469         // Verify that the current expiration time is within a small value of the default time.
470         long expirationTime = mKeyStore.getNextExpirationTime();
471         if (Math.abs(expirationTime - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME)
472                 > epsilon) {
473             fail("The expiration time for a new key, " + expirationTime
474                     + ", is outside the expected value of "
475                     + Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
476         }
477         // The delay until the next job should be the lesser of the default expiration time and the
478         // AdbDebuggingHandler's job interval.
479         long expectedValue = Math.min(
480                 AdbDebuggingManager.AdbDebuggingHandler.UPDATE_KEYSTORE_JOB_INTERVAL,
481                 Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
482         long delay = mHandler.scheduleJobToUpdateAdbKeyStore();
483         if (Math.abs(delay - expectedValue) > epsilon) {
484             fail("The delay before the next scheduled job, " + delay
485                     + ", is outside the expected value of " + expectedValue);
486         }
487 
488         // Set the current expiration time to a minute from expiration and verify this new value is
489         // returned.
490         final long newExpirationTime = 60000;
491         mKeyStore.setLastConnectionTime(
492                 TEST_KEY_1,
493                 mFakeTicker.currentTimeMillis()
494                         - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
495                         + newExpirationTime,
496                 true);
497         expirationTime = mKeyStore.getNextExpirationTime();
498         if (Math.abs(expirationTime - newExpirationTime) > epsilon) {
499             fail("The expiration time for a key about to expire, " + expirationTime
500                     + ", is outside the expected value of " + newExpirationTime);
501         }
502         delay = mHandler.scheduleJobToUpdateAdbKeyStore();
503         if (Math.abs(delay - newExpirationTime) > epsilon) {
504             fail("The delay before the next scheduled job, " + delay
505                     + ", is outside the expected value of " + newExpirationTime);
506         }
507 
508         // If a key is already expired the expiration time and delay before the next job runs should
509         // be 0.
510         mKeyStore.setLastConnectionTime(TEST_KEY_1, 1, true);
511         assertEquals("The expiration time for a key that is already expired must be 0", 0,
512                 mKeyStore.getNextExpirationTime());
513         assertEquals(
514                 "The delay before the next scheduled job for a key that is already expired must"
515                         + " be 0", 0, mHandler.scheduleJobToUpdateAdbKeyStore());
516 
517         // If the previous behavior of never removing old keys is set then the expiration time
518         // should be -1 to indicate the job does not need to run.
519         setAllowedConnectionTime(0);
520         assertEquals("The expiration time must be -1 when the keys are set to never expire", -1,
521                 mKeyStore.getNextExpirationTime());
522     }
523 
524     @Test
testConnectionTimeUpdatedWithConnectedKeyMessage()525     public void testConnectionTimeUpdatedWithConnectedKeyMessage() throws Exception {
526         // When a system successfully passes the SIGNATURE challenge adbd sends a connected key
527         // message to the framework to notify of the newly connected key. This message should
528         // trigger the AdbDebuggingManager to update the last connection time for this key and mark
529         // it as the currently connected key so that its time can be updated during subsequent
530         // keystore update jobs as well as when the disconnected message is received.
531 
532         // Allow the test key to connect with the 'always allow' option selected.
533         runAdbTest(TEST_KEY_1, true, true, false);
534 
535         // Simulate disconnecting the key before a subsequent connection without user interaction.
536         disconnectKey(TEST_KEY_1);
537 
538         // Get the last connection time for the key to verify that it is updated when the connected
539         // key message is sent.
540         long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
541         mFakeTicker.advance(10);
542         mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY,
543                 TEST_KEY_1).sendToTarget();
544         flushHandlerQueue();
545         assertNotEquals(
546                 "The connection time for the key must be updated when the connected key message "
547                         + "is received",
548                 connectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
549 
550         // Verify that the scheduled job updates the connection time of the key.
551         connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
552         mFakeTicker.advance(10);
553         updateKeyStore();
554         assertNotEquals(
555                 "The connection time for the key must be updated when the update keystore message"
556                         + " is sent",
557                 connectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
558 
559         // Verify that the connection time is updated when the key is disconnected.
560         connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
561         mFakeTicker.advance(10);
562         disconnectKey(TEST_KEY_1);
563         assertNotEquals(
564                 "The connection time for the key must be updated when the disconnected message is"
565                         + " received",
566                 connectionTime, mKeyStore.getLastConnectionTime(TEST_KEY_1));
567     }
568 
569     @Test
testClearAuthorizations()570     public void testClearAuthorizations() throws Exception {
571         // When the user selects the 'Revoke USB debugging authorizations' all previously 'always
572         // allow' keys should be deleted.
573 
574         // Set the allowed connection time to the default value to ensure tests do not fail due to
575         // a small value.
576         setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
577 
578         // Allow the test key to connect with the 'always allow' option selected.
579         runAdbTest(TEST_KEY_1, true, true, false);
580         persistKeyStore();
581 
582         // Verify that the key is authorized and in the adb_keys file
583         assertTrue(
584                 "The test key must be in the keystore after the 'always allow' option is selected",
585                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
586         assertTrue(
587                 "The test key must be in the adb_keys file after the 'always allow option is "
588                         + "selected",
589                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
590 
591         // Send the message to clear the adb authorizations and verify that the keys are no longer
592         // authorized.
593         clearKeyStore();
594         assertFalse(
595                 "The test key must not be in the keystore after clearing the authorizations",
596                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
597         assertFalse(
598                 "The test key must not be in the adb_keys file after clearing the authorizations",
599                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
600     }
601 
602     @Test
testClearKeystoreAfterDisablingAdb()603     public void testClearKeystoreAfterDisablingAdb() throws Exception {
604         // When the user disables adb they should still be able to clear the authorized keys.
605 
606         // Allow the test key to connect with the 'always allow' option selected and persist the
607         // keystore.
608         runAdbTest(TEST_KEY_1, true, true, false);
609         persistKeyStore();
610 
611         // Disable adb and verify that the keystore can be cleared without throwing an exception.
612         disableAdb();
613         clearKeyStore();
614         assertFalse(
615                 "The test key must not be in the adb_keys file after clearing the authorizations",
616                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
617     }
618 
619     @Test
testUntrackedUserKeysAddedToKeystore()620     public void testUntrackedUserKeysAddedToKeystore() throws Exception {
621         // When a device is first updated to a build that tracks the connection time of adb keys
622         // the keys in the user key file will not have a connection time. To prevent immediately
623         // deleting keys that the user is actively using these untracked keys should be added to the
624         // keystore with the current system time; this gives the user time to reconnect
625         // automatically with an active key while inactive keys are deleted after the expiration
626         // time.
627 
628         final long epsilon = 5000;
629         final String[] testKeys = {TEST_KEY_1, TEST_KEY_2};
630 
631         // Add the test keys to the user key file.
632         FileOutputStream fo = new FileOutputStream(mAdbKeyFile);
633         for (String key : testKeys) {
634             fo.write(key.getBytes());
635             fo.write('\n');
636         }
637         fo.close();
638 
639         // Set the expiration time to the default and use this value to verify the expiration time
640         // of the previously untracked keys.
641         setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
642 
643         // The untracked keys should be added to the keystore as part of the constructor.
644         AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore();
645 
646         // Verify that the connection time for each test key is within a small value of the current
647         // time.
648         long time = mFakeTicker.currentTimeMillis();
649         for (String key : testKeys) {
650             long connectionTime = adbKeyStore.getLastConnectionTime(key);
651             if (Math.abs(time - connectionTime) > epsilon) {
652                 fail("The connection time for a previously untracked key, " + connectionTime
653                         + ", is beyond the current time of " + time);
654             }
655         }
656     }
657 
658     @Test
testConnectionTimeUpdatedForMultipleConnectedKeys()659     public void testConnectionTimeUpdatedForMultipleConnectedKeys() throws Exception {
660         // Since ADB supports multiple simultaneous connections verify that the connection time of
661         // each key is updated by the scheduled job as long as it is connected.
662 
663         // Allow both test keys to connect with the 'always allow' option selected.
664         runAdbTest(TEST_KEY_1, true, true, false);
665         runAdbTest(TEST_KEY_2, true, true, false);
666 
667         // Advance a small amount of time to ensure the connection time is updated by the scheduled
668         // job.
669         long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
670         long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
671         mFakeTicker.advance(10);
672         updateKeyStore();
673         assertNotEquals(
674                 "The connection time for test key 1 must be updated after the scheduled job runs",
675                 connectionTime1, mKeyStore.getLastConnectionTime(TEST_KEY_1));
676         assertNotEquals(
677                 "The connection time for test key 2 must be updated after the scheduled job runs",
678                 connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2));
679 
680         // Disconnect the second test key and verify that the last connection time of the first key
681         // is the only one updated.
682         disconnectKey(TEST_KEY_2);
683         connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
684         connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
685         mFakeTicker.advance(10);
686         updateKeyStore();
687         assertNotEquals(
688                 "The connection time for test key 1 must be updated after another key is "
689                         + "disconnected and the scheduled job runs",
690                 connectionTime1, mKeyStore.getLastConnectionTime(TEST_KEY_1));
691         assertEquals(
692                 "The connection time for test key 2 must not be updated after it is disconnected",
693                 connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2));
694     }
695 
696     @Test
testClearAuthorizationsBeforeAdbEnabled()697     public void testClearAuthorizationsBeforeAdbEnabled() throws Exception {
698         // The adb key store is not instantiated until adb is enabled; however if the user attempts
699         // to clear the adb authorizations when adb is disabled after a boot a NullPointerException
700         // was thrown as deleteKeyStore is invoked against the key store. This test ensures the
701         // key store can be successfully cleared when adb is disabled.
702         clearKeyStore();
703     }
704 
705     @Test
testClearAuthorizationsDeletesKeyFiles()706     public void testClearAuthorizationsDeletesKeyFiles() throws Exception {
707         mAdbKeyFile.createNewFile();
708         mAdbKeyXmlFile.createNewFile();
709 
710         clearKeyStore();
711 
712         assertFalse("The adb key file should have been deleted after revocation of the grants",
713                 mAdbKeyFile.exists());
714         assertFalse("The adb xml key file should have been deleted after revocation of the grants",
715                 mAdbKeyXmlFile.exists());
716     }
717 
718     @Test
testAdbKeyStore_removeKey()719     public void testAdbKeyStore_removeKey() throws Exception {
720         // Accept the test key with the 'Always allow' option selected.
721         runAdbTest(TEST_KEY_1, true, true, false);
722         runAdbTest(TEST_KEY_2, true, true, false);
723 
724         // Set the connection time to 0 to restore the original behavior.
725         setAllowedConnectionTime(0);
726 
727         // Verify that the key is in the adb_keys file to ensure subsequent connections are
728         // automatically allowed by adbd.
729         persistKeyStore();
730         assertTrue("The key was not in the adb_keys file after persisting the keystore",
731                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
732         assertTrue("The key was not in the adb_keys file after persisting the keystore",
733                 isKeyInFile(TEST_KEY_2, mAdbKeyFile));
734 
735         // Now remove one of the keys and make sure the other key is still there
736         mKeyStore.removeKey(TEST_KEY_1);
737         // Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE
738         flushHandlerQueue();
739 
740         assertFalse("The key was still in the adb_keys file after removing the key",
741                 isKeyInFile(TEST_KEY_1, mAdbKeyFile));
742         assertTrue("The key was not in the adb_keys file after removing a different key",
743                 isKeyInFile(TEST_KEY_2, mAdbKeyFile));
744     }
745 
746     @Test
testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile()747     public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception {
748         setAllowedConnectionTime(0);
749 
750         runAdbTest(TEST_KEY_1, true, true, false);
751         persistKeyStore();
752         runAdbTest(TEST_KEY_1, true, true, false);
753         persistKeyStore();
754 
755         assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size());
756     }
757 
758     @Test
testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml()759     public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception {
760         long insertTime = mFakeTicker.currentTimeMillis();
761         runAdbTest(TEST_KEY_1, true, true, false);
762         persistKeyStore();
763 
764         mFakeTicker.advance(10);
765         AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
766 
767         assertEquals(
768                 "KeyStore not populated from the XML file.",
769                 insertTime,
770                 newKeyStore.getLastConnectionTime(TEST_KEY_1));
771     }
772 
773     @Test
test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()774     public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()
775             throws Exception {
776         runAdbTest(TEST_KEY_1, true, true, false);
777         persistKeyStore();
778 
779         Files.delete(mAdbKeyXmlFile.toPath());
780         Files.delete(mAdbKeyFile.toPath());
781 
782         mManager.notifyKeyFilesUpdated();
783         flushHandlerQueue();
784 
785         assertFalse(
786                 "Key is authorized after reloading deleted key files. Was state preserved?",
787                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
788     }
789 
790     @Test
test_notifyKeyFilesUpdated_newKeyIsAuthorized()791     public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception {
792         runAdbTest(TEST_KEY_1, true, true, false);
793         persistKeyStore();
794 
795         // Back up the existing key files
796         Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp");
797         Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp");
798         Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING);
799         Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING);
800 
801         // Delete the existing key files
802         Files.delete(mAdbKeyXmlFile.toPath());
803         Files.delete(mAdbKeyFile.toPath());
804 
805         // Notify the manager that adb key files have changed.
806         mManager.notifyKeyFilesUpdated();
807         flushHandlerQueue();
808 
809         // Copy the files back
810         Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
811         Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
812 
813         // Tell the manager that the key files have changed.
814         mManager.notifyKeyFilesUpdated();
815         flushHandlerQueue();
816 
817         assertTrue(
818                 "Key is not authorized after reloading key files.",
819                 mKeyStore.isKeyAuthorized(TEST_KEY_1));
820     }
821 
822     @Test
testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow()823     public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception {
824         String trustedNetwork = "My Network";
825         mKeyStore.addTrustedNetwork(trustedNetwork);
826         persistKeyStore();
827 
828         AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
829 
830         assertTrue(
831                 "Persisted trusted network not found in new keystore instance.",
832                 newKeyStore.isTrustedNetwork(trustedNetwork));
833     }
834 
835     @Test
testIsValidMdnsServiceName()836     public void testIsValidMdnsServiceName() {
837         // Longer than 15 characters
838         assertFalse(isValidMdnsServiceName("abcd1234abcd1234"));
839 
840         // Contains invalid characters
841         assertFalse(isValidMdnsServiceName("a*a"));
842         assertFalse(isValidMdnsServiceName("a_a"));
843         assertFalse(isValidMdnsServiceName("_a"));
844 
845         // Does not begin or end with letter or digit
846         assertFalse(isValidMdnsServiceName(""));
847         assertFalse(isValidMdnsServiceName("-"));
848         assertFalse(isValidMdnsServiceName("-a"));
849         assertFalse(isValidMdnsServiceName("-1"));
850         assertFalse(isValidMdnsServiceName("a-"));
851         assertFalse(isValidMdnsServiceName("1-"));
852 
853         // Contains consecutive hyphens
854         assertFalse(isValidMdnsServiceName("a--a"));
855 
856         // Does not contain at least one letter
857         assertFalse(isValidMdnsServiceName("1"));
858         assertFalse(isValidMdnsServiceName("12"));
859         assertFalse(isValidMdnsServiceName("1-2"));
860 
861         // letter not within [a-zA-Z]
862         assertFalse(isValidMdnsServiceName("aés"));
863 
864         // Some valid names
865         assertTrue(isValidMdnsServiceName("a"));
866         assertTrue(isValidMdnsServiceName("a1"));
867         assertTrue(isValidMdnsServiceName("1A"));
868         assertTrue(isValidMdnsServiceName("aZ"));
869         assertTrue(isValidMdnsServiceName("a-Z"));
870         assertTrue(isValidMdnsServiceName("a-b-Z"));
871         assertTrue(isValidMdnsServiceName("abc-def-123-456"));
872     }
873 
874     @Test
testPairingThread_MdnsServiceName_RFC6335()875     public void testPairingThread_MdnsServiceName_RFC6335() {
876         assertTrue(isValidMdnsServiceName(AdbDebuggingManager.PairingThread.SERVICE_PROTOCOL));
877     }
878 
isValidMdnsServiceName(String name)879     private boolean isValidMdnsServiceName(String name) {
880         // The rules for Service Names [RFC6335] state that they may be no more
881         // than fifteen characters long (not counting the mandatory underscore),
882         // consisting of only letters, digits, and hyphens, must begin and end
883         // with a letter or digit, must not contain consecutive hyphens, and
884         // must contain at least one letter.
885         // No more than 15 characters long
886         final int len = name.length();
887         if (name.isEmpty() || len > 15) {
888             return false;
889         }
890 
891         boolean hasAtLeastOneLetter = false;
892         boolean sawHyphen = false;
893         for (int i = 0; i < len; ++i) {
894             // Must contain at least one letter
895             // Only contains letters, digits and hyphens
896             char c = name.charAt(i);
897             if (c == '-') {
898                 // Cannot be at beginning or end
899                 if (i == 0 || i == len - 1) {
900                     return false;
901                 }
902                 if (sawHyphen) {
903                     // Consecutive hyphen found
904                     return false;
905                 }
906                 sawHyphen = true;
907                 continue;
908             }
909 
910             sawHyphen = false;
911             if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
912                 hasAtLeastOneLetter = true;
913                 continue;
914             }
915 
916             if (c >= '0' && c <= '9') {
917                 continue;
918             }
919 
920             // Invalid character
921             return false;
922         }
923 
924         return hasAtLeastOneLetter;
925     }
926 
927     CountDownLatch mAdbActionLatch = new CountDownLatch(1);
928     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
929         @Override
930         public void onReceive(Context context, Intent intent) {
931             String action = intent.getAction();
932             Log.i(TAG, "Received intent action=" + action);
933             if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) {
934                 assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
935                         context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
936                         PackageManager.PERMISSION_GRANTED);
937                 Log.i(TAG, "action=" + action + " paired_device=" + intent.getSerializableExtra(
938                         AdbManager.WIRELESS_DEVICES_EXTRA).toString());
939                 mAdbActionLatch.countDown();
940             } else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) {
941                 assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
942                         context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
943                         PackageManager.PERMISSION_GRANTED);
944                 int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA,
945                         AdbManager.WIRELESS_STATUS_DISCONNECTED);
946                 Log.i(TAG, "action=" + action + " status=" + status);
947                 mAdbActionLatch.countDown();
948             } else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) {
949                 assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
950                         context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
951                         PackageManager.PERMISSION_GRANTED);
952                 Integer res = intent.getIntExtra(
953                         AdbManager.WIRELESS_STATUS_EXTRA,
954                         AdbManager.WIRELESS_STATUS_FAIL);
955                 Log.i(TAG, "action=" + action + " result=" + res);
956 
957                 if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) {
958                     String pairingCode = intent.getStringExtra(
959                                 AdbManager.WIRELESS_PAIRING_CODE_EXTRA);
960                     Log.i(TAG, "pairingCode=" + pairingCode);
961                 } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) {
962                     int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
963                     Log.i(TAG, "port=" + port);
964                 }
965                 mAdbActionLatch.countDown();
966             }
967         }
968     };
969 
adoptShellPermissionIdentity()970     private void adoptShellPermissionIdentity() {
971         InstrumentationRegistry.getInstrumentation().getUiAutomation()
972             .adoptShellPermissionIdentity(android.Manifest.permission.MANAGE_DEBUGGING);
973     }
974 
dropShellPermissionIdentity()975     private void dropShellPermissionIdentity() {
976         InstrumentationRegistry.getInstrumentation().getUiAutomation()
977             .dropShellPermissionIdentity();
978     }
979 
980     @Test
testBroadcastReceiverWithPermissions()981     public void testBroadcastReceiverWithPermissions() throws Exception {
982         adoptShellPermissionIdentity();
983         final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
984                 ServiceManager.getService(Context.ADB_SERVICE));
985         IntentFilter intentFilter =
986                 new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
987         intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
988         intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
989         assertEquals("Context does not have MANAGE_DEBUGGING permission.",
990                 mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
991                 PackageManager.PERMISSION_GRANTED);
992         try {
993             mContext.registerReceiver(mReceiver, intentFilter);
994             mAdbManager.enablePairingByPairingCode();
995             if (!mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
996                 fail("Receiver did not receive adb intent action within the timeout duration");
997             }
998         } finally {
999             mContext.unregisterReceiver(mReceiver);
1000         }
1001     }
1002 
1003     @Test
testBroadcastReceiverWithoutPermissions()1004     public void testBroadcastReceiverWithoutPermissions() throws Exception {
1005         adoptShellPermissionIdentity();
1006         final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
1007                 ServiceManager.getService(Context.ADB_SERVICE));
1008         IntentFilter intentFilter =
1009                 new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
1010         intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
1011         intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
1012         mAdbManager.enablePairingByPairingCode();
1013 
1014         dropShellPermissionIdentity();
1015         assertEquals("Context has MANAGE_DEBUGGING permission.",
1016                 mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
1017                 PackageManager.PERMISSION_DENIED);
1018         try {
1019             mContext.registerReceiver(mReceiver, intentFilter);
1020 
1021             if (mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
1022                 fail("Broadcast receiver received adb action intent without debug permissions");
1023             }
1024         } finally {
1025             mContext.unregisterReceiver(mReceiver);
1026         }
1027     }
1028 
1029     /**
1030      * Runs an adb test with the provided configuration.
1031      *
1032      * @param key The base64 encoding of the key to be used during the test.
1033      * @param allowKey boolean indicating whether the key should be allowed to connect.
1034      * @param alwaysAllow boolean indicating whether the 'Always allow' option should be selected.
1035      * @param autoAllowExpected boolean indicating whether the key is expected to be automatically
1036      *                          allowed without user interaction.
1037      */
runAdbTest(String key, boolean allowKey, boolean alwaysAllow, boolean autoAllowExpected)1038     private void runAdbTest(String key, boolean allowKey, boolean alwaysAllow,
1039             boolean autoAllowExpected) throws Exception {
1040         // if the key should not be automatically allowed then set up the activity
1041         if (!autoAllowExpected) {
1042             new AdbDebuggingManagerTestActivity.Configurator()
1043                     .setExpectedKey(key)
1044                     .setAllowKey(allowKey)
1045                     .setAlwaysAllow(alwaysAllow)
1046                     .setHandler(mHandler)
1047                     .setBlockingQueue(mBlockingQueue);
1048         }
1049         // send the message indicating a new key is attempting to connect
1050         mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONFIRM,
1051                 key).sendToTarget();
1052         // if the key should not be automatically allowed then the ADB public key confirmation
1053         // activity should be launched
1054         if (!autoAllowExpected) {
1055             TestResult activityResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
1056             assertNotNull(
1057                     "The ADB public key confirmation activity did not complete within the timeout"
1058                             + " period", activityResult);
1059             assertEquals("The ADB public key activity failed with result: " + activityResult,
1060                     TestResult.RESULT_ACTIVITY_LAUNCHED, activityResult.mReturnCode);
1061         }
1062         // If the activity was launched it should send a response back to the manager that would
1063         // trigger a response to the thread, or if the key is a known valid key then a response
1064         // should be sent back without requiring interaction with the activity.
1065         TestResult threadResult = mBlockingQueue.poll(TIMEOUT, TIMEOUT_TIME_UNIT);
1066         assertNotNull("A response was not sent to the thread within the timeout period",
1067                 threadResult);
1068         // verify that the result is an expected message from the thread
1069         assertEquals("An unexpected result was received: " + threadResult,
1070                 TestResult.RESULT_RESPONSE_RECEIVED, threadResult.mReturnCode);
1071         assertEquals("The manager did not send the proper response for allowKey = " + allowKey,
1072                 allowKey ? RESPONSE_KEY_ALLOWED : RESPONSE_KEY_DENIED, threadResult.mMessage);
1073         // if the key is not allowed or not always allowed verify it is not in the key store
1074         if (!allowKey || !alwaysAllow) {
1075             assertFalse("The key must not be authorized in the key store",
1076                     mKeyStore.isKeyAuthorized(key));
1077             assertFalse(
1078                     "The key must not be stored in the adb_keys file",
1079                     isKeyInFile(key, mAdbKeyFile));
1080         }
1081         flushHandlerQueue();
1082     }
1083 
persistKeyStore()1084     private void persistKeyStore() throws Exception {
1085         // Send a message to the handler to persist the key store.
1086         mHandler.obtainMessage(
1087                 AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEYSTORE)
1088                     .sendToTarget();
1089         flushHandlerQueue();
1090     }
1091 
disconnectKey(String key)1092     private void disconnectKey(String key) throws Exception {
1093         // Send a message to the handler to disconnect the currently connected key.
1094         mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT,
1095                 key).sendToTarget();
1096         flushHandlerQueue();
1097     }
1098 
updateKeyStore()1099     private void updateKeyStore() throws Exception {
1100         // Send a message to the handler to run the update keystore job.
1101         mHandler.obtainMessage(
1102                 AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEYSTORE).sendToTarget();
1103         flushHandlerQueue();
1104     }
1105 
clearKeyStore()1106     private void clearKeyStore() throws Exception {
1107         // Send a message to the handler to clear all previously authorized keys.
1108         mHandler.obtainMessage(
1109                 AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CLEAR).sendToTarget();
1110         flushHandlerQueue();
1111     }
1112 
disableAdb()1113     private void disableAdb() throws Exception {
1114         // Send a message to the handler to disable adb.
1115         mHandler.obtainMessage(
1116                 AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_DISABLED).sendToTarget();
1117         flushHandlerQueue();
1118     }
1119 
flushHandlerQueue()1120     private void flushHandlerQueue() throws Exception {
1121         // Post a Runnable to ensure that all of the current messages in the queue are flushed.
1122         CountDownLatch latch = new CountDownLatch(1);
1123         mHandler.post(() -> {
1124             latch.countDown();
1125         });
1126         if (!latch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
1127             fail("The Runnable to flush the handler's queue did not complete within the timeout "
1128                     + "period");
1129         }
1130     }
1131 
isKeyInFile(String key, File keyFile)1132     private boolean isKeyInFile(String key, File keyFile) throws Exception {
1133         if (key == null) {
1134             return false;
1135         }
1136         return adbKeyFileKeys(keyFile).contains(key);
1137     }
1138 
adbKeyFileKeys(File keyFile)1139     private static List<String> adbKeyFileKeys(File keyFile) throws Exception {
1140         List<String> keys = new ArrayList<>();
1141         if (keyFile.exists()) {
1142             try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
1143                 String currKey;
1144                 while ((currKey = in.readLine()) != null) {
1145                     keys.add(currKey);
1146                 }
1147             }
1148         }
1149         return keys;
1150     }
1151 
1152     /**
1153      * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
1154      * indicating whether the key should be allowed to  connect.
1155      */
1156     private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
1157         @Override
sendResponse(String msg)1158         public void sendResponse(String msg) {
1159             TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
1160             try {
1161                 mBlockingQueue.put(result);
1162             } catch (InterruptedException e) {
1163                 Log.e(TAG,
1164                         "Caught an InterruptedException putting the result in the queue: " + result,
1165                         e);
1166             }
1167         }
1168     }
1169 
1170     /**
1171      * Contains the result for the current portion of the test along with any corresponding
1172      * messages.
1173      */
1174     public static class TestResult {
1175         public int mReturnCode;
1176         public String mMessage;
1177 
1178         public static final int RESULT_ACTIVITY_LAUNCHED = 1;
1179         public static final int RESULT_UNEXPECTED_KEY = 2;
1180         public static final int RESULT_RESPONSE_RECEIVED = 3;
1181 
TestResult(int returnCode)1182         public TestResult(int returnCode) {
1183             this(returnCode, null);
1184         }
1185 
TestResult(int returnCode, String message)1186         public TestResult(int returnCode, String message) {
1187             mReturnCode = returnCode;
1188             mMessage = message;
1189         }
1190 
1191         @Override
toString()1192         public String toString() {
1193             return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
1194         }
1195     }
1196 
1197     private static class FakeTicker implements AdbDebuggingManager.Ticker {
1198         private long mCurrentTime;
1199 
advance(long milliseconds)1200         private void advance(long milliseconds) {
1201             mCurrentTime += milliseconds;
1202         }
1203 
1204         @Override
currentTimeMillis()1205         public long currentTimeMillis() {
1206             return mCurrentTime;
1207         }
1208     }
1209 }
1210