1 /*
2  * Copyright (C) 2024 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.server.appsearch.appsindexer.appsearchtypes;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.appsearch.AppSearchSchema;
23 import android.app.appsearch.GenericDocument;
24 import android.net.Uri;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.Objects;
29 
30 /**
31  * Represents a installed app to enable searching using name, nicknames, and package name>
32  *
33  * @hide
34  */
35 public class MobileApplication extends GenericDocument {
36 
37     private static final String TAG = "AppSearchMobileApplication";
38 
39     public static final String SCHEMA_TYPE = "builtin:MobileApplication";
40     public static final String APPS_NAMESPACE = "apps";
41 
42     public static final String APP_PROPERTY_PACKAGE_NAME = "packageName";
43     public static final String APP_PROPERTY_DISPLAY_NAME = "displayName";
44     public static final String APP_PROPERTY_ALTERNATE_NAMES = "alternateNames";
45     public static final String APP_PROPERTY_ICON_URI = "iconUri";
46     public static final String APP_PROPERTY_SHA256_CERTIFICATE = "sha256Certificate";
47     public static final String APP_PROPERTY_UPDATED_TIMESTAMP = "updatedTimestamp";
48     public static final String APP_PROPERTY_CLASS_NAME = "className";
49 
50     /** Returns a per-app schema name. */
51     @VisibleForTesting
getSchemaNameForPackage(@onNull String pkg)52     public static String getSchemaNameForPackage(@NonNull String pkg) {
53         return SCHEMA_TYPE + "-" + Objects.requireNonNull(pkg);
54     }
55 
56     /**
57      * Returns a MobileApplication {@link AppSearchSchema} for the a package.
58      *
59      * <p>This is necessary as the base schema and the per-app schemas share all the same
60      * properties. However, we cannot easily modify the base schema to create a per-app schema. So
61      * instead, to create the base schema we call this method with a blank AppSearchSchema with a
62      * schema type of SCHEMA_TYPE. For per-app schemas, we set the schema type to a per-app schema
63      * type, add a parent type of SCHEMA_TYPE, then add the properties.
64      *
65      * @param packageName The package name to create a schema for. Will create the base schema if
66      *     called with null.
67      */
68     @NonNull
createMobileApplicationSchemaForPackage( @onNull String packageName)69     public static AppSearchSchema createMobileApplicationSchemaForPackage(
70             @NonNull String packageName) {
71         Objects.requireNonNull(packageName);
72         return new AppSearchSchema.Builder(getSchemaNameForPackage(packageName))
73                 // It's possible the user knows the package name, or wants to search for all apps
74                 // from a certain developer. They could search for "com.developer.*".
75                 .addProperty(
76                         new AppSearchSchema.StringPropertyConfig.Builder(APP_PROPERTY_PACKAGE_NAME)
77                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
78                                 .setIndexingType(
79                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
80                                 .setTokenizerType(
81                                         AppSearchSchema.StringPropertyConfig
82                                                 .TOKENIZER_TYPE_VERBATIM)
83                                 .build())
84                 .addProperty(
85                         new AppSearchSchema.StringPropertyConfig.Builder(APP_PROPERTY_DISPLAY_NAME)
86                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
87                                 .setIndexingType(
88                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
89                                 .setTokenizerType(
90                                         AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
91                                 .build())
92                 .addProperty(
93                         new AppSearchSchema.StringPropertyConfig.Builder(
94                                         APP_PROPERTY_ALTERNATE_NAMES)
95                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
96                                 .setIndexingType(
97                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
98                                 .setTokenizerType(
99                                         AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
100                                 .build())
101                 .addProperty(
102                         new AppSearchSchema.StringPropertyConfig.Builder(APP_PROPERTY_ICON_URI)
103                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
104                                 .build())
105                 .addProperty(
106                         new AppSearchSchema.BytesPropertyConfig.Builder(
107                                         APP_PROPERTY_SHA256_CERTIFICATE)
108                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
109                                 .build())
110                 .addProperty(
111                         new AppSearchSchema.LongPropertyConfig.Builder(
112                                         APP_PROPERTY_UPDATED_TIMESTAMP)
113                                 .setIndexingType(
114                                         AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE)
115                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
116                                 .build())
117                 .addProperty(
118                         new AppSearchSchema.StringPropertyConfig.Builder(APP_PROPERTY_CLASS_NAME)
119                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
120                                 .build())
121                 .build();
122     }
123 
124     /** Constructs a {@link MobileApplication}. */
125     @VisibleForTesting
MobileApplication(@onNull GenericDocument document)126     public MobileApplication(@NonNull GenericDocument document) {
127         super(document);
128     }
129 
130     /**
131      * Returns the package name this {@link MobileApplication} represents. For example,
132      * "com.android.vending".
133      */
134     @NonNull
getPackageName()135     public String getPackageName() {
136         return getId();
137     }
138 
139     /**
140      * Returns the display name of the app. This is indexed. This is what is displayed in the
141      * launcher. This might look like "Play Store".
142      */
143     @Nullable
getDisplayName()144     public String getDisplayName() {
145         return getPropertyString(APP_PROPERTY_DISPLAY_NAME);
146     }
147 
148     /**
149      * Returns alternative names of the application. These are indexed. For example, you might have
150      * the alternative name "pay" for a wallet app.
151      */
152     @Nullable
getAlternateNames()153     public String[] getAlternateNames() {
154         return getPropertyStringArray(APP_PROPERTY_ALTERNATE_NAMES);
155     }
156 
157     /**
158      * Returns the full name of the resource identifier of the app icon, which can be used for
159      * displaying results. The Uri could be
160      * "android.resource://com.example.vending/drawable/2131230871", for example.
161      */
162     @Nullable
getIconUri()163     public Uri getIconUri() {
164         String uriStr = getPropertyString(APP_PROPERTY_ICON_URI);
165         if (uriStr == null) {
166             return null;
167         }
168         try {
169             return Uri.parse(uriStr);
170         } catch (RuntimeException e) {
171             return null;
172         }
173     }
174 
175     /** Returns the SHA-256 certificate of the application. */
176     @NonNull
getSha256Certificate()177     public byte[] getSha256Certificate() {
178         return getPropertyBytes(APP_PROPERTY_SHA256_CERTIFICATE);
179     }
180 
181     /** Returns the last time the app was installed or updated on the device. */
182     @CurrentTimeMillisLong
getUpdatedTimestamp()183     public long getUpdatedTimestamp() {
184         return getPropertyLong(APP_PROPERTY_UPDATED_TIMESTAMP);
185     }
186 
187     /**
188      * Returns the fully qualified name of the Application class for this mobile app. This would
189      * look something like "com.android.vending.SearchActivity". Combined with the package name, a
190      * launch intent can be created with <code>
191      *     Intent launcher = new Intent(Intent.ACTION_MAIN);
192      *     launcher.setComponent(new ComponentName(app.getPackageName(), app.getClassName()));
193      *     launcher.setPackage(app.getPackageName());
194      *     launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
195      *     launcher.addCategory(Intent.CATEGORY_LAUNCHER);
196      *     appListFragment.getActivity().startActivity(launcher);
197      *  </code>
198      */
199     @Nullable
getClassName()200     public String getClassName() {
201         return getPropertyString(APP_PROPERTY_CLASS_NAME);
202     }
203 
204     public static final class Builder extends GenericDocument.Builder<Builder> {
Builder(@onNull String packageName, @NonNull byte[] sha256Certificate)205         public Builder(@NonNull String packageName, @NonNull byte[] sha256Certificate) {
206             // Changing the schema type dynamically so that we can use separate schemas
207             super(
208                     APPS_NAMESPACE,
209                     Objects.requireNonNull(packageName),
210                     getSchemaNameForPackage(packageName));
211             setPropertyString(APP_PROPERTY_PACKAGE_NAME, packageName);
212             setPropertyBytes(
213                     APP_PROPERTY_SHA256_CERTIFICATE, Objects.requireNonNull(sha256Certificate));
214         }
215 
216         /** Sets the display name. */
217         @NonNull
setDisplayName(@onNull String displayName)218         public Builder setDisplayName(@NonNull String displayName) {
219             setPropertyString(APP_PROPERTY_DISPLAY_NAME, Objects.requireNonNull(displayName));
220             return this;
221         }
222 
223         /** Sets the alternate names. An empty array will erase the list. */
224         @NonNull
setAlternateNames(@onNull String... alternateNames)225         public Builder setAlternateNames(@NonNull String... alternateNames) {
226             setPropertyString(APP_PROPERTY_ALTERNATE_NAMES, Objects.requireNonNull(alternateNames));
227             return this;
228         }
229 
230         /** Sets the icon uri. */
231         @NonNull
setIconUri(@onNull String iconUri)232         public Builder setIconUri(@NonNull String iconUri) {
233             setPropertyString(APP_PROPERTY_ICON_URI, Objects.requireNonNull(iconUri));
234             return this;
235         }
236 
237         @NonNull
setUpdatedTimestampMs(@urrentTimeMillisLong long updatedTimestampMs)238         public Builder setUpdatedTimestampMs(@CurrentTimeMillisLong long updatedTimestampMs) {
239             setPropertyLong(APP_PROPERTY_UPDATED_TIMESTAMP, updatedTimestampMs);
240             return this;
241         }
242 
243         /** Sets the class name. */
244         @NonNull
setClassName(@onNull String className)245         public Builder setClassName(@NonNull String className) {
246             setPropertyString(APP_PROPERTY_CLASS_NAME, Objects.requireNonNull(className));
247             return this;
248         }
249 
250         @NonNull
build()251         public MobileApplication build() {
252             return new MobileApplication(super.build());
253         }
254     }
255 }
256 
257