| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.phone; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertFalse; |
| import static junit.framework.Assert.assertNull; |
| import static junit.framework.Assert.assertTrue; |
| |
| import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.anyBoolean; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Matchers.eq; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.role.OnRoleHoldersChangedListener; |
| import android.app.role.RoleManager; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.PersistableBundle; |
| import android.os.UserHandle; |
| import android.provider.Telephony.SimInfo; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyRegistryManager; |
| import android.telephony.ims.ProvisioningManager; |
| import android.telephony.ims.RcsConfig; |
| import android.telephony.ims.aidl.IImsConfig; |
| import android.telephony.ims.feature.ImsFeature; |
| import android.test.mock.MockContentProvider; |
| import android.test.mock.MockContentResolver; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.testing.TestableLooper; |
| import android.util.Log; |
| |
| import com.android.internal.telephony.ITelephony; |
| import com.android.internal.telephony.ims.ImsResolver; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Unit tests for RcsProvisioningMonitor |
| */ |
| public class RcsProvisioningMonitorTest { |
| private static final String TAG = "RcsProvisioningMonitorTest"; |
| private static final String SAMPLE_CONFIG = "<RCSConfig>\n" |
| + "\t<rcsVolteSingleRegistration>1</rcsVolteSingleRegistration>\n" |
| + "\t<SERVICES>\n" |
| + "\t\t<SupportedRCSProfileVersions>UP_2.0</SupportedRCSProfileVersions>\n" |
| + "\t\t<ChatAuth>1</ChatAuth>\n" |
| + "\t\t<GroupChatAuth>1</GroupChatAuth>\n" |
| + "\t\t<ftAuth>1</ftAuth>\n" |
| + "\t\t<standaloneMsgAuth>1</standaloneMsgAuth>\n" |
| + "\t\t<geolocPushAuth>1<geolocPushAuth>\n" |
| + "\t\t<Ext>\n" |
| + "\t\t\t<DataOff>\n" |
| + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n" |
| + "\t\t\t\t<fileTransferDataOff>1</fileTransferDataOff>\n" |
| + "\t\t\t\t<mmsDataOff>1</mmsDataOff>\n" |
| + "\t\t\t\t<syncDataOff>1</syncDataOff>\n" |
| + "\t\t\t</DataOff>\n" |
| + "\t\t</Ext>\n" |
| + "\t</SERVICES>\n" |
| + "</RCSConfig>"; |
| private static final int FAKE_SUB_ID_BASE = 0x0FFFFFF0; |
| private static final String DEFAULT_MESSAGING_APP1 = "DMA1"; |
| private static final String DEFAULT_MESSAGING_APP2 = "DMA2"; |
| |
| private RcsProvisioningMonitor mRcsProvisioningMonitor; |
| private Handler mHandler; |
| private HandlerThread mHandlerThread; |
| private TestableLooper mLooper; |
| private PersistableBundle mBundle; |
| private MockContentResolver mContentResolver = new MockContentResolver(); |
| private SimInfoContentProvider mProvider; |
| private BroadcastReceiver mReceiver; |
| @Mock |
| private Cursor mCursor; |
| @Mock |
| private SubscriptionManager mSubscriptionManager; |
| private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener; |
| @Mock |
| private TelephonyRegistryManager mTelephonyRegistryManager; |
| @Mock |
| private CarrierConfigManager mCarrierConfigManager; |
| private OnRoleHoldersChangedListener mRoleHolderChangedListener; |
| @Mock |
| private RcsProvisioningMonitor.RoleManagerAdapter mRoleManager; |
| @Mock |
| private ITelephony.Stub mITelephony; |
| @Mock |
| private ImsResolver mImsResolver; |
| @Mock |
| private IImsConfig.Stub mIImsConfig; |
| @Mock |
| private Resources mResources; |
| @Mock |
| private PhoneGlobals mPhone; |
| |
| private Executor mExecutor = new Executor() { |
| @Override |
| public void execute(Runnable r) { |
| r.run(); |
| } |
| }; |
| |
| private class SimInfoContentProvider extends MockContentProvider { |
| private Cursor mCursor; |
| private ContentValues mValues; |
| |
| SimInfoContentProvider(Context context) { |
| super(context); |
| } |
| |
| public void setCursor(Cursor cursor) { |
| mCursor = cursor; |
| } |
| |
| @Override |
| public Cursor query(Uri uri, String[] projection, String selection, |
| String[] selectionArgs, String sortOrder) { |
| return mCursor; |
| } |
| |
| @Override |
| public int update(Uri uri, ContentValues values, |
| String selection, String[] selectionArgs) { |
| mValues = values; |
| return 1; |
| } |
| |
| ContentValues getContentValues() { |
| return mValues; |
| } |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| |
| when(mPhone.getResources()).thenReturn(mResources); |
| when(mResources.getBoolean( |
| eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true); |
| when(mPhone.getMainExecutor()).thenReturn(mExecutor); |
| when(mPhone.getSystemServiceName(eq(CarrierConfigManager.class))) |
| .thenReturn(Context.CARRIER_CONFIG_SERVICE); |
| when(mPhone.getSystemServiceName(eq(SubscriptionManager.class))) |
| .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE); |
| when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class))) |
| .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE); |
| when(mPhone.getSystemServiceName(eq(RoleManager.class))) |
| .thenReturn(Context.ROLE_SERVICE); |
| when(mPhone.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE))) |
| .thenReturn(mCarrierConfigManager); |
| when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE))) |
| .thenReturn(mSubscriptionManager); |
| when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE))) |
| .thenReturn(mTelephonyRegistryManager); |
| |
| mBundle = new PersistableBundle(); |
| when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle); |
| |
| doAnswer(new Answer<Void>() { |
| @Override |
| public Void answer(InvocationOnMock invocation) throws Throwable { |
| mReceiver = (BroadcastReceiver) invocation.getArguments()[0]; |
| return null; |
| } |
| }).when(mPhone).registerReceiver(any(BroadcastReceiver.class), any()); |
| |
| doAnswer(new Answer<Void>() { |
| @Override |
| public Void answer(InvocationOnMock invocation) throws Throwable { |
| mSubChangedListener = (SubscriptionManager.OnSubscriptionsChangedListener) |
| invocation.getArguments()[0]; |
| return null; |
| } |
| }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener( |
| any(SubscriptionManager.OnSubscriptionsChangedListener.class), |
| any()); |
| |
| doAnswer(new Answer<Void>() { |
| @Override |
| public Void answer(InvocationOnMock invocation) throws Throwable { |
| mRoleHolderChangedListener = (OnRoleHoldersChangedListener) |
| invocation.getArguments()[1]; |
| return null; |
| } |
| }).when(mRoleManager).addOnRoleHoldersChangedListenerAsUser(any(Executor.class), |
| any(OnRoleHoldersChangedListener.class), any(UserHandle.class)); |
| List<String> dmas = new ArrayList<>(); |
| dmas.add(DEFAULT_MESSAGING_APP1); |
| when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas); |
| |
| mProvider = new SimInfoContentProvider(mPhone); |
| mProvider.setCursor(mCursor); |
| mContentResolver.addProvider(SimInfo.CONTENT_URI.getAuthority(), mProvider); |
| when(mPhone.getContentResolver()).thenReturn(mContentResolver); |
| when(mCursor.moveToFirst()).thenReturn(true); |
| when(mCursor.getColumnIndexOrThrow(any())).thenReturn(1); |
| when(mCursor.getBlob(anyInt())).thenReturn( |
| RcsConfig.compressGzip(SAMPLE_CONFIG.getBytes())); |
| when(mPhone.getImsResolver()).thenReturn(mImsResolver); |
| when(mImsResolver.getImsConfig(anyInt(), anyInt())).thenReturn(mIImsConfig); |
| mHandlerThread = new HandlerThread("RcsProvisioningMonitorTest"); |
| mHandlerThread.start(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (mRcsProvisioningMonitor != null) { |
| mRcsProvisioningMonitor.destroy(); |
| mRcsProvisioningMonitor = null; |
| } |
| |
| if (mLooper != null) { |
| mLooper.destroy(); |
| mLooper = null; |
| } |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitWithSavedConfig() throws Exception { |
| createMonitor(3); |
| ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); |
| for (int i = 0; i < 3; i++) { |
| assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), |
| mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i))); |
| } |
| |
| verify(mPhone, times(3)).sendBroadcast(captorIntent.capture()); |
| Intent capturedIntent = captorIntent.getAllValues().get(1); |
| assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE, |
| capturedIntent.getAction()); |
| PhoneGlobals.getInstance().getImsResolver(); |
| verify(mPhone, atLeastOnce()).getImsResolver(); |
| verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitWithoutSavedConfig() throws Exception { |
| when(mCursor.getBlob(anyInt())).thenReturn(null); |
| ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); |
| createMonitor(3); |
| |
| verify(mPhone, times(3)).sendBroadcast(captorIntent.capture()); |
| Intent capturedIntent = captorIntent.getAllValues().get(1); |
| assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE, |
| capturedIntent.getAction()); |
| //Should not notify null config |
| verify(mIImsConfig, never()).notifyRcsAutoConfigurationReceived(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testSubInfoChanged() throws Exception { |
| createMonitor(3); |
| ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); |
| mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged()); |
| processAllMessages(); |
| for (int i = 0; i < 3; i++) { |
| assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), |
| mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i))); |
| } |
| verify(mPhone, times(3)).sendBroadcast(captorIntent.capture()); |
| Intent capturedIntent = captorIntent.getAllValues().get(1); |
| assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE, |
| capturedIntent.getAction()); |
| verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean()); |
| |
| makeFakeActiveSubIds(1); |
| mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged()); |
| processAllMessages(); |
| |
| for (int i = 1; i < 3; i++) { |
| assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)); |
| } |
| verify(mIImsConfig, times(2)).notifyRcsAutoConfigurationRemoved(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testDefaultMessagingApplicationChangedWithAcs() throws Exception { |
| createMonitor(1); |
| updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2); |
| mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, true); |
| processAllMessages(); |
| byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE); |
| |
| assertNull(configCached); |
| assertNull(mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG)); |
| verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationRemoved(); |
| verify(mIImsConfig, atLeastOnce()).triggerRcsReconfiguration(); |
| // The api should only be called when monitor is initilized. |
| verify(mIImsConfig, times(1)) |
| .notifyRcsAutoConfigurationReceived(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testDefaultMessagingApplicationChangedWithoutAcs() throws Exception { |
| createMonitor(1); |
| updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2); |
| mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, false); |
| processAllMessages(); |
| byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE); |
| |
| assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), configCached)); |
| verify(mIImsConfig, never()).notifyRcsAutoConfigurationRemoved(); |
| // The api should be called 2 times, one happens when monitor is initilized, |
| // Another happens when DMS is changed. |
| verify(mIImsConfig, times(2)) |
| .notifyRcsAutoConfigurationReceived(any(), anyBoolean()); |
| } |
| |
| @Test |
| @SmallTest |
| public void testCarrierConfigChanged() throws Exception { |
| createMonitor(1); |
| when(mResources.getBoolean( |
| eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true); |
| ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); |
| mBundle.putBoolean( |
| CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture()); |
| Intent capturedIntent = captorIntent.getValue(); |
| assertEquals(capturedIntent.getAction(), |
| ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE); |
| assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra( |
| ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1)); |
| assertEquals(ProvisioningManager.STATUS_CAPABLE, |
| capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1)); |
| |
| mBundle.putBoolean( |
| CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture()); |
| capturedIntent = captorIntent.getValue(); |
| assertEquals(capturedIntent.getAction(), |
| ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE); |
| assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra( |
| ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1)); |
| assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE, |
| capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1)); |
| |
| |
| when(mResources.getBoolean( |
| eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(false); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture()); |
| capturedIntent = captorIntent.getValue(); |
| assertEquals(capturedIntent.getAction(), |
| ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE); |
| assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra( |
| ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1)); |
| assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE |
| | ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE, |
| capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1)); |
| } |
| |
| @Test |
| @SmallTest |
| public void testUpdateConfig() throws Exception { |
| createMonitor(1); |
| final ArgumentCaptor<byte[]> argumentBytes = ArgumentCaptor.forClass(byte[].class); |
| |
| mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false); |
| processAllMessages(); |
| |
| verify(mImsResolver, atLeastOnce()).getImsConfig( |
| anyInt(), eq(ImsFeature.FEATURE_RCS)); |
| verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationReceived( |
| argumentBytes.capture(), eq(false)); |
| assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), argumentBytes.getValue())); |
| } |
| |
| @Test |
| @SmallTest |
| public void testRequestReconfig() throws Exception { |
| createMonitor(1); |
| |
| mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| |
| verify(mImsResolver, atLeastOnce()).getImsConfig( |
| anyInt(), eq(ImsFeature.FEATURE_RCS)); |
| verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved(); |
| verify(mIImsConfig, times(1)).triggerRcsReconfiguration(); |
| } |
| |
| |
| @Test |
| @SmallTest |
| public void testIsRcsVolteSingleRegistrationEnabled() throws Exception { |
| createMonitor(1); |
| |
| when(mResources.getBoolean( |
| eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(true); |
| mBundle.putBoolean( |
| CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE)); |
| |
| mBundle.putBoolean( |
| CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE)); |
| |
| |
| when(mResources.getBoolean( |
| eq(R.bool.config_rcsVolteSingleRegistrationEnabled))).thenReturn(false); |
| mBundle.putBoolean( |
| CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true); |
| broadcastCarrierConfigChange(FAKE_SUB_ID_BASE); |
| processAllMessages(); |
| assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE)); |
| } |
| |
| private void createMonitor(int subCount) { |
| if (Looper.myLooper() == null) { |
| Looper.prepare(); |
| } |
| makeFakeActiveSubIds(subCount); |
| mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(), |
| mRoleManager); |
| mHandler = mRcsProvisioningMonitor.getHandler(); |
| try { |
| mLooper = new TestableLooper(mHandler.getLooper()); |
| } catch (Exception e) { |
| logd("Unable to create looper from handler."); |
| } |
| } |
| |
| private void broadcastCarrierConfigChange(int subId) { |
| Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); |
| intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId); |
| mExecutor.execute(() -> { |
| mReceiver.onReceive(mPhone, intent); |
| }); |
| } |
| |
| private void makeFakeActiveSubIds(int count) { |
| final int[] subIds = new int[count]; |
| for (int i = 0; i < count; i++) { |
| subIds[i] = FAKE_SUB_ID_BASE + i; |
| } |
| when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds); |
| } |
| |
| private void updateDefaultMessageApplication(String packageName) { |
| List<String> dmas = new ArrayList<>(); |
| dmas.add(packageName); |
| when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas); |
| mExecutor.execute(() -> mRoleHolderChangedListener.onRoleHoldersChanged( |
| RoleManager.ROLE_SMS, UserHandle.ALL)); |
| } |
| |
| private void processAllMessages() { |
| while (!mLooper.getLooper().getQueue().isIdle()) { |
| mLooper.processAllMessages(); |
| } |
| } |
| |
| private static void logd(String str) { |
| Log.d(TAG, str); |
| } |
| } |