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