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