1/*
2 * Copyright (C) 2010 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.providers.applications;
18
19import android.app.SearchManager;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.database.Cursor;
24import android.net.Uri;
25import android.provider.Applications;
26import android.test.ProviderTestCase2;
27import android.test.suitebuilder.annotation.SmallTest;
28
29import java.util.concurrent.FutureTask;
30
31
32/**
33 * Instrumentation test for the ApplicationsProvider.
34 *
35 * The tests use an IsolatedContext, and are not affected by the real list of
36 * applications on the device. The ApplicationsProvider's persistent database
37 * is also created in an isolated context so it doesn't interfere with the
38 * database of the actual ApplicationsProvider installed on the device.
39 */
40@SmallTest
41public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProviderForTesting> {
42
43    private ApplicationsProviderForTesting mProvider;
44
45    private MockActivityManager mMockActivityManager;
46
47    public ApplicationsProviderTest() {
48        super(ApplicationsProviderForTesting.class, Applications.AUTHORITY);
49    }
50
51    @Override
52    protected void setUp() throws Exception {
53        super.setUp();
54        mProvider = getProvider();
55        mMockActivityManager = new MockActivityManager();
56    }
57
58    /**
59     * Ensures that the ApplicationsProvider is in a ready-to-test state.
60     */
61    private void initProvider(ApplicationsProviderForTesting provider) throws Exception {
62        // Decouple the provider from Android's real list of applications.
63        MockPackageManager mockPackageManager = new MockPackageManager();
64        addDefaultTestPackages(mockPackageManager);
65
66        initProvider(provider, mockPackageManager);
67    }
68
69    private void initProvider(ApplicationsProviderForTesting provider,
70            MockPackageManager mockPackageManager) throws Exception {
71        provider.setMockPackageManager(mockPackageManager);
72        provider.setMockActivityManager(mMockActivityManager);
73
74        assertTrue(provider.dispatchNextMessage());
75    }
76
77    /**
78     * Register a few default applications with the ApplicationsProvider that
79     * tests can query.
80     */
81    private void addDefaultTestPackages(MockPackageManager mockPackageManager) {
82        mockPackageManager.addPackage(
83                "Email", new ComponentName("com.android.email", "com.android.email.MainView"));
84        mockPackageManager.addPackage(
85                "Ebay", new ComponentName("com.android.ebay", "com.android.ebay.Shopping"));
86        mockPackageManager.addPackage(
87                "Fakeapp", new ComponentName("com.android.fakeapp", "com.android.fakeapp.FakeView"));
88
89        // Apps that can be used to test ordering.
90        mockPackageManager.addPackage("AlphabeticA", new ComponentName("a", "a.AView"));
91        mockPackageManager.addPackage("AlphabeticB", new ComponentName("b", "b.BView"));
92        mockPackageManager.addPackage("AlphabeticC", new ComponentName("c", "c.CView"));
93        mockPackageManager.addPackage("AlphabeticD", new ComponentName("d", "d.DView"));
94        mockPackageManager.addPackage("AlphabeticD2", new ComponentName("d", "d.DView2"));
95    }
96
97    public void testSearch_singleResult() throws Exception {
98        initProvider(mProvider);
99        testSearch("ema", "Email");
100    }
101
102    public void testSearch_multipleResults() throws Exception {
103        initProvider(mProvider);
104        testSearch("e", "Ebay", "Email");
105    }
106
107    public void testSearch_noResults() throws Exception {
108        initProvider(mProvider);
109        testSearch("nosuchapp");
110    }
111
112    public void testSearch_orderingIsAlphabeticByDefault() throws Exception {
113        initProvider(mProvider);
114        testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD",
115                "AlphabeticD2");
116    }
117
118    public void testSearch_emptySearchQueryReturnsEverything() throws Exception {
119        initProvider(mProvider);
120        testSearch("",
121                "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD", "AlphabeticD2",
122                "Ebay", "Email", "Fakeapp");
123    }
124
125    public void testSearch_appsAreRankedByUsageTimeOnStartup() throws Exception {
126        mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
127        mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
128        // Missing usage time for "a".
129        mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
130
131        // Launch count database is populated on startup.
132        mProvider.setHasGlobalSearchPermission(true);
133
134        initProvider(mProvider);
135
136        // New ranking: D, B, A, C (first by launch count, then
137        // - if the launch counts of two apps are equal - alphabetically)
138        testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC",
139                "AlphabeticD2");
140    }
141
142    public void testSearch_appsAreRankedByResumeTimeAfterUpdate() throws Exception {
143        initProvider(mProvider);
144        mProvider.setHasGlobalSearchPermission(true);
145
146        mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
147        mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
148        // Missing launch count for "a".
149        mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
150
151        // Fetch new data from usage stat provider (in the real instance this
152        // is triggered by a zero-query from global search).
153        mProvider.updateUsageStats();
154
155        // New ranking: D, B, A, C (first by launch count, then
156        // - if the launch counts of two apps are equal - alphabetically)
157        testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC",
158                "AlphabeticD2");
159    }
160
161    public void testSearch_noLastAccessTimesWithoutPermission() throws Exception {
162        initProvider(mProvider);
163        mProvider.setHasGlobalSearchPermission(false);
164        mMockActivityManager.addLastResumeTime("d", "d.DView", 1);
165        mMockActivityManager.addLastResumeTime("b", "b.BView", 2);
166        mMockActivityManager.addLastResumeTime("c", "c.CView", 3);
167
168        assertNull(getGlobalSearchCursor("", false));
169
170        Cursor cursor = getGlobalSearchCursor("alphabetic", false);
171        assertEquals(-1, cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT));
172    }
173
174    public void testSearch_lastAccessTimes() throws Exception {
175        initProvider(mProvider);
176        mProvider.setHasGlobalSearchPermission(true);
177        mMockActivityManager.addLastResumeTime("d", "d.DView", 1);
178        mMockActivityManager.addLastResumeTime("b", "b.BView", 2);
179        mMockActivityManager.addLastResumeTime("c", "c.CView", 3);
180
181        testLastAccessTimes("", true, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0);
182
183        testLastAccessTimes("alphabetic", true, 3, 2, 1, 0, 0);
184
185        // Update the last resume time of "c".
186        mMockActivityManager.addLastResumeTime("c", "c.CView", 5);
187        // Without a refresh, we should see the same results as before.
188        testLastAccessTimes("alphabetic", false, 3, 2, 1, 0, 0);
189        // If we refresh, we should see the change.
190        testLastAccessTimes("alphabetic", true, 5, 2, 1, 0, 0);
191    }
192
193    /**
194     * The ApplicationsProvider must only rank by launch count if the caller
195     * is a privileged application - ordering apps by launch count when asked
196     * by a regular application would leak information about user behavior.
197     */
198    public void testSearch_notAllowedToRankByLaunchCount() throws Exception {
199        initProvider(mProvider);
200        // Simulate non-privileged calling application.
201        mProvider.setHasGlobalSearchPermission(false);
202
203        mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
204        mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
205        mMockActivityManager.addLastResumeTime("a", "a.AView", 0);
206        mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
207
208        // Fetch new data from launch count provider.
209        mProvider.updateUsageStats();
210
211        // Launch count information mustn't be leaked - ranking is still
212        // alphabetic.
213        testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD",
214                "AlphabeticD2");
215    }
216
217    public void testSearch_disabledPackage() throws Exception {
218        MockPackageManager mockPackageManager = new MockPackageManager();
219        mockPackageManager.addPackage("DisabledPackageApp1",
220                new ComponentName("dp", "dp.DisView1"));
221        mockPackageManager.addPackage("DisabledPackageApp2",
222                new ComponentName("dp", "dp.DisView2"),
223                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
224                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
225        initProvider(mProvider, mockPackageManager);
226
227        mProvider.setHasGlobalSearchPermission(true);
228        testSearch("dis");
229    }
230
231    public void testSearch_disabledComponent() throws Exception {
232        MockPackageManager mockPackageManager = new MockPackageManager();
233        mockPackageManager.addPackage("DisabledApp1", new ComponentName("da", "da.DaView1"),
234                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
235                PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
236        mockPackageManager.addPackage("DisabledApp2", new ComponentName("da", "da.DaView2"),
237                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
238                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
239        initProvider(mProvider, mockPackageManager);
240
241        mProvider.setHasGlobalSearchPermission(true);
242        testSearch("dis", "DisabledApp2");
243    }
244
245    private void testSearch(String searchQuery, String... expectedResultsInOrder) {
246        Cursor cursor = Applications.search(getMockContentResolver(), searchQuery);
247
248        assertNotNull(cursor);
249        assertFalse(cursor.isClosed());
250
251        verifySearchResults(cursor, expectedResultsInOrder);
252
253        cursor.close();
254    }
255
256    private void verifySearchResults(Cursor cursor, String... expectedResultsInOrder) {
257        int expectedResultCount = expectedResultsInOrder.length;
258        assertEquals("Wrong number of app search results.",
259                expectedResultCount, cursor.getCount());
260
261        if (expectedResultCount > 0) {
262            cursor.moveToFirst();
263            int nameIndex = cursor.getColumnIndex(ApplicationsProvider.NAME);
264            // Verify that the actual results match the expected ones.
265            for (int i = 0; i < cursor.getCount(); i++) {
266                assertEquals("Wrong search result at position " + i,
267                        expectedResultsInOrder[i], cursor.getString(nameIndex));
268                cursor.moveToNext();
269            }
270        }
271    }
272
273    private Cursor getGlobalSearchCursor(String searchQuery, boolean refresh) {
274        Uri.Builder uriBuilder = Applications.CONTENT_URI.buildUpon();
275        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath(searchQuery);
276        if (refresh) {
277            uriBuilder.appendQueryParameter(ApplicationsProvider.REFRESH_STATS, "");
278        }
279        return getMockContentResolver().query(uriBuilder.build(), null, null, null, null);
280    }
281
282    private void testLastAccessTimes(String searchQuery, boolean refresh,
283            int... expectedLastAccessTimesInOrder) {
284        Cursor cursor = getGlobalSearchCursor(searchQuery, refresh);
285
286        assertNotNull(cursor);
287        assertFalse(cursor.isClosed());
288
289        verifyLastAccessTimes(cursor, expectedLastAccessTimesInOrder);
290
291        cursor.close();
292    }
293
294    private void verifyLastAccessTimes(Cursor cursor, int... expectedLastAccessTimesInOrder) {
295        cursor.moveToFirst();
296        int lastAccessTimeIndex = cursor.getColumnIndex(
297                SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT);
298        // Verify that the actual results match the expected ones.
299        for (int i = 0; i < cursor.getCount(); i++) {
300            assertEquals("Wrong last-access time at position " + i,
301                    expectedLastAccessTimesInOrder[i], cursor.getInt(lastAccessTimeIndex));
302            cursor.moveToNext();
303        }
304    }
305}
306