Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2016 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 com.android.internal.telephony;
     18 
     19 import com.google.common.collect.ArrayListMultimap;
     20 import com.google.common.collect.Multimap;
     21 
     22 import org.mockito.MockitoAnnotations;
     23 import org.mockito.invocation.InvocationOnMock;
     24 import org.mockito.stubbing.Answer;
     25 
     26 import android.app.AlarmManager;
     27 import android.app.AppOpsManager;
     28 import android.app.NotificationManager;
     29 import android.app.usage.UsageStatsManager;
     30 import android.content.BroadcastReceiver;
     31 import android.content.ComponentName;
     32 import android.content.ContentProvider;
     33 import android.content.ContentResolver;
     34 import android.content.ContentValues;
     35 import android.content.Context;
     36 import android.content.Intent;
     37 import android.content.IntentFilter;
     38 import android.content.ServiceConnection;
     39 import android.content.SharedPreferences;
     40 import android.content.pm.PackageManager;
     41 import android.content.pm.ResolveInfo;
     42 import android.content.pm.ServiceInfo;
     43 import android.content.res.Configuration;
     44 import android.content.res.Resources;
     45 import android.database.Cursor;
     46 import android.database.MatrixCursor;
     47 import android.net.ConnectivityManager;
     48 import android.net.Uri;
     49 import android.net.wifi.WifiManager;
     50 import android.os.Bundle;
     51 import android.os.Handler;
     52 import android.os.IInterface;
     53 import android.os.PersistableBundle;
     54 import android.os.UserHandle;
     55 import android.os.UserManager;
     56 import android.preference.PreferenceManager;
     57 import android.provider.Settings;
     58 import android.telephony.CarrierConfigManager;
     59 import android.telephony.SubscriptionManager;
     60 import android.telephony.TelephonyManager;
     61 import android.test.mock.MockContentProvider;
     62 import android.test.mock.MockContentResolver;
     63 import android.test.mock.MockContext;
     64 import android.util.Log;
     65 
     66 import java.util.ArrayList;
     67 import java.util.Arrays;
     68 import java.util.Collection;
     69 import java.util.HashMap;
     70 import java.util.HashSet;
     71 import java.util.List;
     72 import java.util.Locale;
     73 import java.util.Map;
     74 
     75 import static org.mockito.Mockito.any;
     76 import static org.mockito.Mockito.anyInt;
     77 import static org.mockito.Mockito.doAnswer;
     78 import static org.mockito.Mockito.doReturn;
     79 import static org.mockito.Mockito.eq;
     80 import static org.mockito.Mockito.mock;
     81 import static org.mockito.Mockito.spy;
     82 import static org.mockito.Mockito.when;
     83 
     84 /**
     85  * Controls a test {@link Context} as would be provided by the Android framework to an
     86  * {@code Activity}, {@code Service} or other system-instantiated component.
     87  *
     88  * Contains Fake<Component> classes like FakeContext for components that require complex and
     89  * reusable stubbing. Others can be mocked using Mockito functions in tests or constructor/public
     90  * methods of this class.
     91  */
     92 public class ContextFixture implements TestFixture<Context> {
     93     private static final String TAG = "ContextFixture";
     94     public static final String PERMISSION_ENABLE_ALL = "android.permission.STUB_PERMISSION";
     95 
     96     public class FakeContentProvider extends MockContentProvider {
     97         private String[] mColumns = {"name", "value"};
     98         private HashMap<String, String> mKeyValuePairs = new HashMap<String, String>();
     99         private int mNumKeyValuePairs = 0;
    100 
    101         @Override
    102         public int delete(Uri uri, String selection, String[] selectionArgs) {
    103             return 0;
    104         }
    105 
    106         @Override
    107         public Uri insert(Uri uri, ContentValues values) {
    108             Uri newUri = null;
    109             if (values != null) {
    110                 mKeyValuePairs.put(values.getAsString("name"), values.getAsString("value"));
    111                 mNumKeyValuePairs++;
    112                 newUri = Uri.withAppendedPath(uri, "" + mNumKeyValuePairs);
    113             }
    114             logd("insert called, new mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri +
    115                     " newUri: " + newUri);
    116             return newUri;
    117         }
    118 
    119         @Override
    120         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    121                             String sortOrder) {
    122             //assuming query will always be of the form 'name = ?'
    123             logd("query called, mNumKeyValuePairs: " + mNumKeyValuePairs + " uri: " + uri);
    124             if (mKeyValuePairs.containsKey(selectionArgs[0])) {
    125                 MatrixCursor cursor = new MatrixCursor(projection);
    126                 cursor.addRow(new String[]{mKeyValuePairs.get(selectionArgs[0])});
    127                 return cursor;
    128             }
    129             return null;
    130         }
    131 
    132         @Override
    133         public Bundle call(String method, String request, Bundle args) {
    134             logd("call called, mNumKeyValuePairs: " + mNumKeyValuePairs + " method: " + method +
    135                     " request: " + request);
    136             switch(method) {
    137                 case Settings.CALL_METHOD_GET_GLOBAL:
    138                 case Settings.CALL_METHOD_GET_SECURE:
    139                 case Settings.CALL_METHOD_GET_SYSTEM:
    140                     if (mKeyValuePairs.containsKey(request)) {
    141                         Bundle b = new Bundle(1);
    142                         b.putCharSequence("value", mKeyValuePairs.get(request));
    143                         logd("returning value pair: " + mKeyValuePairs.get(request) + " for " +
    144                                 request);
    145                         return b;
    146                     }
    147                     break;
    148                 case Settings.CALL_METHOD_PUT_GLOBAL:
    149                 case Settings.CALL_METHOD_PUT_SECURE:
    150                 case Settings.CALL_METHOD_PUT_SYSTEM:
    151                     logd("adding key-value pair: " + request + "-" + (String)args.get("value"));
    152                     mKeyValuePairs.put(request, (String)args.get("value"));
    153                     mNumKeyValuePairs++;
    154                     break;
    155             }
    156             return null;
    157         }
    158     }
    159 
    160     private final HashMap<String, Object> mSystemServices = new HashMap<String, Object>();
    161 
    162     public void setSystemService(String name, Object service) {
    163         synchronized (mSystemServices) {
    164             mSystemServices.put(name, service);
    165         }
    166     }
    167 
    168     public class FakeContext extends MockContext {
    169         @Override
    170         public PackageManager getPackageManager() {
    171             return mPackageManager;
    172         }
    173 
    174         @Override
    175         public boolean bindService(
    176                 Intent serviceIntent,
    177                 ServiceConnection connection,
    178                 int flags) {
    179             if (mServiceByServiceConnection.containsKey(connection)) {
    180                 throw new RuntimeException("ServiceConnection already bound: " + connection);
    181             }
    182             IInterface service = mServiceByComponentName.get(serviceIntent.getComponent());
    183             if (service == null) {
    184                 throw new RuntimeException("ServiceConnection not found: "
    185                         + serviceIntent.getComponent());
    186             }
    187             mServiceByServiceConnection.put(connection, service);
    188             connection.onServiceConnected(serviceIntent.getComponent(), service.asBinder());
    189             return true;
    190         }
    191 
    192         @Override
    193         public void unbindService(
    194                 ServiceConnection connection) {
    195             IInterface service = mServiceByServiceConnection.remove(connection);
    196             if (service == null) {
    197                 throw new RuntimeException("ServiceConnection not found: " + connection);
    198             }
    199             connection.onServiceDisconnected(mComponentNameByService.get(service));
    200         }
    201 
    202         @Override
    203         public Object getSystemService(String name) {
    204             synchronized (mSystemServices) {
    205                 Object service = mSystemServices.get(name);
    206                 if (service != null) return service;
    207             }
    208             switch (name) {
    209                 case Context.TELEPHONY_SERVICE:
    210                     return mTelephonyManager;
    211                 case Context.APP_OPS_SERVICE:
    212                     return mAppOpsManager;
    213                 case Context.NOTIFICATION_SERVICE:
    214                     return mNotificationManager;
    215                 case Context.USER_SERVICE:
    216                     return mUserManager;
    217                 case Context.CARRIER_CONFIG_SERVICE:
    218                     return mCarrierConfigManager;
    219                 case Context.POWER_SERVICE:
    220                     // PowerManager is a final class so cannot be mocked, return real service
    221                     return TestApplication.getAppContext().getSystemService(name);
    222                 case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
    223                     return mSubscriptionManager;
    224                 case Context.WIFI_SERVICE:
    225                     return mWifiManager;
    226                 case Context.ALARM_SERVICE:
    227                     return mAlarmManager;
    228                 case Context.CONNECTIVITY_SERVICE:
    229                     return mConnectivityManager;
    230                 case Context.USAGE_STATS_SERVICE:
    231                     return mUsageStatManager;
    232                 default:
    233                     return null;
    234             }
    235         }
    236 
    237         @Override
    238         public int getUserId() {
    239             return 0;
    240         }
    241 
    242         @Override
    243         public Resources getResources() {
    244             return mResources;
    245         }
    246 
    247         @Override
    248         public String getOpPackageName() {
    249             return "com.android.internal.telephony";
    250         }
    251 
    252         @Override
    253         public ContentResolver getContentResolver() {
    254             return mContentResolver;
    255         }
    256 
    257         @Override
    258         public void unregisterReceiver(BroadcastReceiver receiver) {
    259         }
    260 
    261         @Override
    262         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    263             return registerReceiver(receiver, filter, null, null);
    264         }
    265 
    266         @Override
    267         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
    268                 String broadcastPermission, Handler scheduler) {
    269             return registerReceiverAsUser(receiver, null, filter, broadcastPermission, scheduler);
    270         }
    271 
    272         @Override
    273         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
    274                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
    275             Intent result = null;
    276             synchronized (mBroadcastReceiversByAction) {
    277                 for (int i = 0 ; i < filter.countActions() ; i++) {
    278                     mBroadcastReceiversByAction.put(filter.getAction(i), receiver);
    279                     if (result == null) {
    280                         result = mStickyBroadcastByAction.get(filter.getAction(i));
    281                     }
    282                 }
    283             }
    284 
    285             return result;
    286         }
    287 
    288         @Override
    289         public void sendBroadcast(Intent intent) {
    290             logd("sendBroadcast called for " + intent.getAction());
    291             synchronized (mBroadcastReceiversByAction) {
    292                 for (BroadcastReceiver broadcastReceiver :
    293                         mBroadcastReceiversByAction.get(intent.getAction())) {
    294                     broadcastReceiver.onReceive(mContext, intent);
    295                 }
    296             }
    297         }
    298 
    299         @Override
    300         public void sendBroadcast(Intent intent, String receiverPermission) {
    301             logd("sendBroadcast called for " + intent.getAction());
    302             sendBroadcast(intent);
    303         }
    304 
    305         @Override
    306         public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
    307             logd("sendOrderedBroadcast called for " + intent.getAction());
    308             sendBroadcast(intent);
    309         }
    310 
    311         @Override
    312         public void sendOrderedBroadcast(Intent intent, String receiverPermission,
    313                 BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
    314                 String initialData, Bundle initialExtras) {
    315             sendOrderedBroadcast(intent, receiverPermission);
    316             if (resultReceiver != null) {
    317                 synchronized (mOrderedBroadcastReceivers) {
    318                     mOrderedBroadcastReceivers.put(intent, resultReceiver);
    319                 }
    320             }
    321         }
    322 
    323         @Override
    324         public void sendOrderedBroadcast(Intent intent, String receiverPermission, Bundle options,
    325                 BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
    326                 String initialData, Bundle initialExtras) {
    327             sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler,
    328                     initialCode, initialData, initialExtras);
    329         }
    330 
    331         @Override
    332         public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp,
    333                 BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
    334                 String initialData, Bundle initialExtras) {
    335             sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler,
    336                     initialCode, initialData, initialExtras);
    337         }
    338 
    339         @Override
    340         public void sendBroadcastAsUser(Intent intent, UserHandle user) {
    341             sendBroadcast(intent);
    342         }
    343 
    344         @Override
    345         public void sendBroadcastAsUser(Intent intent, UserHandle user,
    346                                         String receiverPermission) {
    347             sendBroadcast(intent);
    348         }
    349 
    350         @Override
    351         public void sendBroadcastAsUser(Intent intent, UserHandle user,
    352                                         String receiverPermission, int appOp) {
    353             sendBroadcast(intent);
    354         }
    355 
    356         @Override
    357         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
    358                 String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
    359                 int initialCode, String initialData, Bundle initialExtras) {
    360             logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
    361             sendBroadcast(intent);
    362             if (resultReceiver != null) {
    363                 synchronized (mOrderedBroadcastReceivers) {
    364                     mOrderedBroadcastReceivers.put(intent, resultReceiver);
    365                 }
    366             }
    367         }
    368 
    369         @Override
    370         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
    371                 String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
    372                 Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
    373             logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
    374             sendBroadcast(intent);
    375             if (resultReceiver != null) {
    376                 synchronized (mOrderedBroadcastReceivers) {
    377                     mOrderedBroadcastReceivers.put(intent, resultReceiver);
    378                 }
    379             }
    380         }
    381 
    382         @Override
    383         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
    384                 String receiverPermission, int appOp, Bundle options,
    385                 BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
    386                 String initialData, Bundle initialExtras) {
    387             logd("sendOrderedBroadcastAsUser called for " + intent.getAction());
    388             sendBroadcast(intent);
    389             if (resultReceiver != null) {
    390                 synchronized (mOrderedBroadcastReceivers) {
    391                     mOrderedBroadcastReceivers.put(intent, resultReceiver);
    392                 }
    393             }
    394         }
    395 
    396         @Override
    397         public void sendStickyBroadcast(Intent intent) {
    398             logd("sendStickyBroadcast called for " + intent.getAction());
    399             synchronized (mBroadcastReceiversByAction) {
    400                 sendBroadcast(intent);
    401                 mStickyBroadcastByAction.put(intent.getAction(), intent);
    402             }
    403         }
    404 
    405         @Override
    406         public void sendStickyBroadcastAsUser(Intent intent, UserHandle ignored) {
    407             logd("sendStickyBroadcastAsUser called for " + intent.getAction());
    408             sendStickyBroadcast(intent);
    409         }
    410 
    411         @Override
    412         public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
    413                 throws PackageManager.NameNotFoundException {
    414             return this;
    415         }
    416 
    417         @Override
    418         public void enforceCallingOrSelfPermission(String permission, String message) {
    419             if (mPermissionTable.contains(permission)
    420                     || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) {
    421                 return;
    422             }
    423             logd("requested permission: " + permission + " got denied");
    424             throw new SecurityException(permission + " denied: " + message);
    425         }
    426 
    427         @Override
    428         public int checkCallingOrSelfPermission(String permission) {
    429             return PackageManager.PERMISSION_GRANTED;
    430         }
    431 
    432         @Override
    433         public SharedPreferences getSharedPreferences(String name, int mode) {
    434             return mSharedPreferences;
    435         }
    436 
    437         @Override
    438         public String getPackageName() {
    439             return "com.android.internal.telephony";
    440         }
    441     }
    442 
    443     private final Multimap<String, ComponentName> mComponentNamesByAction =
    444             ArrayListMultimap.create();
    445     private final Map<ComponentName, IInterface> mServiceByComponentName =
    446             new HashMap<ComponentName, IInterface>();
    447     private final Map<ComponentName, ServiceInfo> mServiceInfoByComponentName =
    448             new HashMap<ComponentName, ServiceInfo>();
    449     private final Map<IInterface, ComponentName> mComponentNameByService =
    450             new HashMap<IInterface, ComponentName>();
    451     private final Map<ServiceConnection, IInterface> mServiceByServiceConnection =
    452             new HashMap<ServiceConnection, IInterface>();
    453     private final Multimap<String, BroadcastReceiver> mBroadcastReceiversByAction =
    454             ArrayListMultimap.create();
    455     private final HashMap<String, Intent> mStickyBroadcastByAction =
    456             new HashMap<String, Intent>();
    457     private final Multimap<Intent, BroadcastReceiver> mOrderedBroadcastReceivers =
    458             ArrayListMultimap.create();
    459     private final HashSet<String> mPermissionTable = new HashSet<>();
    460 
    461 
    462 
    463     // The application context is the most important object this class provides to the system
    464     // under test.
    465     private final Context mContext = spy(new FakeContext());
    466 
    467     // We then create a spy on the application context allowing standard Mockito-style
    468     // when(...) logic to be used to add specific little responses where needed.
    469 
    470     private final Resources mResources = mock(Resources.class);
    471     private final PackageManager mPackageManager = mock(PackageManager.class);
    472     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
    473     private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
    474     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
    475     private final UserManager mUserManager = mock(UserManager.class);
    476     private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
    477     private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
    478     private final AlarmManager mAlarmManager = mock(AlarmManager.class);
    479     private final ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
    480     private final UsageStatsManager mUsageStatManager = null;
    481     private final WifiManager mWifiManager = mock(WifiManager.class);
    482 
    483     private final ContentProvider mContentProvider = spy(new FakeContentProvider());
    484 
    485     private final Configuration mConfiguration = new Configuration();
    486     private final SharedPreferences mSharedPreferences = PreferenceManager
    487             .getDefaultSharedPreferences(TestApplication.getAppContext());
    488     private final MockContentResolver mContentResolver = new MockContentResolver();
    489     private final PersistableBundle mBundle = new PersistableBundle();
    490 
    491     public ContextFixture() {
    492         MockitoAnnotations.initMocks(this);
    493 
    494         doAnswer(new Answer<List<ResolveInfo>>() {
    495             @Override
    496             public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
    497                 return doQueryIntentServices(
    498                         (Intent) invocation.getArguments()[0],
    499                         (Integer) invocation.getArguments()[1]);
    500             }
    501         }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt());
    502 
    503         doAnswer(new Answer<List<ResolveInfo>>() {
    504             @Override
    505             public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
    506                 return doQueryIntentServices(
    507                         (Intent) invocation.getArguments()[0],
    508                         (Integer) invocation.getArguments()[1]);
    509             }
    510         }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
    511 
    512         doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
    513         //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt());
    514         doReturn(mBundle).when(mCarrierConfigManager).getConfig();
    515 
    516         mConfiguration.locale = Locale.US;
    517         doReturn(mConfiguration).when(mResources).getConfiguration();
    518 
    519         mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
    520         mPermissionTable.add(PERMISSION_ENABLE_ALL);
    521     }
    522 
    523     @Override
    524     public Context getTestDouble() {
    525         return mContext;
    526     }
    527 
    528     public void putResource(int id, final String value) {
    529         when(mResources.getText(eq(id))).thenReturn(value);
    530         when(mResources.getString(eq(id))).thenReturn(value);
    531         when(mResources.getString(eq(id), any())).thenAnswer(new Answer<String>() {
    532             @Override
    533             public String answer(InvocationOnMock invocation) {
    534                 Object[] args = invocation.getArguments();
    535                 return String.format(value, Arrays.copyOfRange(args, 1, args.length));
    536             }
    537         });
    538     }
    539 
    540     public void putBooleanResource(int id, boolean value) {
    541         when(mResources.getBoolean(eq(id))).thenReturn(value);
    542     }
    543 
    544     public void putStringArrayResource(int id, String[] values) {
    545         doReturn(values).when(mResources).getStringArray(eq(id));
    546     }
    547 
    548     public void putIntArrayResource(int id, int[] values) {
    549         doReturn(values).when(mResources).getIntArray(eq(id));
    550     }
    551 
    552     public PersistableBundle getCarrierConfigBundle() {
    553         return mBundle;
    554     }
    555 
    556     private void addService(String action, ComponentName name, IInterface service) {
    557         mComponentNamesByAction.put(action, name);
    558         mServiceByComponentName.put(name, service);
    559         mComponentNameByService.put(service, name);
    560     }
    561 
    562     private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
    563         List<ResolveInfo> result = new ArrayList<ResolveInfo>();
    564         for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
    565             ResolveInfo resolveInfo = new ResolveInfo();
    566             resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
    567             result.add(resolveInfo);
    568         }
    569         return result;
    570     }
    571 
    572     public void sendBroadcastToOrderedBroadcastReceivers() {
    573         synchronized (mOrderedBroadcastReceivers) {
    574             // having a map separate from mOrderedBroadcastReceivers is helpful here as onReceive()
    575             // call within the loop may lead to sendOrderedBroadcast() which can add to
    576             // mOrderedBroadcastReceivers
    577             Collection<Map.Entry<Intent, BroadcastReceiver>> map =
    578                     mOrderedBroadcastReceivers.entries();
    579             for (Map.Entry<Intent, BroadcastReceiver> entry : map) {
    580                 entry.getValue().onReceive(mContext, entry.getKey());
    581                 mOrderedBroadcastReceivers.remove(entry.getKey(), entry.getValue());
    582             }
    583         }
    584     }
    585 
    586     public void addCallingOrSelfPermission(String permission) {
    587         synchronized (mPermissionTable) {
    588             if (mPermissionTable != null && permission != null) {
    589                 mPermissionTable.remove(PERMISSION_ENABLE_ALL);
    590                 mPermissionTable.add(permission);
    591             }
    592         }
    593     }
    594 
    595     public void removeCallingOrSelfPermission(String permission) {
    596         synchronized (mPermissionTable) {
    597             if (mPermissionTable != null && permission != null) {
    598                 mPermissionTable.remove(permission);
    599             }
    600         }
    601     }
    602 
    603     private static void logd(String s) {
    604         Log.d(TAG, s);
    605     }
    606 }
    607