Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5abc2f4 authored by William Hester's avatar William Hester
Browse files

Add notification for Test Harness Mode

As part of Security Review's requirements, I need to add a notification to
Test Harness Mode. It is visible iff Test Harness Mode is active (unless
the user blocks notifications from the system).

Currently, tapping on it does nothing; there's no Intent available in
the system to direct the user to perform a factory reset.

Bug: 80137798
Test: make && adb shell cmd testharness enable; verify that the dialog
exists
Change-Id: I9b391bce57ec1ef5392bd78bfdcf62a3a7e00e55
parent 5ccf21b3
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -3595,6 +3595,11 @@
    <string name="adb_active_notification_message">Tap to turn off USB debugging</string>
    <string name="adb_active_notification_message" product="tv">Select to disable USB debugging.</string>

    <!-- Title of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
    <string name="test_harness_mode_notification_title">Test Harness Mode enabled</string>
    <!-- Message of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
    <string name="test_harness_mode_notification_message">Perform a factory reset to disable Test Harness Mode.</string>

    <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
    <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
    <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
+2 −0
Original line number Diff line number Diff line
@@ -2059,6 +2059,8 @@
  <java-symbol type="string" name="accessibility_binding_label" />
  <java-symbol type="string" name="adb_active_notification_message" />
  <java-symbol type="string" name="adb_active_notification_title" />
  <java-symbol type="string" name="test_harness_mode_notification_title" />
  <java-symbol type="string" name="test_harness_mode_notification_message" />
  <java-symbol type="string" name="taking_remote_bugreport_notification_title" />
  <java-symbol type="string" name="share_remote_bugreport_notification_title" />
  <java-symbol type="string" name="sharing_remote_bugreport_notification_title" />
+4 −0
Original line number Diff line number Diff line
@@ -226,6 +226,10 @@ message SystemMessage {
    // Inform that user that the USB port is free of contaminants.
    NOTE_USB_CONTAMINANT_NOT_DETECTED = 53;

    // Inform the user that Test Harness Mode is active.
    // Package: android
    NOTE_TEST_HARNESS_MODE_ENABLED = 54;

    // ADD_NEW_IDS_ABOVE_THIS_LINE
    // Legacy IDs with arbitrary values appear below
    // Legacy IDs existed as stable non-conflicting constants prior to the O release
+78 −21
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.server.testharness;

import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +37,8 @@ import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;

import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.server.LocalServices;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemService;
@@ -87,6 +91,7 @@ public class TestHarnessModeService extends SystemService {
            case PHASE_BOOT_COMPLETED:
                disableAutoSync();
                configureSettings();
                showNotification();
                break;
        }
        super.onBootPhase(phase);
@@ -99,19 +104,29 @@ public class TestHarnessModeService extends SystemService {
            // There's no data to apply, so leave it as-is.
            return;
        }
        PersistentData persistentData;
        try {
            persistentData = PersistentData.fromBytes(testHarnessModeData);
        } catch (SetUpTestHarnessModeException e) {
            Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e);
            return;
        } finally {
            // Clear out the Test Harness Mode data. It's now in memory if successful or we should
            // skip setting up.
            getPersistentDataBlock().clearTestHarnessModeData();
        }
        mShouldSetUpTestHarnessMode = true;
        setUpAdb(testHarnessModeData);
        setUpAdb(persistentData);
        setDeviceProvisioned();
    }

    private void setUpAdb(byte[] testHarnessModeData) {
    private void setUpAdb(PersistentData persistentData) {
        ContentResolver cr = getContext().getContentResolver();

        // Disable the TTL for ADB keys before enabling ADB
        Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);

        PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);

        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1");
        writeAdbKeysFile(persistentData);
    }

@@ -145,9 +160,6 @@ public class TestHarnessModeService extends SystemService {

        writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath());
        writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());

        // Clear out the data block so that we don't revert the ADB keys on every boot.
        getPersistentDataBlock().clearTestHarnessModeData();
    }

    private void writeBytesToFile(byte[] keys, Path adbKeys) {
@@ -177,6 +189,36 @@ public class TestHarnessModeService extends SystemService {
                UserHandle.USER_CURRENT);
    }

    private void showNotification() {
        if (!SystemProperties.getBoolean(TEST_HARNESS_MODE_PROPERTY, false)) {
            return;
        }
        String title = getContext()
                .getString(com.android.internal.R.string.test_harness_mode_notification_title);
        String message = getContext()
                .getString(com.android.internal.R.string.test_harness_mode_notification_message);

        Notification notification =
                new Notification.Builder(getContext(), SystemNotificationChannels.DEVELOPER)
                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                        .setWhen(0)
                        .setOngoing(true)
                        .setTicker(title)
                        .setDefaults(0)  // please be quiet
                        .setColor(getContext().getColor(
                                com.android.internal.R.color
                                        .system_notification_accent_color))
                        .setContentTitle(title)
                        .setContentText(message)
                        .setVisibility(Notification.VISIBILITY_PUBLIC)
                        .build();

        NotificationManager notificationManager =
                getContext().getSystemService(NotificationManager.class);
        notificationManager.notifyAsUser(
                null, SystemMessage.NOTE_TEST_HARNESS_MODE_ENABLED, notification, UserHandle.ALL);
    }

    @Nullable
    private PersistentDataBlockManagerInternal getPersistentDataBlock() {
        if (mPersistentDataBlockManagerInternal == null) {
@@ -248,8 +290,7 @@ public class TestHarnessModeService extends SystemService {
                byte[] adbKeysBytes = getBytesFromFile(adbKeys);
                byte[] adbTempKeysBytes = getBytesFromFile(adbTempKeys);

                PersistentData persistentData =
                        new PersistentData(true, adbKeysBytes, adbTempKeysBytes);
                PersistentData persistentData = new PersistentData(adbKeysBytes, adbTempKeysBytes);
                getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes());
            } catch (IOException e) {
                Slog.e(TAG, "Failed to store ADB keys.", e);
@@ -316,37 +357,40 @@ public class TestHarnessModeService extends SystemService {
     */
    public static class PersistentData {
        static final byte VERSION_1 = 1;
        static final byte VERSION_2 = 2;

        final int mVersion;
        final boolean mEnabled;
        final byte[] mAdbKeys;
        final byte[] mAdbTempKeys;

        PersistentData(boolean enabled, byte[] adbKeys, byte[] adbTempKeys) {
            this(VERSION_1, enabled, adbKeys, adbTempKeys);
        PersistentData(byte[] adbKeys, byte[] adbTempKeys) {
            this(VERSION_2, adbKeys, adbTempKeys);
        }

        PersistentData(int version, boolean enabled, byte[] adbKeys, byte[] adbTempKeys) {
        PersistentData(int version, byte[] adbKeys, byte[] adbTempKeys) {
            this.mVersion = version;
            this.mEnabled = enabled;
            this.mAdbKeys = adbKeys;
            this.mAdbTempKeys = adbTempKeys;
        }

        static PersistentData fromBytes(byte[] bytes) {
        static PersistentData fromBytes(byte[] bytes) throws SetUpTestHarnessModeException {
            try {
                DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
                int version = is.readInt();
                boolean enabled = is.readBoolean();
                if (version == VERSION_1) {
                    // Version 1 of Test Harness Mode contained an "enabled" bit that we need to
                    // skip. If we don't, the binary format will be bad and it will fail to set up.
                    is.readBoolean();
                }
                int adbKeysLength = is.readInt();
                byte[] adbKeys = new byte[adbKeysLength];
                is.readFully(adbKeys);
                int adbTempKeysLength = is.readInt();
                byte[] adbTempKeys = new byte[adbTempKeysLength];
                is.readFully(adbTempKeys);
                return new PersistentData(version, enabled, adbKeys, adbTempKeys);
                return new PersistentData(version, adbKeys, adbTempKeys);
            } catch (IOException e) {
                throw new RuntimeException(e);
                throw new SetUpTestHarnessModeException(e);
            }
        }

@@ -354,8 +398,7 @@ public class TestHarnessModeService extends SystemService {
            try {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                DataOutputStream dos = new DataOutputStream(os);
                dos.writeInt(VERSION_1);
                dos.writeBoolean(mEnabled);
                dos.writeInt(VERSION_2);
                dos.writeInt(mAdbKeys.length);
                dos.write(mAdbKeys);
                dos.writeInt(mAdbTempKeys.length);
@@ -367,4 +410,18 @@ public class TestHarnessModeService extends SystemService {
            }
        }
    }

    /**
     * An exception thrown when Test Harness Mode fails to set up.
     *
     * <p>In the event that Test Harness Mode fails to set up, all of the data should be discarded
     * and the Test Harness Mode portion of the persistent data block should be wiped. This will
     * prevent the device from becoming stuck, as there is no way (without rooting the device) to
     * clear the persistent data block.
     */
    private static class SetUpTestHarnessModeException extends Exception {
        SetUpTestHarnessModeException(Exception e) {
            super(e);
        }
    }
}