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