1 /* 2 * Copyright (C) 2022 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 android.multiuser; 18 19 import static java.util.concurrent.TimeUnit.SECONDS; 20 21 import android.content.BroadcastReceiver; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import java.io.Closeable; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.concurrent.ConcurrentHashMap; 37 import java.util.concurrent.Semaphore; 38 39 public class BroadcastWaiter implements Closeable { 40 private final Context mContext; 41 private final String mTag; 42 private final int mTimeoutInSecond; 43 private final Set<String> mActions; 44 45 private final Set<String> mActionReceivedForUser = new HashSet<>(); 46 private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>(); 47 48 private final Map<String, Semaphore> mSemaphoresMap = new ConcurrentHashMap<>(); getSemaphore(final String action, final int userId)49 private Semaphore getSemaphore(final String action, final int userId) { 50 final String key = action + userId; 51 return mSemaphoresMap.computeIfAbsent(key, (String absentKey) -> new Semaphore(0)); 52 } 53 BroadcastWaiter(Context context, String tag, int timeoutInSecond, String... actions)54 public BroadcastWaiter(Context context, String tag, int timeoutInSecond, String... actions) { 55 mContext = context; 56 mTag = tag + "_" + BroadcastWaiter.class.getSimpleName(); 57 mTimeoutInSecond = timeoutInSecond; 58 59 mActions = new HashSet<>(Arrays.asList(actions)); 60 mActions.forEach(this::registerBroadcastReceiver); 61 } 62 registerBroadcastReceiver(String action)63 private void registerBroadcastReceiver(String action) { 64 Log.d(mTag, "#registerBroadcastReceiver for " + action); 65 66 final IntentFilter filter = new IntentFilter(action); 67 if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 68 filter.addDataScheme(ContentResolver.SCHEME_FILE); 69 } 70 71 final BroadcastReceiver receiver = new BroadcastReceiver() { 72 @Override 73 public void onReceive(Context context, Intent intent) { 74 if (action.equals(intent.getAction())) { 75 final int userId = getSendingUserId(); 76 final String data = intent.getDataString(); 77 Log.d(mTag, "Received " + action + " for user " + userId 78 + (!TextUtils.isEmpty(data) ? " with " + data : "")); 79 mActionReceivedForUser.add(action + userId); 80 getSemaphore(action, userId).release(); 81 } 82 } 83 }; 84 85 mContext.registerReceiverForAllUsers(receiver, filter, null, null); 86 mBroadcastReceivers.add(receiver); 87 } 88 89 @Override close()90 public void close() { 91 mBroadcastReceivers.forEach(mContext::unregisterReceiver); 92 } 93 hasActionBeenReceivedForUser(String action, int userId)94 public boolean hasActionBeenReceivedForUser(String action, int userId) { 95 return mActionReceivedForUser.contains(action + userId); 96 } 97 waitActionForUser(String action, int userId)98 public boolean waitActionForUser(String action, int userId) { 99 Log.d(mTag, "#waitActionForUser(action: " + action + ", userId: " + userId + ")"); 100 101 if (!mActions.contains(action)) { 102 Log.d(mTag, "No broadcast receivers registered for " + action); 103 return false; 104 } 105 106 try { 107 if (!getSemaphore(action, userId).tryAcquire(1, mTimeoutInSecond, SECONDS)) { 108 Log.e(mTag, action + " broadcast wasn't received for user " + userId); 109 return false; 110 } 111 } catch (InterruptedException e) { 112 Log.e(mTag, "Interrupted while waiting " + action + " for user " + userId); 113 return false; 114 } 115 return true; 116 } 117 waitActionForUserIfNotReceivedYet(String action, int userId)118 public boolean waitActionForUserIfNotReceivedYet(String action, int userId) { 119 return hasActionBeenReceivedForUser(action, userId) 120 || waitActionForUser(action, userId); 121 } 122 } 123