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

Commit 276ef85a authored by Robert Berry's avatar Robert Berry Committed by android-build-merger
Browse files

Merge "Create snapshot even if no PendingIntent is registered" into pi-dev am: 6b73b231

am: 3711c980

Change-Id: Id3faf4519300b62decb4020efb1f27f9099f0ad6
parents 8302b6ed 3711c980
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);
    }
}