1 /* 2 * Copyright (C) 2017 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.locksettings.recoverablekeystore; 18 19 import android.annotation.Nullable; 20 import android.app.PendingIntent; 21 import android.util.ArraySet; 22 import android.util.Log; 23 import android.util.SparseArray; 24 25 import com.android.internal.annotations.GuardedBy; 26 27 /** 28 * In memory storage for listeners to be notified when new recovery snapshot is available. This 29 * class is thread-safe. It is used on two threads - the service thread and the thread that runs the 30 * {@link KeySyncTask}. 31 * 32 * @hide 33 */ 34 public class RecoverySnapshotListenersStorage { 35 private static final String TAG = "RecoverySnapshotLstnrs"; 36 37 @GuardedBy("this") 38 private SparseArray<PendingIntent> mAgentIntents = new SparseArray<>(); 39 40 @GuardedBy("this") 41 private ArraySet<Integer> mAgentsWithPendingSnapshots = new ArraySet<>(); 42 43 /** 44 * Sets new listener for the recovery agent, identified by {@code uid}. 45 * 46 * @param recoveryAgentUid uid of the recovery agent. 47 * @param intent PendingIntent which will be triggered when new snapshot is available. 48 */ setSnapshotListener( int recoveryAgentUid, @Nullable PendingIntent intent)49 public synchronized void setSnapshotListener( 50 int recoveryAgentUid, @Nullable PendingIntent intent) { 51 Log.i(TAG, "Registered listener for agent with uid " + recoveryAgentUid); 52 mAgentIntents.put(recoveryAgentUid, intent); 53 54 if (mAgentsWithPendingSnapshots.contains(recoveryAgentUid)) { 55 Log.i(TAG, "Snapshot already created for agent. Immediately triggering intent."); 56 tryToSendIntent(recoveryAgentUid, intent); 57 } 58 } 59 60 /** 61 * Returns {@code true} if a listener has been set for the recovery agent. 62 */ hasListener(int recoveryAgentUid)63 public synchronized boolean hasListener(int recoveryAgentUid) { 64 return mAgentIntents.get(recoveryAgentUid) != null; 65 } 66 67 /** 68 * Notifies recovery agent that new snapshot is available. If a recovery agent has not yet 69 * registered a {@link PendingIntent}, remembers that a snapshot is pending for it, so that 70 * when it does register, that intent is immediately triggered. 71 * 72 * @param recoveryAgentUid uid of recovery agent. 73 */ recoverySnapshotAvailable(int recoveryAgentUid)74 public synchronized void recoverySnapshotAvailable(int recoveryAgentUid) { 75 PendingIntent intent = mAgentIntents.get(recoveryAgentUid); 76 if (intent == null) { 77 Log.i(TAG, "Snapshot available for agent " + recoveryAgentUid 78 + " but agent has not yet initialized. Will notify agent when it does."); 79 mAgentsWithPendingSnapshots.add(recoveryAgentUid); 80 return; 81 } 82 83 tryToSendIntent(recoveryAgentUid, intent); 84 } 85 86 /** 87 * Attempts to send {@code intent} for the recovery agent. If this fails, remembers to notify 88 * the recovery agent immediately if it registers a new intent. 89 */ tryToSendIntent(int recoveryAgentUid, PendingIntent intent)90 private synchronized void tryToSendIntent(int recoveryAgentUid, PendingIntent intent) { 91 try { 92 intent.send(); 93 mAgentsWithPendingSnapshots.remove(recoveryAgentUid); 94 Log.d(TAG, "Successfully notified listener."); 95 } catch (PendingIntent.CanceledException e) { 96 Log.e(TAG, 97 "Failed to trigger PendingIntent for " + recoveryAgentUid, 98 e); 99 // As it failed to trigger, trigger immediately if a new intent is registered later. 100 mAgentsWithPendingSnapshots.add(recoveryAgentUid); 101 } 102 } 103 } 104