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