• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.settings.intelligence.search.indexing;
19 
20 import android.content.Context;
21 import android.content.Intent;
22 import android.text.TextUtils;
23 
24 import com.android.settings.intelligence.search.ResultPayload;
25 import com.android.settings.intelligence.search.ResultPayloadUtils;
26 
27 import java.text.Normalizer;
28 import java.util.Locale;
29 import java.util.regex.Pattern;
30 
31 /**
32  * Data class representing a single row in the Setting Search results database.
33  */
34 public class IndexData {
35     /**
36      * This is different from intentTargetPackage.
37      *
38      * @see SearchIndexableData#iconResId
39      */
40     public final String packageName;
41     public final String locale;
42     public final String updatedTitle;
43     public final String normalizedTitle;
44     public final String updatedSummaryOn;
45     public final String normalizedSummaryOn;
46     public final String entries;
47     public final String className;
48     public final String childClassName;
49     public final String screenTitle;
50     public final int iconResId;
51     public final String spaceDelimitedKeywords;
52     public final String intentAction;
53     public final String intentTargetPackage;
54     public final String intentTargetClass;
55     public final boolean enabled;
56     public final String key;
57     public final int payloadType;
58     public final byte[] payload;
59 
60     private static final String NON_BREAKING_HYPHEN = "\u2011";
61     private static final String EMPTY = "";
62     private static final String HYPHEN = "-";
63     private static final String SPACE = " ";
64     // Regex matching a comma, and any number of subsequent white spaces.
65     private static final String LIST_DELIMITERS = "[,]\\s*";
66 
67     private static final Pattern REMOVE_DIACRITICALS_PATTERN
68             = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
69 
IndexData(Builder builder)70     private IndexData(Builder builder) {
71         locale = Locale.getDefault().toString();
72         updatedTitle = normalizeHyphen(builder.mTitle);
73         updatedSummaryOn = normalizeHyphen(builder.mSummaryOn);
74         if (Locale.JAPAN.toString().equalsIgnoreCase(locale)) {
75             // Special case for JP. Convert charset to the same type for indexing purpose.
76             normalizedTitle = normalizeJapaneseString(builder.mTitle);
77             normalizedSummaryOn = normalizeJapaneseString(builder.mSummaryOn);
78         } else {
79             normalizedTitle = normalizeString(builder.mTitle);
80             normalizedSummaryOn = normalizeString(builder.mSummaryOn);
81         }
82         entries = builder.mEntries;
83         className = builder.mClassName;
84         childClassName = builder.mChildClassName;
85         screenTitle = builder.mScreenTitle;
86         iconResId = builder.mIconResId;
87         spaceDelimitedKeywords = normalizeKeywords(builder.mKeywords);
88         intentAction = builder.mIntentAction;
89         packageName = builder.mPackageName;
90         intentTargetPackage = builder.mIntentTargetPackage;
91         intentTargetClass = builder.mIntentTargetClass;
92         enabled = builder.mEnabled;
93         key = builder.mKey;
94         payloadType = builder.mPayloadType;
95         payload = builder.mPayload != null ? ResultPayloadUtils.marshall(builder.mPayload)
96                 : null;
97     }
98 
99     @Override
toString()100     public String toString() {
101         return new StringBuilder(updatedTitle)
102                 .append(": ")
103                 .append(updatedSummaryOn)
104                 .toString();
105     }
106 
107     /**
108      * In the list of keywords, replace the comma and all subsequent whitespace with a single space.
109      */
normalizeKeywords(String input)110     public static String normalizeKeywords(String input) {
111         return (input != null) ? input.replaceAll(LIST_DELIMITERS, SPACE) : EMPTY;
112     }
113 
114     /**
115      * @return {@param input} where all non-standard hyphens are replaced by normal hyphens.
116      */
normalizeHyphen(String input)117     public static String normalizeHyphen(String input) {
118         return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
119     }
120 
121     /**
122      * @return {@param input} with all hyphens removed, and all letters lower case.
123      */
normalizeString(String input)124     public static String normalizeString(String input) {
125         final String normalizedHypen = normalizeHyphen(input);
126         final String nohyphen = (input != null) ? normalizedHypen.replaceAll(HYPHEN, EMPTY) : EMPTY;
127         final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD);
128 
129         return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase();
130     }
131 
normalizeJapaneseString(String input)132     public static String normalizeJapaneseString(String input) {
133         final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY;
134         final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFKD);
135         final StringBuffer sb = new StringBuffer();
136         final int length = normalized.length();
137         for (int i = 0; i < length; i++) {
138             char c = normalized.charAt(i);
139             // Convert Hiragana to full-width Katakana
140             if (c >= '\u3041' && c <= '\u3096') {
141                 sb.append((char) (c - '\u3041' + '\u30A1'));
142             } else {
143                 sb.append(c);
144             }
145         }
146 
147         return REMOVE_DIACRITICALS_PATTERN.matcher(sb.toString()).replaceAll("").toLowerCase();
148     }
149 
150     public static class Builder {
151         private String mTitle;
152         private String mSummaryOn;
153         private String mEntries;
154         private String mClassName;
155         private String mChildClassName;
156         private String mScreenTitle;
157         private String mPackageName;
158         private int mIconResId;
159         private String mKeywords;
160         private String mIntentAction;
161         private String mIntentTargetPackage;
162         private String mIntentTargetClass;
163         private boolean mEnabled;
164         private String mKey;
165         @ResultPayload.PayloadType
166         private int mPayloadType;
167         private ResultPayload mPayload;
168 
169         @Override
toString()170         public String toString() {
171             return "IndexData.Builder {"
172                     + "title: " + mTitle + ","
173                     + "package: " + mPackageName
174                     + "}";
175         }
176 
setTitle(String title)177         public Builder setTitle(String title) {
178             mTitle = title;
179             return this;
180         }
181 
getKey()182         public String getKey() {
183             return mKey;
184         }
185 
setSummaryOn(String summaryOn)186         public Builder setSummaryOn(String summaryOn) {
187             mSummaryOn = summaryOn;
188             return this;
189         }
190 
setEntries(String entries)191         public Builder setEntries(String entries) {
192             mEntries = entries;
193             return this;
194         }
195 
setClassName(String className)196         public Builder setClassName(String className) {
197             mClassName = className;
198             return this;
199         }
200 
setChildClassName(String childClassName)201         public Builder setChildClassName(String childClassName) {
202             mChildClassName = childClassName;
203             return this;
204         }
205 
setScreenTitle(String screenTitle)206         public Builder setScreenTitle(String screenTitle) {
207             mScreenTitle = screenTitle;
208             return this;
209         }
210 
setPackageName(String packageName)211         public Builder setPackageName(String packageName) {
212             mPackageName = packageName;
213             return this;
214         }
215 
setIconResId(int iconResId)216         public Builder setIconResId(int iconResId) {
217             mIconResId = iconResId;
218             return this;
219         }
220 
setKeywords(String keywords)221         public Builder setKeywords(String keywords) {
222             mKeywords = keywords;
223             return this;
224         }
225 
setIntentAction(String intentAction)226         public Builder setIntentAction(String intentAction) {
227             mIntentAction = intentAction;
228             return this;
229         }
230 
setIntentTargetPackage(String intentTargetPackage)231         public Builder setIntentTargetPackage(String intentTargetPackage) {
232             mIntentTargetPackage = intentTargetPackage;
233             return this;
234         }
235 
setIntentTargetClass(String intentTargetClass)236         public Builder setIntentTargetClass(String intentTargetClass) {
237             mIntentTargetClass = intentTargetClass;
238             return this;
239         }
240 
setEnabled(boolean enabled)241         public Builder setEnabled(boolean enabled) {
242             mEnabled = enabled;
243             return this;
244         }
245 
setKey(String key)246         public Builder setKey(String key) {
247             mKey = key;
248             return this;
249         }
250 
setPayload(ResultPayload payload)251         public Builder setPayload(ResultPayload payload) {
252             mPayload = payload;
253 
254             if (mPayload != null) {
255                 setPayloadType(mPayload.getType());
256             }
257             return this;
258         }
259 
260         /**
261          * Payload type is added when a Payload is added to the Builder in {setPayload}
262          *
263          * @param payloadType PayloadType
264          * @return The Builder
265          */
setPayloadType(@esultPayload.PayloadType int payloadType)266         private Builder setPayloadType(@ResultPayload.PayloadType int payloadType) {
267             mPayloadType = payloadType;
268             return this;
269         }
270 
271         /**
272          * Adds intent to inline payloads, or creates an Intent Payload as a fallback if the
273          * payload is null.
274          */
setIntent(Context context)275         private void setIntent(Context context) {
276             if (mPayload != null) {
277                 return;
278             }
279             final Intent intent = buildIntent(context);
280             mPayload = new ResultPayload(intent);
281             mPayloadType = ResultPayload.PayloadType.INTENT;
282         }
283 
284         /**
285          * Adds Intent payload to builder.
286          */
buildIntent(Context context)287         private Intent buildIntent(Context context) {
288             final Intent intent;
289 
290             // TODO REFACTOR (b/62807132) With inline results re-add proper intent support
291             boolean isEmptyIntentAction = TextUtils.isEmpty(mIntentAction);
292             if (isEmptyIntentAction) {
293                 // No intent action is set, or the intent action is for a sub-setting.
294                 intent = DatabaseIndexingUtils.buildSearchTrampolineIntent(context, mClassName,
295                         mKey, mScreenTitle);
296             } else {
297                 intent = DatabaseIndexingUtils.buildDirectSearchResultIntent(mIntentAction,
298                         mIntentTargetPackage, mIntentTargetClass, mKey);
299             }
300             return intent;
301         }
302 
build(Context context)303         public IndexData build(Context context) {
304             setIntent(context);
305             return new IndexData(this);
306         }
307     }
308 }