1 /*
2  * Copyright (C) 2023 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.launcher3.provider;
18 
19 import static android.content.pm.LauncherApps.EXTRA_PIN_ITEM_REQUEST;
20 
21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
22 
23 import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
24 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.mockito.Mockito.doAnswer;
29 import static org.mockito.Mockito.mock;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.ContentValues;
33 import android.content.Context;
34 import android.content.ContextWrapper;
35 import android.content.Intent;
36 import android.content.pm.LauncherApps.PinItemRequest;
37 import android.content.pm.ShortcutInfo;
38 import android.content.pm.ShortcutManager;
39 import android.database.Cursor;
40 import android.database.sqlite.SQLiteDatabase;
41 import android.net.Uri;
42 import android.os.Process;
43 
44 import com.android.launcher3.LauncherSettings.Favorites;
45 import com.android.launcher3.R;
46 import com.android.launcher3.model.DatabaseHelper;
47 import com.android.launcher3.model.DbDowngradeHelper;
48 import com.android.launcher3.pm.UserCache;
49 import com.android.launcher3.settings.SettingsActivity;
50 import com.android.launcher3.shortcuts.ShortcutKey;
51 import com.android.launcher3.util.IOUtils;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.mockito.ArgumentCaptor;
56 
57 import java.io.InputStream;
58 
59 /**
60  * Test for {@link LauncherDbUtils}.
61  */
62 public class LauncherDbUtilsTest {
63 
64     private Context mContext;
65     private ArgumentCaptor<ShortcutInfo> mInfoArgumentCaptor;
66     private boolean mPinningAllowed = true;
67 
68     @Before
setup()69     public void setup() {
70         PinItemRequest req = mock(PinItemRequest.class);
71         doAnswer(i -> mPinningAllowed).when(req).accept();
72 
73         mInfoArgumentCaptor = ArgumentCaptor.forClass(ShortcutInfo.class);
74         ShortcutManager sm = mock(ShortcutManager.class);
75         when(sm.createShortcutResultIntent(mInfoArgumentCaptor.capture()))
76                 .thenReturn(new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, req));
77 
78         mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
79 
80             @Override
81             public Object getSystemService(String name) {
82                 return SHORTCUT_SERVICE.equals(name) ? sm : super.getSystemService(name);
83             }
84         };
85     }
86 
87     @Test
migrateLegacyShortcuts_invalidIntent()88     public void migrateLegacyShortcuts_invalidIntent() throws Exception {
89         SQLiteDatabase db = setupLegacyShortcut(null);
90         assertEquals(1, getFavoriteDataCount(db));
91 
92         LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
93 
94         assertEquals(0, getFavoriteDataCount(db));
95     }
96 
97     @Test
migrateLegacyShortcuts_protectedIntent()98     public void migrateLegacyShortcuts_protectedIntent() throws Exception {
99         Intent intent = new Intent(Intent.ACTION_CALL)
100                 .setData(Uri.parse("tel:0000000000"));
101         SQLiteDatabase db = setupLegacyShortcut(intent);
102         assertEquals(1, getFavoriteDataCount(db));
103 
104         LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
105 
106         assertEquals(0, getFavoriteDataCount(db));
107     }
108 
109     @Test
migrateLegacyShortcuts_pinningDisabled()110     public void migrateLegacyShortcuts_pinningDisabled() throws Exception {
111         mPinningAllowed = false;
112         Intent intent = new Intent(mContext, SettingsActivity.class);
113         SQLiteDatabase db = setupLegacyShortcut(intent);
114         assertEquals(1, getFavoriteDataCount(db));
115 
116         LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
117 
118         assertEquals(0, getFavoriteDataCount(db));
119     }
120 
121     @Test
migrateLegacyShortcuts_success()122     public void migrateLegacyShortcuts_success() throws Exception {
123         Intent intent = new Intent(mContext, SettingsActivity.class);
124         SQLiteDatabase db = setupLegacyShortcut(intent);
125         assertEquals(1, getFavoriteDataCount(db));
126 
127         LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
128 
129         assertEquals(1, getFavoriteDataCount(db));
130         ShortcutInfo info = mInfoArgumentCaptor.getValue();
131         assertNotNull(info);
132         try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
133             c.moveToNext();
134             assertEquals(Favorites.ITEM_TYPE_DEEP_SHORTCUT, c.getInt(c.getColumnIndex(ITEM_TYPE)));
135 
136             ShortcutKey key = ShortcutKey.fromIntent(
137                     Intent.parseUri(c.getString(c.getColumnIndex(INTENT)), 0),
138                     Process.myUserHandle());
139 
140             assertEquals(info.getId(), key.getId());
141             assertEquals(info.getPackage(), key.getPackageName());
142         }
143     }
144 
setupLegacyShortcut(Intent intent)145     private SQLiteDatabase setupLegacyShortcut(Intent intent) throws Exception {
146         SQLiteDatabase db = new MyDatabaseHelper().getWritableDatabase();
147         try (InputStream in = mContext.getResources().openRawResource(R.raw.downgrade_schema)) {
148             DbDowngradeHelper.parse(IOUtils.toByteArray(in)).onDowngrade(db, db.getVersion(), 31);
149         }
150 
151         ContentValues cv = new ContentValues();
152         cv.put("itemType", 1);
153         cv.put("title", "Hello");
154         cv.put("intent", intent == null ? null : intent.toUri(0));
155         db.insert(Favorites.TABLE_NAME, null, cv);
156         return db;
157     }
158 
getFavoriteDataCount(SQLiteDatabase db)159     public static int getFavoriteDataCount(SQLiteDatabase db) {
160         try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
161             return c.getCount();
162         }
163     }
164 
165     private class MyDatabaseHelper extends DatabaseHelper {
166 
MyDatabaseHelper()167         MyDatabaseHelper() {
168             super(mContext, null, UserCache.INSTANCE.get(mContext)::getSerialNumberForUser,
169                     () -> { });
170         }
171 
172         @Override
handleOneTimeDataUpgrade(SQLiteDatabase db)173         protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
174     }
175 }
176