1 /* 2 * Copyright (C) 2022 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.quicksearchbox 17 18 import android.app.SearchManager 19 import android.content.Intent 20 import android.net.Uri 21 import android.os.Bundle 22 import com.google.common.annotations.VisibleForTesting 23 import kotlin.text.StringBuilder 24 25 /** Some utilities for suggestions. */ 26 object SuggestionUtils { 27 @JvmStatic getSuggestionIntentnull28 fun getSuggestionIntent(suggestion: SuggestionCursor?, appSearchData: Bundle?): Intent { 29 val action: String? = suggestion?.suggestionIntentAction 30 val data: String? = suggestion?.suggestionIntentDataString 31 val query: String? = suggestion?.suggestionQuery 32 val userQuery: String? = suggestion?.userQuery 33 val extraData: String? = suggestion?.suggestionIntentExtraData 34 35 // Now build the Intent 36 val intent = Intent(action) 37 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 38 // We need CLEAR_TOP to avoid reusing an old task that has other activities 39 // on top of the one we want. 40 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 41 if (data != null) { 42 intent.setData(Uri.parse(data)) 43 } 44 intent.putExtra(SearchManager.USER_QUERY, userQuery) 45 if (query != null) { 46 intent.putExtra(SearchManager.QUERY, query) 47 } 48 if (extraData != null) { 49 intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData) 50 } 51 if (appSearchData != null) { 52 intent.putExtra(SearchManager.APP_DATA, appSearchData) 53 } 54 intent.setComponent(suggestion?.suggestionIntentComponent) 55 return intent 56 } 57 58 /** 59 * Gets a unique key that identifies a suggestion. This is used to avoid duplicate suggestions. 60 */ 61 @JvmStatic getSuggestionKeynull62 fun getSuggestionKey(suggestion: Suggestion): String { 63 val action: String = makeKeyComponent(suggestion.suggestionIntentAction) 64 val data: String = makeKeyComponent(normalizeUrl(suggestion.suggestionIntentDataString)) 65 val query: String = makeKeyComponent(normalizeUrl(suggestion.suggestionQuery)) 66 // calculating accurate size of string builder avoids an allocation vs starting with 67 // the default size and having to expand. 68 val size: Int = action.length + 2 + data.length + query.length 69 return StringBuilder(size) 70 .append(action) 71 .append('#') 72 .append(data) 73 .append('#') 74 .append(query) 75 .toString() 76 } 77 makeKeyComponentnull78 private fun makeKeyComponent(str: String?): String { 79 return str ?: "" 80 } 81 82 private const val SCHEME_SEPARATOR = "://" 83 private const val DEFAULT_SCHEME = "http" 84 85 /** 86 * Simple url normalization that adds http:// if no scheme exists, and strips empty paths, e.g., 87 * www.google.com/ -> http://www.google.com. Used to prevent obvious duplication of nav 88 * suggestions, bookmarks and urls entered by the user. 89 */ 90 @JvmStatic 91 @VisibleForTesting normalizeUrlnull92 fun normalizeUrl(url: String?): String? { 93 val normalized: String 94 if (url != null) { 95 val start: Int 96 val schemePos: Int = url.indexOf(SCHEME_SEPARATOR) 97 if (schemePos == -1) { 98 // no scheme - add the default 99 normalized = DEFAULT_SCHEME + SCHEME_SEPARATOR + url 100 start = DEFAULT_SCHEME.length + SCHEME_SEPARATOR.length 101 } else { 102 normalized = url 103 start = schemePos + SCHEME_SEPARATOR.length 104 } 105 var end: Int = normalized.length 106 if (normalized.indexOf('/', start) == end - 1) { 107 end-- 108 } 109 return normalized.substring(0, end) 110 } 111 return url 112 } 113 } 114