1 /*
2  * Copyright (C) 2020 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 package com.android.launcher3.model;
17 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.os.UserHandle;
23 
24 import androidx.annotation.AnyThread;
25 import androidx.annotation.WorkerThread;
26 
27 import com.android.launcher3.R;
28 import com.android.launcher3.Utilities;
29 import com.android.launcher3.pm.UserCache;
30 import com.android.launcher3.util.ComponentKey;
31 import com.android.launcher3.util.Preconditions;
32 import com.android.launcher3.util.ResourceBasedOverride;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /**
38  * Model Helper for app predictions
39  */
40 public class PredictionModel implements ResourceBasedOverride {
41 
42     private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
43     private static final int MAX_CACHE_ITEMS = 5;
44 
45     protected Context mContext;
46     private SharedPreferences mDevicePrefs;
47     private UserCache mUserCache;
48 
49 
50     /**
51      * Retrieve instance of this object that can be overridden in runtime based on the build
52      * variant of the application.
53      */
newInstance(Context context)54     public static PredictionModel newInstance(Context context) {
55         PredictionModel model = Overrides.getObject(PredictionModel.class, context,
56                 R.string.prediction_model_class);
57         model.init(context);
58         return model;
59     }
60 
init(Context context)61     protected void init(Context context) {
62         mContext = context;
63         mDevicePrefs = Utilities.getDevicePrefs(mContext);
64         mUserCache = UserCache.INSTANCE.get(mContext);
65 
66     }
67     /**
68      * Formats and stores a list of component key in device preferences.
69      */
70     @AnyThread
cachePredictionComponentKeys(List<ComponentKey> componentKeys)71     public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
72         MODEL_EXECUTOR.execute(() -> {
73             StringBuilder builder = new StringBuilder();
74             int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
75             for (int i = 0; i < count; i++) {
76                 builder.append(serializeComponentKeyToString(componentKeys.get(i)));
77                 builder.append("\n");
78             }
79             mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
80         });
81     }
82 
83     /**
84      * parses and returns ComponentKeys saved by
85      * {@link PredictionModel#cachePredictionComponentKeys(List)}
86      */
87     @WorkerThread
getPredictionComponentKeys()88     public List<ComponentKey> getPredictionComponentKeys() {
89         Preconditions.assertWorkerThread();
90         ArrayList<ComponentKey> items = new ArrayList<>();
91         String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
92         for (String line : cachedBlob.split("\n")) {
93             ComponentKey key = getComponentKeyFromSerializedString(line);
94             if (key != null) {
95                 items.add(key);
96             }
97 
98         }
99         return items;
100     }
101 
serializeComponentKeyToString(ComponentKey componentKey)102     private String serializeComponentKeyToString(ComponentKey componentKey) {
103         long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
104         return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
105     }
106 
getComponentKeyFromSerializedString(String str)107     private ComponentKey getComponentKeyFromSerializedString(String str) {
108         int sep = str.indexOf('#');
109         if (sep < 0 || (sep + 1) >= str.length()) {
110             return null;
111         }
112         ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
113         if (componentName == null) {
114             return null;
115         }
116         try {
117             long serialNumber = Long.parseLong(str.substring(sep + 1));
118             UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
119             return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
120         } catch (NumberFormatException ex) {
121             return null;
122         }
123     }
124 }
125