1 /* 2 * Copyright (C) 2021 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 android.content.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Person; 22 import android.app.appsearch.AppSearchSchema; 23 import android.app.appsearch.GenericDocument; 24 import android.graphics.drawable.Icon; 25 import android.net.UriCodec; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.IOException; 32 import java.nio.charset.StandardCharsets; 33 import java.util.Objects; 34 import java.util.UUID; 35 36 /** 37 * A {@link GenericDocument} representation of {@link Person} object. 38 * 39 * @hide 40 */ 41 public class AppSearchShortcutPerson extends GenericDocument { 42 43 /** 44 * The name of the schema type for {@link Person} documents. 45 * @hide 46 */ 47 public static final String SCHEMA_TYPE = "ShortcutPerson"; 48 49 private static final String KEY_NAME = "name"; 50 private static final String KEY_KEY = "key"; 51 private static final String KEY_IS_BOT = "isBot"; 52 private static final String KEY_IS_IMPORTANT = "isImportant"; 53 private static final String KEY_ICON = "icon"; 54 AppSearchShortcutPerson(@onNull GenericDocument document)55 public AppSearchShortcutPerson(@NonNull GenericDocument document) { 56 super(document); 57 } 58 59 public static final AppSearchSchema SCHEMA = new AppSearchSchema.Builder(SCHEMA_TYPE) 60 .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_NAME) 61 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 62 .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) 63 .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) 64 .build() 65 66 ).addProperty(new AppSearchSchema.StringPropertyConfig.Builder(KEY_KEY) 67 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 68 .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) 69 .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) 70 .build() 71 72 ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_BOT) 73 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) 74 .build() 75 76 ).addProperty(new AppSearchSchema.BooleanPropertyConfig.Builder(KEY_IS_IMPORTANT) 77 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) 78 .build() 79 80 ).addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(KEY_ICON) 81 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 82 .build() 83 84 ).build(); 85 86 /** @hide */ 87 @NonNull instance(@onNull final Person person)88 public static AppSearchShortcutPerson instance(@NonNull final Person person) { 89 Objects.requireNonNull(person); 90 final String id; 91 if (person.getUri() != null) { 92 id = person.getUri(); 93 } else { 94 // NOTE: an identifier is required even when uri is null. 95 id = UUID.randomUUID().toString(); 96 } 97 return new Builder(id).setName(person.getName()) 98 .setKey(person.getKey()).setIsBot(person.isBot()) 99 .setIsImportant(person.isImportant()) 100 .setIcon(transformToByteArray(person.getIcon())).build(); 101 } 102 103 /** 104 * Convert this {@link GenericDocument} into {@link Person}. 105 */ 106 @NonNull toPerson()107 public Person toPerson() { 108 String uri; 109 try { 110 uri = UriCodec.decode( 111 getId(), false /* convertPlus */, StandardCharsets.UTF_8, 112 true /* throwOnFailure */); 113 } catch (IllegalArgumentException e) { 114 uri = null; 115 } 116 return new Person.Builder().setName(getPropertyString(KEY_NAME)) 117 .setUri(uri).setKey(getPropertyString(KEY_KEY)) 118 .setBot(getPropertyBoolean(KEY_IS_BOT)) 119 .setImportant(getPropertyBoolean(KEY_IS_IMPORTANT)) 120 .setIcon(transformToIcon(getPropertyBytes(KEY_ICON))) 121 .build(); 122 } 123 124 /** @hide */ 125 @VisibleForTesting 126 public static class Builder extends GenericDocument.Builder<Builder> { 127 Builder(@onNull final String id)128 public Builder(@NonNull final String id) { 129 super(/*namespace=*/ "", id, SCHEMA_TYPE); 130 } 131 132 /** @hide */ 133 @NonNull setName(@ullable final CharSequence name)134 public Builder setName(@Nullable final CharSequence name) { 135 if (name != null) { 136 setPropertyString(KEY_NAME, name.toString()); 137 } 138 return this; 139 } 140 141 /** @hide */ 142 @NonNull setKey(@ullable final String key)143 public Builder setKey(@Nullable final String key) { 144 if (key != null) { 145 setPropertyString(KEY_KEY, key); 146 } 147 return this; 148 } 149 150 /** @hide */ 151 @NonNull setIsBot(final boolean isBot)152 public Builder setIsBot(final boolean isBot) { 153 setPropertyBoolean(KEY_IS_BOT, isBot); 154 return this; 155 } 156 157 /** @hide */ 158 @NonNull setIsImportant(final boolean isImportant)159 public Builder setIsImportant(final boolean isImportant) { 160 setPropertyBoolean(KEY_IS_IMPORTANT, isImportant); 161 return this; 162 } 163 164 /** @hide */ 165 @NonNull setIcon(@ullable final byte[] icon)166 public Builder setIcon(@Nullable final byte[] icon) { 167 if (icon != null) { 168 setPropertyBytes(KEY_ICON, icon); 169 } 170 return this; 171 } 172 173 /** @hide */ 174 @NonNull 175 @Override build()176 public AppSearchShortcutPerson build() { 177 return new AppSearchShortcutPerson(super.build()); 178 } 179 } 180 181 /** 182 * Convert {@link Icon} into byte[]. 183 */ 184 @Nullable transformToByteArray(@ullable final Icon icon)185 private static byte[] transformToByteArray(@Nullable final Icon icon) { 186 if (icon == null) { 187 return null; 188 } 189 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 190 icon.writeToStream(baos); 191 return baos.toByteArray(); 192 } catch (IOException e) { 193 return null; 194 } 195 } 196 197 /** 198 * Convert byte[] into {@link Icon}. 199 */ 200 @Nullable transformToIcon(@ullable final byte[] icon)201 private Icon transformToIcon(@Nullable final byte[] icon) { 202 if (icon == null) { 203 return null; 204 } 205 try (ByteArrayInputStream bais = new ByteArrayInputStream(icon)) { 206 return Icon.createFromStream(bais); 207 } catch (IOException e) { 208 return null; 209 } 210 } 211 } 212