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.example.sampleleanbacklauncher.apps;
18 
19 import android.content.ComponentName;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteCursor;
24 import android.database.sqlite.SQLiteCursorDriver;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.database.sqlite.SQLiteQuery;
28 import android.os.Trace;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.WorkerThread;
31 import android.util.ArrayMap;
32 
33 import java.util.Date;
34 import java.util.Map;
35 
36 @WorkerThread
37 public class LaunchItemsDbHelper extends SQLiteOpenHelper {
38 
39     // Increment whenever schema changes
40     private static final int DATABASE_VERSION = 1;
41     // Database name
42     private static final String DATABASE_NAME = "launch_items";
43 
44     private static final String CREATE_APP_TABLE =
45             "CREATE TABLE IF NOT EXISTS " + AppDbItem.TABLE_NAME + "("
46                 + AppDbItem.COLUMN_COMPONENT + " TEXT NOT NULL PRIMARY KEY , "
47                 + AppDbItem.COLUMN_ORDER_PRIORITY + " INTEGER NOT NULL DEFAULT 0 , "
48                 + AppDbItem.COLUMN_LAST_OPEN + " INTEGER "
49             + " )";
50 
LaunchItemsDbHelper(Context context)51     public LaunchItemsDbHelper(Context context) {
52         super(context, DATABASE_NAME, new SQLiteDatabase.CursorFactory() {
53             @Override
54             public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
55                                     String editTable, SQLiteQuery query) {
56                 return new SQLiteCursor(masterQuery, editTable, query);
57             }
58         }, DATABASE_VERSION);
59     }
60 
61     @Override
onCreate(SQLiteDatabase db)62     public void onCreate(SQLiteDatabase db) {
63         db.execSQL(CREATE_APP_TABLE);
64     }
65 
66     @Override
onConfigure(SQLiteDatabase db)67     public void onConfigure(SQLiteDatabase db) {
68         super.onConfigure(db);
69         setWriteAheadLoggingEnabled(true);
70     }
71 
72     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)73     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
74         // DO NOT USE CONSTANTS FOR DB UPGRADE STEPS, USE ONLY LITERAL SQL STRINGS!
75     }
76 
77     @Override
getWritableDatabase()78     public SQLiteDatabase getWritableDatabase() {
79         Trace.beginSection("getWritableDatabase");
80         try {
81             return super.getWritableDatabase();
82         } finally {
83             Trace.endSection();
84         }
85     }
86 
87     @Override
getReadableDatabase()88     public SQLiteDatabase getReadableDatabase() {
89         Trace.beginSection("getReadableDatabase");
90         try {
91             return super.getReadableDatabase();
92         } finally {
93             Trace.endSection();
94         }
95     }
96 
97     @Nullable
readLastOpens()98     public Map<ComponentName, Date> readLastOpens() {
99         Trace.beginSection("readLastOpens");
100         try {
101             final SQLiteDatabase db = getReadableDatabase();
102 
103             try (Cursor c = db.query(
104                     AppDbItem.TABLE_NAME,
105                     new String[] {AppDbItem.COLUMN_COMPONENT, AppDbItem.COLUMN_LAST_OPEN},
106                     null, null, null, null, null, null)) {
107 
108                 final int componentIndex = c.getColumnIndex(AppDbItem.COLUMN_COMPONENT);
109                 final int lastOpenIndex = c.getColumnIndex(AppDbItem.COLUMN_LAST_OPEN);
110 
111                 final Map<ComponentName, Date> map = new ArrayMap<>(c.getCount());
112                 while (c.moveToNext()) {
113                     final ComponentName componentName =
114                             ComponentName.unflattenFromString(c.getString(componentIndex));
115                     if (c.isNull(lastOpenIndex)) {
116                         map.put(componentName, null);
117                     } else {
118                         map.put(componentName, new Date(c.getLong(lastOpenIndex)));
119                     }
120                 }
121                 return map;
122 
123             }
124         } finally {
125             Trace.endSection();
126         }
127     }
128 
writeLastOpen(ComponentName componentName, Date lastOpen)129     public void writeLastOpen(ComponentName componentName, Date lastOpen) {
130         Trace.beginSection("writeLastOpen");
131         try {
132             final SQLiteDatabase db = getWritableDatabase();
133 
134             db.beginTransactionNonExclusive();
135             try {
136                 final ContentValues contentValues = new ContentValues(2);
137                 contentValues.put(AppDbItem.COLUMN_LAST_OPEN, lastOpen.getTime());
138 
139                 final int cnt = db.update(
140                         AppDbItem.TABLE_NAME,
141                         contentValues,
142                         AppDbItem.COLUMN_COMPONENT + "=?",
143                         new String[]{componentName.flattenToString()});
144 
145                 if (cnt < 1) {
146                     contentValues.put(AppDbItem.COLUMN_COMPONENT, componentName.flattenToString());
147                     db.insert(AppDbItem.TABLE_NAME, AppDbItem.COLUMN_COMPONENT, contentValues);
148                 }
149                 db.setTransactionSuccessful();
150             } finally {
151                 db.endTransaction();
152             }
153         } finally {
154             Trace.endSection();
155         }
156     }
157 
readOrderPriorities()158     public Map<ComponentName, Long> readOrderPriorities() {
159         Trace.beginSection("readOrderPriorities");
160         try {
161             final SQLiteDatabase db = getReadableDatabase();
162 
163             try (Cursor c = db.query(
164                     AppDbItem.TABLE_NAME,
165                     new String[] {AppDbItem.COLUMN_COMPONENT, AppDbItem.COLUMN_ORDER_PRIORITY},
166                     null, null, null, null, null, null)) {
167 
168                 final int componentIndex = c.getColumnIndex(AppDbItem.COLUMN_COMPONENT);
169                 final int priorityIndex = c.getColumnIndex(AppDbItem.COLUMN_ORDER_PRIORITY);
170 
171                 final Map<ComponentName, Long> map = new ArrayMap<>(c.getCount());
172                 while (c.moveToNext()) {
173                     final ComponentName componentName =
174                             ComponentName.unflattenFromString(c.getString(componentIndex));
175                     map.put(componentName, c.getLong(priorityIndex));
176                 }
177                 return map;
178             }
179         } finally {
180             Trace.endSection();
181         }
182     }
183 
184     /**
185      * Writes the order priority field, and does not touch other items' priorities
186      * @param componentName {@link ComponentName} of item to update
187      * @param priority New priority
188      */
writeOrderPriority(ComponentName componentName, long priority)189     public void writeOrderPriority(ComponentName componentName, long priority) {
190         Trace.beginSection("writeOrderPriority");
191         try {
192             final SQLiteDatabase db = getWritableDatabase();
193 
194             db.beginTransactionNonExclusive();
195             try {
196                 final ContentValues contentValues = new ContentValues(2);
197                 contentValues.put(AppDbItem.COLUMN_ORDER_PRIORITY, priority);
198 
199                 final int cnt = db.update(
200                         AppDbItem.TABLE_NAME,
201                         contentValues,
202                         AppDbItem.COLUMN_COMPONENT + "=?",
203                         new String[]{componentName.flattenToString()});
204 
205                 if (cnt < 1) {
206                     contentValues.put(AppDbItem.COLUMN_COMPONENT, componentName.flattenToString());
207                     db.insert(AppDbItem.TABLE_NAME, AppDbItem.COLUMN_COMPONENT, contentValues);
208                 }
209                 db.setTransactionSuccessful();
210             } finally {
211                 db.endTransaction();
212             }
213         } finally {
214             Trace.endSection();
215         }
216     }
217 
218     /**
219      * Writes the order priority field, and increments all priorities equal or greater if the new
220      * priority is greater than 0
221      * @param componentName {@link ComponentName} of item to update
222      * @param priority New priority
223      */
writeOrderPriorityAndShift(ComponentName componentName, int priority)224     public void writeOrderPriorityAndShift(ComponentName componentName, int priority) {
225         Trace.beginSection("writeOrderPriorityAndShift");
226         try {
227             final SQLiteDatabase db = getWritableDatabase();
228 
229             db.beginTransactionNonExclusive();
230             try {
231                 if (priority > 0) {
232                     db.execSQL("UPDATE " + AppDbItem.TABLE_NAME
233                             + " SET " + AppDbItem.COLUMN_ORDER_PRIORITY + "="
234                                     + AppDbItem.COLUMN_ORDER_PRIORITY + "+1"
235                             + " WHERE " + AppDbItem.COLUMN_ORDER_PRIORITY + ">=?",
236                             new String[] {Integer.toString(priority)});
237                 }
238 
239                 writeOrderPriority(componentName, priority);
240 
241                 db.setTransactionSuccessful();
242             } finally {
243                 db.endTransaction();
244             }
245         } finally {
246             Trace.endSection();
247         }
248     }
249 
250     private static class AppDbItem {
251         public static final String TABLE_NAME = "app_items";
252 
253         // Primary key
254         public static final String COLUMN_COMPONENT = "component";
255         // Ordering priority: higher numbers are higher in list
256         public static final String COLUMN_ORDER_PRIORITY = "order_priority";
257         // Recency date
258         public static final String COLUMN_LAST_OPEN = "last_open";
259     }
260 }
261