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