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.cts.verifier.sharesheet; 18 19 import android.content.ClipDescription; 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.res.AssetFileDescriptor; 23 import android.database.Cursor; 24 import android.graphics.Bitmap; 25 import android.graphics.Bitmap.CompressFormat; 26 import android.graphics.Bitmap.Config; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.Paint; 30 import android.graphics.Paint.Align; 31 import android.graphics.Paint.Style; 32 import android.net.Uri; 33 import android.os.ParcelFileDescriptor; 34 import android.util.Log; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 39 import com.android.cts.verifier.sharesheet.TestContract.UriParams; 40 41 import java.io.FileNotFoundException; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.util.concurrent.Executor; 45 import java.util.concurrent.Executors; 46 47 public class SharesheetTestImageProvider extends ContentProvider { 48 private static final String TAG = "TestImageProvider"; 49 private final Executor mExecutor = Executors.newCachedThreadPool(); 50 51 @Override onCreate()52 public boolean onCreate() { 53 return true; 54 } 55 56 @Nullable 57 @Override getType(@onNull Uri uri)58 public String getType(@NonNull Uri uri) { 59 return uri.getQueryParameter(UriParams.Type); 60 } 61 62 @Nullable 63 @Override openAssetFile(@onNull Uri uri, @NonNull String mode)64 public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) 65 throws FileNotFoundException { 66 String name = uri.getQueryParameter(UriParams.Name); 67 if (name == null) { 68 throw new FileNotFoundException("Malformed URI: " + uri); 69 } 70 try { 71 final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe(); 72 mExecutor.execute(() -> { 73 try { 74 sendBitmap(pipe[1], uriToColor(uri), name, getType(uri)); 75 } catch (IOException e) { 76 Log.e(TAG, "Failed to send bitmap", e); 77 } finally { 78 try { 79 pipe[1].close(); 80 } catch (Throwable t) { 81 // ignore 82 } 83 } 84 }); 85 return new AssetFileDescriptor(pipe[0], 0, -1); 86 } catch (IOException e) { 87 throw new FileNotFoundException(e.getMessage()); 88 } 89 } 90 sendBitmap( ParcelFileDescriptor fd, int color, String name, @Nullable String mimeType)91 private void sendBitmap( 92 ParcelFileDescriptor fd, 93 int color, 94 String name, 95 @Nullable String mimeType) throws IOException { 96 Bitmap bitmap = createBitmap(200, 200, color, name); 97 try (FileOutputStream fos = new FileOutputStream(fd.getFileDescriptor())) { 98 CompressFormat format = ClipDescription.compareMimeTypes(mimeType, "image/jpg") 99 ? CompressFormat.JPEG 100 : CompressFormat.PNG; 101 bitmap.compress(format, 100, fos); 102 } 103 } 104 createBitmap(int width, int height, int bgColor, String name)105 private static Bitmap createBitmap(int width, int height, int bgColor, String name) { 106 Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); 107 Canvas canvas = new Canvas(bitmap); 108 Paint paint = new Paint(); 109 paint.setColor(bgColor); 110 paint.setStyle(Style.FILL); 111 canvas.drawPaint(paint); 112 paint.setColor(getContrast(bgColor)); 113 paint.setAntiAlias(true); 114 paint.setTextSize(28f); 115 paint.setTextAlign(Align.CENTER); 116 canvas.drawText(name, width / 2f, height / 2f, paint); 117 return bitmap; 118 } 119 uriToColor(Uri uri)120 private static int uriToColor(Uri uri) { 121 int hash = uri.hashCode(); 122 return Color.argb( 123 255, 124 (hash & 0x00ff0000) >>> 16, 125 (hash & 0x0000ff00) >>> 8, 126 hash & 0x000000ff); 127 } 128 getContrast(int color)129 private static int getContrast(int color) { 130 return Color.argb( 131 255, 132 Color.red(color) > 127 ? 0 : 255, 133 Color.green(color) > 127 ? 0 : 255, 134 Color.blue(color) > 127 ? 0 : 255); 135 } 136 137 @Nullable 138 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)139 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 140 @Nullable String[] selectionArgs, @Nullable String sortOrder) { 141 return null; 142 } 143 144 145 @Nullable 146 @Override insert(@onNull Uri uri, @Nullable ContentValues values)147 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 148 return null; 149 } 150 151 @Override delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)152 public int delete(@NonNull Uri uri, @Nullable String selection, 153 @Nullable String[] selectionArgs) { 154 return 0; 155 } 156 157 @Override update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)158 public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, 159 @Nullable String[] selectionArgs) { 160 return 0; 161 } 162 } 163