DO NOT MERGE Unbind CS if connection is not created within 15 seconds.
This CL adds a check to ensure that connection creation occurs within 15 seconds after binding to that ConnectionService. If the connection/conference is not created in that timespan, this CL adds logic to manually unbind the ConnectionService at that point in time. This prevents malicious apps from keeping a declared permission in forever even in the background.
Bug: 293458004
Test: manually using the provided apk + atest CallsManagerTest
Flag: EXEMPT Security High/Critical Severity CVE
(cherry picked from commit 7aa55ffca6
)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3706488d7f0e63feb560ef180fe6e83a52e80834)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:635e2feeb8ee6f84786e4c405c1e8fa39e6743cb)
Merged-In: I30caed1481dff5af2223a8ff589846597cee8229
Change-Id: I30caed1481dff5af2223a8ff589846597cee8229
This commit is contained in:
parent
bb4efe9433
commit
e0a87ee6d7
|
@ -1134,6 +1134,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
|
||||||
return (!mIsTransactionalCall ? mConnectionService : mTransactionalService);
|
return (!mIsTransactionalCall ? mConnectionService : mTransactionalService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
public int getState() {
|
public int getState() {
|
||||||
return mState;
|
return mState;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import android.telecom.ConnectionService;
|
||||||
import android.telecom.DisconnectCause;
|
import android.telecom.DisconnectCause;
|
||||||
import android.telecom.GatewayInfo;
|
import android.telecom.GatewayInfo;
|
||||||
import android.telecom.Log;
|
import android.telecom.Log;
|
||||||
|
import android.telecom.Logging.Runnable;
|
||||||
import android.telecom.Logging.Session;
|
import android.telecom.Logging.Session;
|
||||||
import android.telecom.ParcelableConference;
|
import android.telecom.ParcelableConference;
|
||||||
import android.telecom.ParcelableConnection;
|
import android.telecom.ParcelableConnection;
|
||||||
|
@ -73,12 +74,14 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
|
* Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
|
||||||
|
@ -95,6 +98,11 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
|
private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
|
||||||
private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
|
private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
private static final long SERVICE_BINDING_TIMEOUT = 15000L;
|
||||||
|
private ScheduledExecutorService mScheduledExecutor =
|
||||||
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
|
// Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
|
||||||
|
private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
|
||||||
private final class Adapter extends IConnectionServiceAdapter.Stub {
|
private final class Adapter extends IConnectionServiceAdapter.Stub {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -107,6 +115,12 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
try {
|
try {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
logIncoming("handleCreateConnectionComplete %s", callId);
|
logIncoming("handleCreateConnectionComplete %s", callId);
|
||||||
|
Call call = mCallIdMapper.getCall(callId);
|
||||||
|
if (mScheduledFutureMap.containsKey(call)) {
|
||||||
|
ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
|
||||||
|
existingTimeout.cancel(false /* cancelIfRunning */);
|
||||||
|
mScheduledFutureMap.remove(call);
|
||||||
|
}
|
||||||
// Check status hints image for cross user access
|
// Check status hints image for cross user access
|
||||||
if (connection.getStatusHints() != null) {
|
if (connection.getStatusHints() != null) {
|
||||||
Icon icon = connection.getStatusHints().getIcon();
|
Icon icon = connection.getStatusHints().getIcon();
|
||||||
|
@ -144,6 +158,12 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
try {
|
try {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
logIncoming("handleCreateConferenceComplete %s", callId);
|
logIncoming("handleCreateConferenceComplete %s", callId);
|
||||||
|
Call call = mCallIdMapper.getCall(callId);
|
||||||
|
if (mScheduledFutureMap.containsKey(call)) {
|
||||||
|
ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
|
||||||
|
existingTimeout.cancel(false /* cancelIfRunning */);
|
||||||
|
mScheduledFutureMap.remove(call);
|
||||||
|
}
|
||||||
ConnectionServiceWrapper.this
|
ConnectionServiceWrapper.this
|
||||||
.handleCreateConferenceComplete(callId, request, conference);
|
.handleCreateConferenceComplete(callId, request, conference);
|
||||||
|
|
||||||
|
@ -1597,6 +1617,26 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
.setIsAdhocConferenceCall(call.isAdhocConferenceCall())
|
.setIsAdhocConferenceCall(call.isAdhocConferenceCall())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Runnable r = new Runnable("CSW.cC", mLock) {
|
||||||
|
@Override
|
||||||
|
public void loggedRun() {
|
||||||
|
if (!call.isCreateConnectionComplete()) {
|
||||||
|
Log.e(this, new Exception(),
|
||||||
|
"Conference %s creation timeout",
|
||||||
|
getComponentName());
|
||||||
|
Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_TIMEOUT,
|
||||||
|
Log.piiHandle(call.getHandle()) + " via:" +
|
||||||
|
getComponentName().getPackageName());
|
||||||
|
response.handleCreateConferenceFailure(
|
||||||
|
new DisconnectCause(DisconnectCause.ERROR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Post cleanup to the executor service and cache the future, so we can cancel it if
|
||||||
|
// needed.
|
||||||
|
ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
|
||||||
|
SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
mScheduledFutureMap.put(call, future);
|
||||||
if (mServiceInterface != null) {
|
if (mServiceInterface != null) {
|
||||||
try {
|
try {
|
||||||
mServiceInterface.createConference(
|
mServiceInterface.createConference(
|
||||||
|
@ -1705,6 +1745,26 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
|
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Runnable r = new Runnable("CSW.cC", mLock) {
|
||||||
|
@Override
|
||||||
|
public void loggedRun() {
|
||||||
|
if (!call.isCreateConnectionComplete()) {
|
||||||
|
Log.e(this, new Exception(),
|
||||||
|
"Connection %s creation timeout",
|
||||||
|
getComponentName());
|
||||||
|
Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_TIMEOUT,
|
||||||
|
Log.piiHandle(call.getHandle()) + " via:" +
|
||||||
|
getComponentName().getPackageName());
|
||||||
|
response.handleCreateConnectionFailure(
|
||||||
|
new DisconnectCause(DisconnectCause.ERROR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Post cleanup to the executor service and cache the future, so we can cancel it if
|
||||||
|
// needed.
|
||||||
|
ScheduledFuture<?> future = mScheduledExecutor.schedule(r.getRunnableToCancel(),
|
||||||
|
SERVICE_BINDING_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
mScheduledFutureMap.put(call, future);
|
||||||
if (mServiceInterface != null) {
|
if (mServiceInterface != null) {
|
||||||
try {
|
try {
|
||||||
mServiceInterface.createConnection(
|
mServiceInterface.createConnection(
|
||||||
|
@ -2181,7 +2241,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addCall(Call call) {
|
@VisibleForTesting
|
||||||
|
public void addCall(Call call) {
|
||||||
if (mCallIdMapper.getCallId(call) == null) {
|
if (mCallIdMapper.getCallId(call) == null) {
|
||||||
mCallIdMapper.addCall(call);
|
mCallIdMapper.addCall(call);
|
||||||
}
|
}
|
||||||
|
@ -2658,4 +2719,9 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
|
||||||
sb.append("]");
|
sb.append("]");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setScheduledExecutorService(ScheduledExecutorService service) {
|
||||||
|
mScheduledExecutor = service;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,10 @@ public class LogUtils {
|
||||||
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
|
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
|
||||||
public static final String START_CONNECTION = "START_CONNECTION";
|
public static final String START_CONNECTION = "START_CONNECTION";
|
||||||
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
|
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
|
||||||
|
public static final String CREATE_CONNECTION_TIMEOUT = "CREATE_CONNECTION_TIMEOUT";
|
||||||
public static final String START_CONFERENCE = "START_CONFERENCE";
|
public static final String START_CONFERENCE = "START_CONFERENCE";
|
||||||
public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
|
public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
|
||||||
|
public static final String CREATE_CONFERENCE_TIMEOUT = "CREATE_CONFERENCE_TIMEOUT";
|
||||||
public static final String BIND_CS = "BIND_CS";
|
public static final String BIND_CS = "BIND_CS";
|
||||||
public static final String CS_BOUND = "CS_BOUND";
|
public static final String CS_BOUND = "CS_BOUND";
|
||||||
public static final String CONFERENCE_WITH = "CONF_WITH";
|
public static final String CONFERENCE_WITH = "CONF_WITH";
|
||||||
|
|
|
@ -995,6 +995,7 @@ public class BasicCallTests extends TelecomSystemTest {
|
||||||
call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
|
call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
|
||||||
assert(call.isVideoCallingSupportedByPhoneAccount());
|
assert(call.isVideoCallingSupportedByPhoneAccount());
|
||||||
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
|
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
|
||||||
|
call.setIsCreateConnectionComplete(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1018,6 +1019,7 @@ public class BasicCallTests extends TelecomSystemTest {
|
||||||
call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
|
call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
|
||||||
assert(!call.isVideoCallingSupportedByPhoneAccount());
|
assert(!call.isVideoCallingSupportedByPhoneAccount());
|
||||||
assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
|
assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
|
||||||
|
call.setIsCreateConnectionComplete(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -43,6 +43,7 @@ import static org.mockito.Mockito.timeout;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
@ -54,6 +55,7 @@ import android.media.AudioManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.OutcomeReceiver;
|
import android.os.OutcomeReceiver;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
@ -80,6 +82,7 @@ import android.test.suitebuilder.annotation.SmallTest;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.internal.telecom.IConnectionService;
|
||||||
import com.android.server.telecom.AnomalyReporterAdapter;
|
import com.android.server.telecom.AnomalyReporterAdapter;
|
||||||
import com.android.server.telecom.AsyncRingtonePlayer;
|
import com.android.server.telecom.AsyncRingtonePlayer;
|
||||||
import com.android.server.telecom.Call;
|
import com.android.server.telecom.Call;
|
||||||
|
@ -97,6 +100,7 @@ import com.android.server.telecom.ClockProxy;
|
||||||
import com.android.server.telecom.ConnectionServiceFocusManager;
|
import com.android.server.telecom.ConnectionServiceFocusManager;
|
||||||
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
|
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
|
||||||
import com.android.server.telecom.ConnectionServiceWrapper;
|
import com.android.server.telecom.ConnectionServiceWrapper;
|
||||||
|
import com.android.server.telecom.CreateConnectionResponse;
|
||||||
import com.android.server.telecom.DefaultDialerCache;
|
import com.android.server.telecom.DefaultDialerCache;
|
||||||
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
|
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
|
||||||
import com.android.server.telecom.EmergencyCallHelper;
|
import com.android.server.telecom.EmergencyCallHelper;
|
||||||
|
@ -277,6 +281,7 @@ public class CallsManagerTest extends TelecomTestCase {
|
||||||
@Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
|
@Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
|
||||||
@Mock private PhoneCapability mPhoneCapability;
|
@Mock private PhoneCapability mPhoneCapability;
|
||||||
@Mock private CallStreamingNotification mCallStreamingNotification;
|
@Mock private CallStreamingNotification mCallStreamingNotification;
|
||||||
|
@Mock private IConnectionService mIConnectionService;
|
||||||
|
|
||||||
private CallsManager mCallsManager;
|
private CallsManager mCallsManager;
|
||||||
|
|
||||||
|
@ -362,11 +367,19 @@ public class CallsManagerTest extends TelecomTestCase {
|
||||||
eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
|
eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
|
||||||
when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
|
when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
|
||||||
when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
|
when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
|
||||||
|
when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));
|
||||||
|
|
||||||
|
mComponentContextFixture.addConnectionService(new ComponentName(mContext.getPackageName(),
|
||||||
|
mContext.getPackageName().getClass().getName()), mIConnectionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
|
mComponentContextFixture.removeConnectionService(
|
||||||
|
new ComponentName(mContext.getPackageName(),
|
||||||
|
mContext.getPackageName().getClass().getName()),
|
||||||
|
mock(IConnectionService.class));
|
||||||
super.tearDown();
|
super.tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3252,6 +3265,31 @@ public class CallsManagerTest extends TelecomTestCase {
|
||||||
assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
|
assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionServiceCreateConnectionTimeout() throws Exception {
|
||||||
|
ConnectionServiceWrapper service = new ConnectionServiceWrapper(new ComponentName(
|
||||||
|
mContext.getPackageName(), mContext.getPackageName().getClass().getName()), null,
|
||||||
|
mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
|
||||||
|
TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
|
||||||
|
service.setScheduledExecutorService(scheduledExecutorService);
|
||||||
|
Call call = addSpyCall();
|
||||||
|
service.addCall(call);
|
||||||
|
when(call.isCreateConnectionComplete()).thenReturn(false);
|
||||||
|
CreateConnectionResponse response = mock(CreateConnectionResponse.class);
|
||||||
|
|
||||||
|
service.createConnection(call, response);
|
||||||
|
waitUntilConditionIsTrueOrTimeout(new Condition() {
|
||||||
|
@Override
|
||||||
|
public Object expected() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object actual() {
|
||||||
|
return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
|
||||||
|
}
|
||||||
|
}, 5000L, "Expected job failed to schedule");
|
||||||
|
}
|
||||||
|
|
||||||
private Call addSpyCall() {
|
private Call addSpyCall() {
|
||||||
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
|
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
|
||||||
|
@ -3355,4 +3393,19 @@ public class CallsManagerTest extends TelecomTestCase {
|
||||||
when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
|
when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
|
||||||
when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
|
when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
|
||||||
|
String description) throws InterruptedException {
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
while (!condition.expected().equals(condition.actual())
|
||||||
|
&& System.currentTimeMillis() - start < timeout) {
|
||||||
|
sleep(50);
|
||||||
|
}
|
||||||
|
assertEquals(description, condition.expected(), condition.actual());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected interface Condition {
|
||||||
|
Object expected();
|
||||||
|
Object actual();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -735,6 +735,14 @@ public class ComponentContextFixture implements TestFixture<Context> {
|
||||||
mServiceInfoByComponentName.put(componentName, serviceInfo);
|
mServiceInfoByComponentName.put(componentName, serviceInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeConnectionService(
|
||||||
|
ComponentName componentName,
|
||||||
|
IConnectionService service)
|
||||||
|
throws Exception {
|
||||||
|
removeService(ConnectionService.SERVICE_INTERFACE, componentName, service);
|
||||||
|
mServiceInfoByComponentName.remove(componentName);
|
||||||
|
}
|
||||||
|
|
||||||
public void addInCallService(
|
public void addInCallService(
|
||||||
ComponentName componentName,
|
ComponentName componentName,
|
||||||
IInCallService service,
|
IInCallService service,
|
||||||
|
@ -828,6 +836,12 @@ public class ComponentContextFixture implements TestFixture<Context> {
|
||||||
mComponentNameByService.put(service, name);
|
mComponentNameByService.put(service, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeService(String action, ComponentName name, IInterface service) {
|
||||||
|
mComponentNamesByAction.remove(action, name);
|
||||||
|
mServiceByComponentName.remove(name);
|
||||||
|
mComponentNameByService.remove(service);
|
||||||
|
}
|
||||||
|
|
||||||
private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
|
private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
|
||||||
List<ResolveInfo> result = new ArrayList<>();
|
List<ResolveInfo> result = new ArrayList<>();
|
||||||
for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
|
for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
|
||||||
|
|
Loading…
Reference in New Issue