1/*
2 * Copyright (C) 2009 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
17package com.android.server.accounts;
18
19import static org.mockito.Matchers.eq;
20import static org.mockito.Mockito.mock;
21import static org.mockito.Mockito.when;
22
23import android.accounts.Account;
24import android.accounts.AccountManagerInternal;
25import android.accounts.AuthenticatorDescription;
26import android.app.AppOpsManager;
27import android.app.Notification;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.pm.RegisteredServicesCache.ServiceInfo;
34import android.content.pm.RegisteredServicesCacheListener;
35import android.content.pm.UserInfo;
36import android.database.Cursor;
37import android.database.DatabaseErrorHandler;
38import android.database.sqlite.SQLiteDatabase;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.UserHandle;
42import android.os.UserManager;
43import android.test.AndroidTestCase;
44import android.test.mock.MockContext;
45import android.test.mock.MockPackageManager;
46import android.util.Log;
47
48import com.android.server.LocalServices;
49
50import java.io.File;
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.Collection;
56import java.util.Comparator;
57
58public class AccountManagerServiceTest extends AndroidTestCase {
59    private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
60
61    static final String PREN_DB = "pren.db";
62    static final String DE_DB = "de.db";
63    static final String CE_DB = "ce.db";
64    private AccountManagerService mAms;
65
66    @Override
67    protected void setUp() throws Exception {
68        Context realTestContext = getContext();
69        Context mockContext = new MyMockContext(realTestContext);
70        setContext(mockContext);
71        mAms = createAccountManagerService(mockContext, realTestContext);
72    }
73
74    @Override
75    protected void tearDown() throws Exception {
76        SQLiteDatabase.deleteDatabase(new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM)));
77        SQLiteDatabase.deleteDatabase(new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM)));
78        SQLiteDatabase.deleteDatabase(new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM)));
79        LocalServices.removeServiceForTest(AccountManagerInternal.class);
80        super.tearDown();
81    }
82
83    public class AccountSorter implements Comparator<Account> {
84        public int compare(Account object1, Account object2) {
85            if (object1 == object2) return 0;
86            if (object1 == null) return 1;
87            if (object2 == null) return -1;
88            int result = object1.type.compareTo(object2.type);
89            if (result != 0) return result;
90            return object1.name.compareTo(object2.name);
91        }
92    }
93
94    public void testCheckAddAccount() throws Exception {
95        unlockSystemUser();
96        Account a11 = new Account("account1", "type1");
97        Account a21 = new Account("account2", "type1");
98        Account a31 = new Account("account3", "type1");
99        Account a12 = new Account("account1", "type2");
100        Account a22 = new Account("account2", "type2");
101        Account a32 = new Account("account3", "type2");
102        mAms.addAccountExplicitly(a11, "p11", null);
103        mAms.addAccountExplicitly(a12, "p12", null);
104        mAms.addAccountExplicitly(a21, "p21", null);
105        mAms.addAccountExplicitly(a22, "p22", null);
106        mAms.addAccountExplicitly(a31, "p31", null);
107        mAms.addAccountExplicitly(a32, "p32", null);
108
109        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
110        Arrays.sort(accounts, new AccountSorter());
111        assertEquals(6, accounts.length);
112        assertEquals(a11, accounts[0]);
113        assertEquals(a21, accounts[1]);
114        assertEquals(a31, accounts[2]);
115        assertEquals(a12, accounts[3]);
116        assertEquals(a22, accounts[4]);
117        assertEquals(a32, accounts[5]);
118
119        accounts = mAms.getAccounts("type1", mContext.getOpPackageName());
120        Arrays.sort(accounts, new AccountSorter());
121        assertEquals(3, accounts.length);
122        assertEquals(a11, accounts[0]);
123        assertEquals(a21, accounts[1]);
124        assertEquals(a31, accounts[2]);
125
126        mAms.removeAccountInternal(a21);
127
128        accounts = mAms.getAccounts("type1", mContext.getOpPackageName());
129        Arrays.sort(accounts, new AccountSorter());
130        assertEquals(2, accounts.length);
131        assertEquals(a11, accounts[0]);
132        assertEquals(a31, accounts[1]);
133    }
134
135    public void testPasswords() throws Exception {
136        unlockSystemUser();
137        Account a11 = new Account("account1", "type1");
138        Account a12 = new Account("account1", "type2");
139        mAms.addAccountExplicitly(a11, "p11", null);
140        mAms.addAccountExplicitly(a12, "p12", null);
141
142        assertEquals("p11", mAms.getPassword(a11));
143        assertEquals("p12", mAms.getPassword(a12));
144
145        mAms.setPassword(a11, "p11b");
146
147        assertEquals("p11b", mAms.getPassword(a11));
148        assertEquals("p12", mAms.getPassword(a12));
149    }
150
151    public void testUserdata() throws Exception {
152        unlockSystemUser();
153        Account a11 = new Account("account1", "type1");
154        Bundle u11 = new Bundle();
155        u11.putString("a", "a_a11");
156        u11.putString("b", "b_a11");
157        u11.putString("c", "c_a11");
158        Account a12 = new Account("account1", "type2");
159        Bundle u12 = new Bundle();
160        u12.putString("a", "a_a12");
161        u12.putString("b", "b_a12");
162        u12.putString("c", "c_a12");
163        mAms.addAccountExplicitly(a11, "p11", u11);
164        mAms.addAccountExplicitly(a12, "p12", u12);
165
166        assertEquals("a_a11", mAms.getUserData(a11, "a"));
167        assertEquals("b_a11", mAms.getUserData(a11, "b"));
168        assertEquals("c_a11", mAms.getUserData(a11, "c"));
169        assertEquals("a_a12", mAms.getUserData(a12, "a"));
170        assertEquals("b_a12", mAms.getUserData(a12, "b"));
171        assertEquals("c_a12", mAms.getUserData(a12, "c"));
172
173        mAms.setUserData(a11, "b", "b_a11b");
174        mAms.setUserData(a12, "c", null);
175
176        assertEquals("a_a11", mAms.getUserData(a11, "a"));
177        assertEquals("b_a11b", mAms.getUserData(a11, "b"));
178        assertEquals("c_a11", mAms.getUserData(a11, "c"));
179        assertEquals("a_a12", mAms.getUserData(a12, "a"));
180        assertEquals("b_a12", mAms.getUserData(a12, "b"));
181        assertNull(mAms.getUserData(a12, "c"));
182    }
183
184    public void testAuthtokens() throws Exception {
185        unlockSystemUser();
186        Account a11 = new Account("account1", "type1");
187        Account a12 = new Account("account1", "type2");
188        mAms.addAccountExplicitly(a11, "p11", null);
189        mAms.addAccountExplicitly(a12, "p12", null);
190
191        mAms.setAuthToken(a11, "att1", "a11_att1");
192        mAms.setAuthToken(a11, "att2", "a11_att2");
193        mAms.setAuthToken(a11, "att3", "a11_att3");
194        mAms.setAuthToken(a12, "att1", "a12_att1");
195        mAms.setAuthToken(a12, "att2", "a12_att2");
196        mAms.setAuthToken(a12, "att3", "a12_att3");
197
198        assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1"));
199        assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2"));
200        assertEquals("a11_att3", mAms.peekAuthToken(a11, "att3"));
201        assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1"));
202        assertEquals("a12_att2", mAms.peekAuthToken(a12, "att2"));
203        assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3"));
204
205        mAms.setAuthToken(a11, "att3", "a11_att3b");
206        mAms.invalidateAuthToken(a12.type, "a12_att2");
207
208        assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1"));
209        assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2"));
210        assertEquals("a11_att3b", mAms.peekAuthToken(a11, "att3"));
211        assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1"));
212        assertNull(mAms.peekAuthToken(a12, "att2"));
213        assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3"));
214
215        assertNull(mAms.peekAuthToken(a12, "att2"));
216    }
217
218    public void testRemovedAccountSync() throws Exception {
219        unlockSystemUser();
220        Account a1 = new Account("account1", "type1");
221        Account a2 = new Account("account2", "type2");
222        mAms.addAccountExplicitly(a1, "p1", null);
223        mAms.addAccountExplicitly(a2, "p2", null);
224
225        Context originalContext = ((MyMockContext)getContext()).mTestContext;
226        // create a separate instance of AMS. It initially assumes that user0 is locked
227        AccountManagerService ams2 = createAccountManagerService(getContext(), originalContext);
228
229        // Verify that account can be removed when user is locked
230        ams2.removeAccountInternal(a1);
231        Account[] accounts = ams2.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
232        assertEquals(1, accounts.length);
233        assertEquals("Only a2 should be returned", a2, accounts[0]);
234
235        // Verify that CE db file is unchanged and still has 2 accounts
236        String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM);
237        int accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName);
238        assertEquals("CE database should still have 2 accounts", 2, accountsNumber);
239
240        // Unlock the user and verify that db has been updated
241        ams2.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM));
242        accountsNumber = readNumberOfAccountsFromDbFile(originalContext, ceDatabaseName);
243        assertEquals("CE database should now have 1 account", 2, accountsNumber);
244        accounts = ams2.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
245        assertEquals(1, accounts.length);
246        assertEquals("Only a2 should be returned", a2, accounts[0]);
247    }
248
249    public void testPreNDatabaseMigration() throws Exception {
250        String preNDatabaseName = mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM);
251        Context originalContext = ((MyMockContext) getContext()).mTestContext;
252        PreNTestDatabaseHelper.createV4Database(originalContext, preNDatabaseName);
253        // Assert that database was created with 1 account
254        int n = readNumberOfAccountsFromDbFile(originalContext, preNDatabaseName);
255        assertEquals("pre-N database should have 1 account", 1, n);
256
257        // Start testing
258        unlockSystemUser();
259        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
260        assertEquals("1 account should be migrated", 1, accounts.length);
261        assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name);
262        assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0]));
263        assertEquals("Authtoken should be migrated",
264                PreNTestDatabaseHelper.TOKEN_STRING,
265                mAms.peekAuthToken(accounts[0], PreNTestDatabaseHelper.TOKEN_TYPE));
266
267        assertFalse("pre-N database file should be removed but was found at " + preNDatabaseName,
268                new File(preNDatabaseName).exists());
269
270        // Verify that ce/de files are present
271        String deDatabaseName = mAms.getDeDatabaseName(UserHandle.USER_SYSTEM);
272        String ceDatabaseName = mAms.getCeDatabaseName(UserHandle.USER_SYSTEM);
273        assertTrue("DE database file should be created at " + deDatabaseName,
274                new File(deDatabaseName).exists());
275        assertTrue("CE database file should be created at " + ceDatabaseName,
276                new File(ceDatabaseName).exists());
277    }
278
279    private int readNumberOfAccountsFromDbFile(Context context, String dbName) {
280        SQLiteDatabase ceDb = context.openOrCreateDatabase(dbName, 0, null);
281        try (Cursor cursor = ceDb.rawQuery("SELECT count(*) FROM accounts", null)) {
282            assertTrue(cursor.moveToNext());
283            return cursor.getInt(0);
284        }
285    }
286
287    private AccountManagerService createAccountManagerService(Context mockContext,
288            Context realContext) {
289        LocalServices.removeServiceForTest(AccountManagerInternal.class);
290        return new MyAccountManagerService(mockContext,
291                new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realContext);
292    }
293
294    private void unlockSystemUser() {
295        mAms.onUserUnlocked(newIntentForUser(UserHandle.USER_SYSTEM));
296    }
297
298    private static Intent newIntentForUser(int userId) {
299        Intent intent = new Intent();
300        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
301        return intent;
302    }
303
304    static public class MockAccountAuthenticatorCache implements IAccountAuthenticatorCache {
305        private ArrayList<ServiceInfo<AuthenticatorDescription>> mServices;
306
307        public MockAccountAuthenticatorCache() {
308            mServices = new ArrayList<>();
309            AuthenticatorDescription d1 = new AuthenticatorDescription("type1", "p1", 0, 0, 0, 0);
310            AuthenticatorDescription d2 = new AuthenticatorDescription("type2", "p2", 0, 0, 0, 0);
311            mServices.add(new ServiceInfo<>(d1, null, null));
312            mServices.add(new ServiceInfo<>(d2, null, null));
313        }
314
315        @Override
316        public ServiceInfo<AuthenticatorDescription> getServiceInfo(
317                AuthenticatorDescription type, int userId) {
318            for (ServiceInfo<AuthenticatorDescription> service : mServices) {
319                if (service.type.equals(type)) {
320                    return service;
321                }
322            }
323            return null;
324        }
325
326        @Override
327        public Collection<ServiceInfo<AuthenticatorDescription>> getAllServices(int userId) {
328            return mServices;
329        }
330
331        @Override
332        public void dump(
333                final FileDescriptor fd, final PrintWriter fout, final String[] args, int userId) {
334        }
335
336        @Override
337        public void setListener(
338                final RegisteredServicesCacheListener<AuthenticatorDescription> listener,
339                final Handler handler) {
340        }
341
342        @Override
343        public void invalidateCache(int userId) {
344        }
345
346        @Override
347        public void updateServices(int userId) {
348        }
349    }
350
351    static public class MyMockContext extends MockContext {
352        private Context mTestContext;
353        private AppOpsManager mAppOpsManager;
354        private UserManager mUserManager;
355        private PackageManager mPackageManager;
356
357        public MyMockContext(Context testContext) {
358            this.mTestContext = testContext;
359            this.mAppOpsManager = mock(AppOpsManager.class);
360            this.mUserManager = mock(UserManager.class);
361            this.mPackageManager = mock(PackageManager.class);
362            final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0);
363            when(mUserManager.getUserInfo(eq(ui.id))).thenReturn(ui);
364        }
365
366        @Override
367        public int checkCallingOrSelfPermission(final String permission) {
368            return PackageManager.PERMISSION_GRANTED;
369        }
370
371        @Override
372        public PackageManager getPackageManager() {
373            return mPackageManager;
374        }
375
376        @Override
377        public Object getSystemService(String name) {
378            if (Context.APP_OPS_SERVICE.equals(name)) {
379                return mAppOpsManager;
380            } else if( Context.USER_SERVICE.equals(name)) {
381                return mUserManager;
382            }
383            return null;
384        }
385
386        @Override
387        public String getSystemServiceName(Class<?> serviceClass) {
388            if (AppOpsManager.class.equals(serviceClass)) {
389                return Context.APP_OPS_SERVICE;
390            }
391            return null;
392        }
393
394        @Override
395        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
396            return null;
397        }
398
399        @Override
400        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
401                IntentFilter filter, String broadcastPermission, Handler scheduler) {
402            return null;
403        }
404
405        @Override
406        public SQLiteDatabase openOrCreateDatabase(String file, int mode,
407                SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
408            Log.i(TAG, "openOrCreateDatabase " + file + " mode " + mode);
409            return mTestContext.openOrCreateDatabase(file, mode, factory,errorHandler);
410        }
411
412        @Override
413        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
414            Log.i(TAG, "sendBroadcastAsUser " + intent + " " + user);
415        }
416
417        @Override
418        public String getOpPackageName() {
419            return null;
420        }
421    }
422
423    static public class MyMockPackageManager extends MockPackageManager {
424        @Override
425        public int checkSignatures(final int uid1, final int uid2) {
426            return PackageManager.SIGNATURE_MATCH;
427        }
428
429        @Override
430        public void addOnPermissionsChangeListener(
431                OnPermissionsChangedListener listener) {
432        }
433    }
434
435    static public class MyAccountManagerService extends AccountManagerService {
436        private Context mRealTestContext;
437        public MyAccountManagerService(Context context, PackageManager packageManager,
438                IAccountAuthenticatorCache authenticatorCache, Context realTestContext) {
439            super(context, packageManager, authenticatorCache);
440            this.mRealTestContext = realTestContext;
441        }
442
443        @Override
444        protected void installNotification(final int notificationId, final Notification n, UserHandle user) {
445        }
446
447        @Override
448        protected void cancelNotification(final int id, UserHandle user) {
449        }
450
451        @Override
452        protected String getCeDatabaseName(int userId) {
453            return new File(mRealTestContext.getCacheDir(), CE_DB).getPath();
454        }
455
456        @Override
457        protected String getDeDatabaseName(int userId) {
458            return new File(mRealTestContext.getCacheDir(), DE_DB).getPath();
459        }
460
461        @Override
462        String getPreNDatabaseName(int userId) {
463            return new File(mRealTestContext.getCacheDir(), PREN_DB).getPath();
464        }
465    }
466}
467