1 /*
2  * Copyright (C) 2021 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 package com.android.bedstead.dpmwrapper;
17 
18 import static com.android.bedstead.dpmwrapper.Utils.MY_USER_ID;
19 import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
20 import static com.android.bedstead.dpmwrapper.Utils.assertCurrentUserOnHeadlessSystemMode;
21 
22 import android.app.ActivityManager;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.UserHandle;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * {@link BroadcastReceiver} used in the test apps to receive intents that were originally sent to
39  * the device owner's {@link android.app.admin.DeviceAdminReceiver}.
40  *
41  * <p>It must be declared in the manifest:
42  * <pre><code>
43    <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
44              android:exported="true"/>
45 </code></pre>
46  *
47  */
48 public final class TestAppCallbacksReceiver extends BroadcastReceiver {
49 
50     private static final String TAG = TestAppCallbacksReceiver.class.getSimpleName();
51     private static final String EXTRA = "relayed_intent";
52 
53     private static final Object LOCK = new Object();
54     private static HandlerThread sHandlerThread;
55     private static Handler sHandler;
56 
57     /**
58      * Map of receivers per intent action.
59      */
60     @GuardedBy("LOCK")
61     private static final ArrayMap<String, ArrayList<BroadcastReceiver>> sRealReceivers =
62             new ArrayMap<>();
63 
setHandlerThread()64     private static void setHandlerThread() {
65         if (sHandlerThread != null) return;
66 
67         sHandlerThread = new HandlerThread("TestAppCallbacksReceiverThread");
68         Log.i(TAG, "Starting thread " + sHandlerThread + " on user " + MY_USER_ID);
69         sHandlerThread.start();
70         sHandler = new Handler(sHandlerThread.getLooper());
71     }
72 
73     @Override
onReceive(Context context, Intent intent)74     public void onReceive(Context context, Intent intent) {
75         Log.i(TAG, " received intent on user " + context.getUserId() + ": " + intent);
76         assertCurrentUserOnHeadlessSystemMode(context);
77         setHandlerThread();
78 
79         Intent realIntent = intent.getParcelableExtra(EXTRA);
80         if (realIntent == null) {
81             Log.e(TAG, "No " + EXTRA + " on intent " + intent);
82             return;
83         }
84         String action = realIntent.getAction();
85         ArrayList<BroadcastReceiver> receivers;
86         synchronized (LOCK) {
87             receivers = sRealReceivers.get(action);
88         }
89         if (receivers == null || receivers.isEmpty()) {
90             Log.e(TAG, "onReceive(): no receiver for " + action + ": " + sRealReceivers);
91             return;
92         }
93         Log.d(TAG, "Will dispatch intent to " + receivers.size() + " on handler thread");
94         receivers.forEach((r) -> sHandler.post(() ->
95                 handleDispatchIntent(r, context, realIntent)));
96     }
97 
handleDispatchIntent(BroadcastReceiver receiver, Context context, Intent intent)98     private void handleDispatchIntent(BroadcastReceiver receiver, Context context,
99             Intent intent) {
100         Log.d(TAG, "Dispatching " + intent + " to " + receiver + " on thread "
101                 + Thread.currentThread());
102         receiver.onReceive(context, intent);
103     }
104 
registerReceiver(Context context, BroadcastReceiver receiver, IntentFilter filter)105     static void registerReceiver(Context context, BroadcastReceiver receiver,
106             IntentFilter filter) {
107         if (VERBOSE) Log.v(TAG, "registerReceiver(): " + receiver);
108         synchronized (LOCK) {
109             filter.actionsIterator().forEachRemaining((action) -> {
110                 Log.d(TAG, "Registering " + receiver + " for " + action);
111                 ArrayList<BroadcastReceiver> receivers = sRealReceivers.get(action);
112                 if (receivers == null) {
113                     receivers = new ArrayList<>();
114                     if (VERBOSE) Log.v(TAG, "Creating list of receivers for " + action);
115                     sRealReceivers.put(action, receivers);
116                 }
117                 receivers.add(receiver);
118             });
119         }
120     }
121 
unregisterReceiver(Context context, BroadcastReceiver receiver)122     static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
123         if (VERBOSE) Log.v(TAG, "unregisterReceiver(): " + receiver);
124 
125         synchronized (LOCK) {
126             for (int i = 0; i < sRealReceivers.size(); i++) {
127                 String action = sRealReceivers.keyAt(i);
128                 ArrayList<BroadcastReceiver> receivers = sRealReceivers.valueAt(i);
129                 boolean removed = receivers.remove(receiver);
130                 if (removed) {
131                     Log.d(TAG, "Removed " + receiver + " for action " + action);
132                 }
133             }
134         }
135     }
136 
sendBroadcast(Context context, Intent intent)137     static void sendBroadcast(Context context, Intent intent) {
138         int currentUserId = ActivityManager.getCurrentUser();
139         Intent bridgeIntent = new Intent(context, TestAppCallbacksReceiver.class)
140                 .putExtra(EXTRA, intent);
141         Log.d(TAG, "Relaying " + intent + " from user " + MY_USER_ID + " to user "
142                 + currentUserId + " using " + bridgeIntent);
143         context.sendBroadcastAsUser(bridgeIntent, UserHandle.of(currentUserId));
144     }
145 }
146