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

Commit 6b73b231 authored by Robert Berry's avatar Robert Berry Committed by Android (Google) Code Review
Browse files

Merge "Create snapshot even if no PendingIntent is registered" into pi-dev

parents 9531b70c 2fd4b597
Loading
Loading
Loading
Loading
+2 −8
Original line number Diff line number Diff line
@@ -172,7 +172,7 @@ public class KeySyncTask implements Runnable {

    private void syncKeysForAgent(int recoveryAgentUid) {
        boolean recreateCurrentVersion = false;
        if (!shoudCreateSnapshot(recoveryAgentUid)) {
        if (!shouldCreateSnapshot(recoveryAgentUid)) {
            recreateCurrentVersion =
                    (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null)
                    && (mRecoverySnapshotStorage.get(recoveryAgentUid) == null);
@@ -184,11 +184,6 @@ public class KeySyncTask implements Runnable {
            }
        }

        if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) {
            Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid);
            return;
        }

        PublicKey publicKey;
        CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
                recoveryAgentUid);
@@ -313,7 +308,6 @@ public class KeySyncTask implements Runnable {
            return;
        }
        mRecoverySnapshotStorage.put(recoveryAgentUid, keyChainSnapshotBuilder.build());

        mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
    }

@@ -354,7 +348,7 @@ public class KeySyncTask implements Runnable {
     * Returns {@code true} if a sync is pending.
     * @param recoveryAgentUid uid of the recovery agent.
     */
    private boolean shoudCreateSnapshot(int recoveryAgentUid) {
    private boolean shouldCreateSnapshot(int recoveryAgentUid) {
        int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
        if (!ArrayUtils.contains(types, KeyChainProtectionParams.TYPE_LOCKSCREEN)) {
            // Only lockscreen type is supported.
+37 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.locksettings.recoverablekeystore;

import android.annotation.Nullable;
import android.app.PendingIntent;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;

@@ -36,6 +37,9 @@ public class RecoverySnapshotListenersStorage {
    @GuardedBy("this")
    private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>();

    @GuardedBy("this")
    private ArraySet<Integer> mAgentsWithPendingSnapshots = new ArraySet<>();

    /**
     * Sets new listener for the recovery agent, identified by {@code uid}.
     *
@@ -46,6 +50,11 @@ public class RecoverySnapshotListenersStorage {
            int recoveryAgentUid, @Nullable PendingIntent intent) {
        Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid);
        mAgentIntents.put(recoveryAgentUid, intent);

        if (mAgentsWithPendingSnapshots.contains(recoveryAgentUid)) {
            Log.i(TAG, "Snapshot already created for agent. Immediately triggering intent.");
            tryToSendIntent(recoveryAgentUid, intent);
        }
    }

    /**
@@ -56,21 +65,39 @@ public class RecoverySnapshotListenersStorage {
    }

    /**
     * Notifies recovery agent that new snapshot is available. Does nothing if a listener was not
     * registered.
     * Notifies recovery agent that new snapshot is available. If a recovery agent has not yet
     * registered a {@link PendingIntent}, remembers that a snapshot is pending for it, so that
     * when it does register, that intent is immediately triggered.
     *
     * @param recoveryAgentUid uid of recovery agent.
     */
    public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) {
        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
        if (intent != null) {
        if (intent == null) {
            Log.i(TAG, "Snapshot available for agent " + recoveryAgentUid
                    + " but agent has not yet initialized. Will notify agent when it does.");
            mAgentsWithPendingSnapshots.add(recoveryAgentUid);
            return;
        }

        tryToSendIntent(recoveryAgentUid, intent);
    }

    /**
     * Attempts to send {@code intent} for the recovery agent. If this fails, remembers to notify
     * the recovery agent immediately if it registers a new intent.
     */
    private synchronized void tryToSendIntent(int recoveryAgentUid, PendingIntent intent) {
        try {
            intent.send();
            mAgentsWithPendingSnapshots.remove(recoveryAgentUid);
            Log.d(TAG, "Successfully notified listener.");
        } catch (PendingIntent.CanceledException e) {
            Log.e(TAG,
                    "Failed to trigger PendingIntent for " + recoveryAgentUid,
                    e);
            }
            // As it failed to trigger, trigger immediately if a new intent is registered later.
            mAgentsWithPendingSnapshots.add(recoveryAgentUid);
        }
    }
}
+11 −15
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
@@ -240,10 +241,8 @@ public class KeySyncTaskTest {
    }

    @Test
    public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
    public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
        SecretKey applicationKey = generateKey();
        mRecoverableKeyStoreDb.setServerParams(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.insertKey(
                TEST_USER_ID,
@@ -252,6 +251,7 @@ public class KeySyncTaskTest {
                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);

        mKeySyncTask.run();

@@ -259,21 +259,18 @@ public class KeySyncTaskTest {
    }

    @Test
    public void run_doesNotSendAnythingIfNoDeviceIdIsSet() throws Exception {
        SecretKey applicationKey = generateKey();
    public void run_stillCreatesSnapshotIfNoRecoveryAgentPendingIntentRegistered()
            throws Exception {
        mRecoverableKeyStoreDb.setServerParams(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
        mRecoverableKeyStoreDb.insertKey(
                TEST_USER_ID,
                TEST_RECOVERY_AGENT_UID,
                TEST_APP_KEY_ALIAS,
                WrappedKey.fromSecretKey(mEncryptKey, applicationKey));
        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
        when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);

        mKeySyncTask.run();

        assertNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
        assertNotNull(mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID));
    }

    @Test
@@ -502,7 +499,7 @@ public class KeySyncTaskTest {
    }

    @Test
    public void run_doesNotSendKeyToNonregisteredAgent() throws Exception {
    public void run_notifiesNonregisteredAgent() throws Exception {
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
@@ -514,8 +511,7 @@ public class KeySyncTaskTest {
        mKeySyncTask.run();

        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
        verify(mSnapshotListenersStorage, never()).
                recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
        verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID2);
    }

    @Test
+57 −1
Original line number Diff line number Diff line
@@ -4,7 +4,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -12,10 +15,18 @@ import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecoverySnapshotListenersStorageTest {

    private static final String TEST_INTENT_ACTION =
            "com.android.server.locksettings.recoverablekeystore.RECOVERY_SNAPSHOT_TEST_ACTION";

    private static final int TEST_TIMEOUT_SECONDS = 1;

    private final RecoverySnapshotListenersStorage mStorage =
            new RecoverySnapshotListenersStorage();

@@ -34,4 +45,49 @@ public class RecoverySnapshotListenersStorageTest {

        assertTrue(mStorage.hasListener(recoveryAgentUid));
    }

    @Test
    public void setSnapshotListener_invokesIntentImmediatelyIfPreviouslyNotified()
            throws Exception {
        Context context = InstrumentationRegistry.getTargetContext();
        int recoveryAgentUid = 1000;
        mStorage.recoverySnapshotAvailable(recoveryAgentUid);
        PendingIntent intent = PendingIntent.getBroadcast(
                context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0);
        CountDownLatch latch = new CountDownLatch(1);
        context.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                context.unregisterReceiver(this);
                latch.countDown();
            }
        }, new IntentFilter(TEST_INTENT_ACTION));

        mStorage.setSnapshotListener(recoveryAgentUid, intent);

        assertTrue(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS));
    }

    @Test
    public void setSnapshotListener_doesNotRepeatedlyInvokeListener() throws Exception {
        Context context = InstrumentationRegistry.getTargetContext();
        int recoveryAgentUid = 1000;
        mStorage.recoverySnapshotAvailable(recoveryAgentUid);
        PendingIntent intent = PendingIntent.getBroadcast(
                context, /*requestCode=*/ 0, new Intent(TEST_INTENT_ACTION), /*flags=*/0);
        CountDownLatch latch = new CountDownLatch(2);
        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                latch.countDown();
            }
        };
        context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION));

        mStorage.setSnapshotListener(recoveryAgentUid, intent);
        mStorage.setSnapshotListener(recoveryAgentUid, intent);

        assertFalse(latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        context.unregisterReceiver(broadcastReceiver);
    }
}