1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui; 16 17 import static com.android.systemui.Flags.sliceBroadcastRelayInBackground; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ComponentName; 21 import android.content.ContentProvider; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.net.Uri; 26 import android.os.UserHandle; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import androidx.annotation.GuardedBy; 31 import androidx.annotation.WorkerThread; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.settingslib.SliceBroadcastRelay; 35 import com.android.systemui.broadcast.BroadcastDispatcher; 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.dagger.qualifiers.Background; 38 39 import java.util.concurrent.CopyOnWriteArraySet; 40 import java.util.concurrent.Executor; 41 42 import javax.inject.Inject; 43 44 /** 45 * Allows settings to register certain broadcasts to launch the settings app for pinned slices. 46 * @see SliceBroadcastRelay 47 */ 48 @SysUISingleton 49 public class SliceBroadcastRelayHandler implements CoreStartable { 50 private static final String TAG = "SliceBroadcastRelay"; 51 private static final boolean DEBUG = false; 52 53 @GuardedBy("mRelays") 54 private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>(); 55 private final Context mContext; 56 private final BroadcastDispatcher mBroadcastDispatcher; 57 private final Executor mBackgroundExecutor; 58 59 @Inject SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher, @Background Executor backgroundExecutor)60 public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher, 61 @Background Executor backgroundExecutor) { 62 mContext = context; 63 mBroadcastDispatcher = broadcastDispatcher; 64 mBackgroundExecutor = backgroundExecutor; 65 } 66 67 @Override start()68 public void start() { 69 if (DEBUG) Log.d(TAG, "Start"); 70 IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER); 71 filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER); 72 73 if (sliceBroadcastRelayInBackground()) { 74 mBroadcastDispatcher.registerReceiver(mReceiver, filter, mBackgroundExecutor); 75 } else { 76 mBroadcastDispatcher.registerReceiver(mReceiver, filter); 77 } 78 } 79 80 // This does not use BroadcastDispatcher as the filter may have schemas or mime types. 81 @WorkerThread 82 @VisibleForTesting handleIntent(Intent intent)83 void handleIntent(Intent intent) { 84 if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) { 85 Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class); 86 ComponentName receiverClass = 87 intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER, 88 ComponentName.class); 89 IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER, 90 IntentFilter.class); 91 if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter); 92 getOrCreateRelay(uri).register(mContext, receiverClass, filter); 93 } else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) { 94 Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class); 95 if (DEBUG) Log.d(TAG, "Unregister " + uri); 96 BroadcastRelay relay = getAndRemoveRelay(uri); 97 if (relay != null) { 98 relay.unregister(mContext); 99 } 100 } 101 } 102 103 @WorkerThread getOrCreateRelay(Uri uri)104 private BroadcastRelay getOrCreateRelay(Uri uri) { 105 synchronized (mRelays) { 106 BroadcastRelay ret = mRelays.get(uri); 107 if (ret == null) { 108 ret = new BroadcastRelay(uri); 109 mRelays.put(uri, ret); 110 } 111 return ret; 112 } 113 } 114 115 @WorkerThread getAndRemoveRelay(Uri uri)116 private BroadcastRelay getAndRemoveRelay(Uri uri) { 117 synchronized (mRelays) { 118 return mRelays.remove(uri); 119 } 120 } 121 122 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 123 @Override 124 public void onReceive(Context context, Intent intent) { 125 handleIntent(intent); 126 } 127 }; 128 129 private static class BroadcastRelay extends BroadcastReceiver { 130 131 private final CopyOnWriteArraySet<ComponentName> mReceivers = new CopyOnWriteArraySet<>(); 132 private final UserHandle mUserId; 133 private final Uri mUri; 134 BroadcastRelay(Uri uri)135 public BroadcastRelay(Uri uri) { 136 mUserId = new UserHandle(ContentProvider.getUserIdFromUri(uri)); 137 mUri = uri; 138 } 139 register(Context context, ComponentName receiver, IntentFilter filter)140 public void register(Context context, ComponentName receiver, IntentFilter filter) { 141 mReceivers.add(receiver); 142 context.registerReceiver(this, filter, 143 Context.RECEIVER_EXPORTED_UNAUDITED); 144 } 145 unregister(Context context)146 public void unregister(Context context) { 147 context.unregisterReceiver(this); 148 } 149 150 @Override onReceive(Context context, Intent intent)151 public void onReceive(Context context, Intent intent) { 152 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 153 for (ComponentName receiver : mReceivers) { 154 intent.setComponent(receiver); 155 intent.putExtra(SliceBroadcastRelay.EXTRA_URI, mUri.toString()); 156 if (DEBUG) Log.d(TAG, "Forwarding " + receiver + " " + intent + " " + mUserId); 157 context.sendBroadcastAsUser(intent, mUserId); 158 } 159 } 160 } 161 } 162