1 /*
2  * Copyright (C) 2017 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.settings.intelligence.search.query;
18 
19 import static com.android.settings.intelligence.search.query.DatabaseResultTask.BASE_RANKS;
20 import static com.android.settings.intelligence.search.SearchResult.TOP_RANK;
21 
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.res.Resources;
25 import android.database.Cursor;
26 import android.graphics.drawable.Drawable;
27 import android.os.BadParcelableException;
28 import android.text.TextUtils;
29 import android.util.Log;
30 import android.view.ContextThemeWrapper;
31 
32 import com.android.settings.intelligence.R;
33 import com.android.settings.intelligence.search.ResultPayload;
34 import com.android.settings.intelligence.search.ResultPayloadUtils;
35 import com.android.settings.intelligence.search.SearchResult;
36 import com.android.settings.intelligence.search.indexing.IndexDatabaseHelper;
37 import com.android.settings.intelligence.search.sitemap.SiteMapManager;
38 
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 
46 /**
47  * Controller to Build search results from {@link Cursor} Objects.
48  *
49  * Each converted {@link Cursor} has the following fields:
50  * - String Title
51  * - String Summary
52  * - int rank
53  * - {@link Drawable} icon
54  * - {@link ResultPayload} payload
55  */
56 public class CursorToSearchResultConverter {
57 
58     private static final String TAG = "CursorConverter";
59 
60     private final Context mContext;
61 
62     private final int LONG_TITLE_LENGTH = 20;
63 
64     private static final String[] whiteList = {
65             "main_toggle_wifi",
66             "main_toggle_bluetooth",
67             "main_toggle_bluetooth_obsolete",
68             "toggle_airplane",
69             "tether_settings",
70             "battery_saver",
71             "toggle_nfc",
72             "restrict_background",
73             "data_usage_enable",
74             "button_roaming_key",
75     };
76     private static final Set<String> prioritySettings = new HashSet(Arrays.asList(whiteList));
77 
78 
CursorToSearchResultConverter(Context context)79     public CursorToSearchResultConverter(Context context) {
80         mContext = context;
81     }
82 
convertCursor(Cursor cursorResults, int baseRank, SiteMapManager siteMapManager)83     public Set<SearchResult> convertCursor(Cursor cursorResults, int baseRank,
84             SiteMapManager siteMapManager) {
85         if (cursorResults == null) {
86             return null;
87         }
88         final Map<String, Context> contextMap = new HashMap<>();
89         final Set<SearchResult> results = new HashSet<>();
90 
91         while (cursorResults.moveToNext()) {
92             SearchResult result = buildSingleSearchResultFromCursor(siteMapManager,
93                     contextMap, cursorResults, baseRank);
94             if (result != null) {
95                 results.add(result);
96             }
97         }
98         return results;
99     }
100 
getUnmarshalledPayload(byte[] marshalledPayload, int payloadType)101     public static ResultPayload getUnmarshalledPayload(byte[] marshalledPayload,
102             int payloadType) {
103         try {
104             switch (payloadType) {
105                 case ResultPayload.PayloadType.INTENT:
106                     return ResultPayloadUtils.unmarshall(marshalledPayload,
107                             ResultPayload.CREATOR);
108                 // TODO REFACTOR (b/62807132) Re-add inline
109 //                case ResultPayload.PayloadType.INLINE_SWITCH:
110 //                    return ResultPayloadUtils.unmarshall(marshalledPayload,
111 //                            InlineSwitchPayload.CREATOR);
112 //                case ResultPayload.PayloadType.INLINE_LIST:
113 //                    return ResultPayloadUtils.unmarshall(marshalledPayload,
114 //                            InlineListPayload.CREATOR);
115             }
116         } catch (BadParcelableException e) {
117             Log.w(TAG, "Error creating parcelable: " + e);
118         }
119         return null;
120     }
121 
buildSingleSearchResultFromCursor(SiteMapManager siteMapManager, Map<String, Context> contextMap, Cursor cursor, int baseRank)122     private SearchResult buildSingleSearchResultFromCursor(SiteMapManager siteMapManager,
123             Map<String, Context> contextMap, Cursor cursor, int baseRank) {
124         final String pkgName = cursor.getString(cursor.getColumnIndexOrThrow(
125                 IndexDatabaseHelper.IndexColumns.DATA_PACKAGE));
126         final String title = cursor.getString(cursor.getColumnIndexOrThrow(
127                 IndexDatabaseHelper.IndexColumns.DATA_TITLE));
128         final String summaryOn = cursor.getString(cursor.getColumnIndex(
129                 IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON));
130         final String key = cursor.getString(cursor.getColumnIndexOrThrow(
131                 IndexDatabaseHelper.IndexColumns.DATA_KEY_REF));
132         final String iconResStr = cursor.getString(cursor.getColumnIndexOrThrow(
133                 IndexDatabaseHelper.IndexColumns.ICON));
134         final int payloadType = cursor.getInt(cursor.getColumnIndexOrThrow(
135                 IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE));
136         final byte[] marshalledPayload = cursor.getBlob(cursor.getColumnIndexOrThrow(
137                 IndexDatabaseHelper.IndexColumns.PAYLOAD));
138         final ResultPayload payload = getUnmarshalledPayload(marshalledPayload, payloadType);
139 
140         final List<String> breadcrumbs = getBreadcrumbs(siteMapManager, cursor);
141         final int rank = getRank(title, baseRank, key);
142         final Drawable icon = getIconForPackage(contextMap, pkgName, iconResStr);
143 
144         final SearchResult.Builder builder = new SearchResult.Builder()
145                 .setDataKey(key)
146                 .setTitle(title)
147                 .setSummary(summaryOn)
148                 .addBreadcrumbs(breadcrumbs)
149                 .setRank(rank)
150                 .setIcon(icon)
151                 .setPayload(payload);
152         return builder.build();
153     }
154 
getIconForPackage(Map<String, Context> contextMap, String pkgName, String iconResStr)155     private Drawable getIconForPackage(Map<String, Context> contextMap, String pkgName,
156             String iconResStr) {
157         if (TextUtils.isEmpty(pkgName)) {
158             return null;
159         }
160         final int iconId = TextUtils.isEmpty(iconResStr) ? 0 : Integer.parseInt(iconResStr);
161         if (iconId == 0) {
162             return null;
163         }
164         Context packageContext = contextMap.get(pkgName);
165         if (packageContext == null) {
166             try {
167                 final Context themedAppContext = new ContextThemeWrapper(
168                         mContext, R.style.Theme_Settings);
169                 packageContext = new ContextThemeWrapper(
170                         themedAppContext.createPackageContext(pkgName, 0),
171                         themedAppContext.getTheme());
172                 contextMap.put(pkgName, packageContext);
173             } catch (PackageManager.NameNotFoundException e) {
174                 Log.e(TAG, "Cannot create Context for package: " + pkgName);
175                 return null;
176             }
177         }
178         try {
179             final Drawable drawable = packageContext.getDrawable(iconId);
180             Log.d(TAG, "Returning icon, id :" + iconId);
181             return drawable;
182         } catch (Resources.NotFoundException nfe) {
183             Log.w(TAG, "Cannot get icon, pkg/id :" + pkgName + "/" + iconId);
184             return null;
185         }
186     }
187 
getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor)188     private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
189         final String screenTitle = cursor.getString(cursor.getColumnIndexOrThrow(
190                 IndexDatabaseHelper.IndexColumns.SCREEN_TITLE));
191         final String screenClass = cursor.getString(cursor.getColumnIndexOrThrow(
192                 IndexDatabaseHelper.IndexColumns.CLASS_NAME));
193         return siteMapManager == null ? null : siteMapManager.buildBreadCrumb(mContext,
194                 screenClass, screenTitle);
195     }
196 
197     /**
198      * Uses the breadcrumbs to determine the offset to the base rank.
199      * There are three checks
200      * A) If the result is prioritized and the highest base level
201      * B) If the query matches the highest level menu title
202      * C) If the query is longer than 20
203      *
204      * If the query matches A, set it to TOP_RANK
205      * If the query matches B, the offset is 0.
206      * If the query matches C, the offset is 1
207      *
208      * @param title    of the result.
209      * @param baseRank of the result. Lower if it's a better result.
210      */
getRank(String title, int baseRank, String key)211     private int getRank(String title, int baseRank, String key) {
212         // The result can only be prioritized if it is a top ranked result.
213         if (prioritySettings.contains(key) && baseRank < BASE_RANKS[1]) {
214             return TOP_RANK;
215         }
216         if (title.length() > LONG_TITLE_LENGTH) {
217             return baseRank + 1;
218         }
219         return baseRank;
220     }
221 }