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