1 /* 2 * Copyright (C) 2020 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.server.textclassifier; 17 18 import android.content.ContentProvider; 19 import android.content.ContentValues; 20 import android.database.Cursor; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.drawable.Drawable; 24 import android.graphics.drawable.Icon; 25 import android.net.Uri; 26 import android.os.ParcelFileDescriptor; 27 import android.os.ParcelFileDescriptor.AutoCloseOutputStream; 28 import android.os.UserHandle; 29 import android.util.Log; 30 import android.util.Pair; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.server.textclassifier.IconsUriHelper.ResourceInfo; 34 35 import java.io.ByteArrayOutputStream; 36 import java.io.IOException; 37 import java.io.OutputStream; 38 import java.util.Arrays; 39 40 /** 41 * A content provider that is used to access icons returned from the TextClassifier service. 42 * 43 * <p>Use {@link IconsUriHelper#getContentUri(String, int)} to access a uri for a specific resource. 44 * The uri may be passed to other processes to access the specified resource. 45 * 46 * <p>NOTE: Care must be taken to avoid leaking resources to non-permitted apps via this provider. 47 */ 48 public final class IconsContentProvider extends ContentProvider { 49 50 private static final String TAG = "IconsContentProvider"; 51 private static final String MIME_TYPE = "image/png"; 52 53 private final PipeDataWriter<Pair<ResourceInfo, Integer>> mWriter = 54 (writeSide, uri, mimeType, bundle, args) -> { 55 try (OutputStream out = new AutoCloseOutputStream(writeSide)) { 56 final ResourceInfo res = args.first; 57 final int userId = args.second; 58 final Drawable drawable = Icon.createWithResource(res.packageName, res.id) 59 .loadDrawableAsUser(getContext(), userId); 60 getBitmap(drawable).compress(Bitmap.CompressFormat.PNG, 100, out); 61 } catch (Exception e) { 62 Log.e(TAG, "Error retrieving icon for uri: " + uri, e); 63 } 64 }; 65 66 @Override openFile(Uri uri, String mode)67 public ParcelFileDescriptor openFile(Uri uri, String mode) { 68 final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri); 69 if (res == null) { 70 Log.e(TAG, "No icon found for uri: " + uri); 71 return null; 72 } 73 74 try { 75 final Pair<ResourceInfo, Integer> args = new Pair(res, UserHandle.getCallingUserId()); 76 return openPipeHelper(uri, MIME_TYPE, /* bundle= */ null, args, mWriter); 77 } catch (IOException e) { 78 Log.e(TAG, "Error opening pipe helper for icon at uri: " + uri, e); 79 } 80 81 return null; 82 } 83 getBitmap(Drawable drawable)84 private static Bitmap getBitmap(Drawable drawable) { 85 if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { 86 throw new IllegalStateException("The icon is zero-sized"); 87 } 88 89 final Bitmap bitmap = Bitmap.createBitmap( 90 drawable.getIntrinsicWidth(), 91 drawable.getIntrinsicHeight(), 92 Bitmap.Config.ARGB_8888); 93 94 final Canvas canvas = new Canvas(bitmap); 95 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 96 drawable.draw(canvas); 97 98 return bitmap; 99 } 100 101 /** 102 * Returns true if the drawables are considered the same. 103 */ 104 @VisibleForTesting sameIcon(Drawable one, Drawable two)105 public static boolean sameIcon(Drawable one, Drawable two) { 106 final ByteArrayOutputStream stream1 = new ByteArrayOutputStream(); 107 getBitmap(one).compress(Bitmap.CompressFormat.PNG, 100, stream1); 108 final ByteArrayOutputStream stream2 = new ByteArrayOutputStream(); 109 getBitmap(two).compress(Bitmap.CompressFormat.PNG, 100, stream2); 110 return Arrays.equals(stream1.toByteArray(), stream2.toByteArray()); 111 } 112 113 @Override getType(Uri uri)114 public String getType(Uri uri) { 115 return MIME_TYPE; 116 } 117 118 @Override onCreate()119 public boolean onCreate() { 120 return true; 121 } 122 123 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)124 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 125 String sortOrder) { 126 return null; 127 } 128 129 @Override delete(Uri uri, String selection, String[] selectionArgs)130 public int delete(Uri uri, String selection, String[] selectionArgs) { 131 return 0; 132 } 133 134 @Override insert(Uri uri, ContentValues values)135 public Uri insert(Uri uri, ContentValues values) { 136 return null; 137 } 138 139 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)140 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 141 return 0; 142 } 143 } 144