1 package com.android.customization.model.clock; 2 3 import android.content.ContentResolver; 4 import android.content.Context; 5 import android.content.pm.ApplicationInfo; 6 import android.content.pm.PackageManager; 7 import android.content.pm.PackageManager.NameNotFoundException; 8 import android.content.pm.ProviderInfo; 9 import android.database.Cursor; 10 import android.net.Uri; 11 import android.os.Handler; 12 import android.os.Looper; 13 import android.text.TextUtils; 14 import android.util.Log; 15 16 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 17 import com.android.customization.model.clock.Clockface.Builder; 18 import com.android.wallpaper.R; 19 import com.android.wallpaper.asset.ContentUriAsset; 20 21 import com.bumptech.glide.Glide; 22 import com.bumptech.glide.request.RequestOptions; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.concurrent.ExecutorService; 27 import java.util.concurrent.Executors; 28 29 public class ContentProviderClockProvider implements ClockProvider { 30 31 private static final String TAG = "ContentProviderClockProvider"; 32 private static final ExecutorService sExecutorService = Executors.newSingleThreadExecutor(); 33 private static final String LIST_OPTIONS = "list_options"; 34 private static final String COL_TITLE = "title"; 35 private static final String COL_ID = "id"; 36 private static final String COL_THUMBNAIL = "thumbnail"; 37 private static final String COL_PREVIEW = "preview"; 38 39 private final Context mContext; 40 private final ProviderInfo mProviderInfo; 41 private List<Clockface> mClocks; 42 private boolean mClockContentAvailable; 43 ContentProviderClockProvider(Context context)44 public ContentProviderClockProvider(Context context) { 45 mContext = context; 46 String providerAuthority = mContext.getString(R.string.clocks_provider_authority); 47 // TODO: check permissions if needed 48 mProviderInfo = TextUtils.isEmpty(providerAuthority) ? null 49 : mContext.getPackageManager().resolveContentProvider(providerAuthority, 50 PackageManager.MATCH_SYSTEM_ONLY); 51 52 if (TextUtils.isEmpty(mContext.getString(R.string.clocks_stub_package))) { 53 mClockContentAvailable = false; 54 } else { 55 try { 56 ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo( 57 mContext.getString(R.string.clocks_stub_package), 58 PackageManager.MATCH_SYSTEM_ONLY); 59 mClockContentAvailable = applicationInfo != null; 60 } catch (NameNotFoundException e) { 61 mClockContentAvailable = false; 62 } 63 } 64 } 65 66 @Override isAvailable()67 public boolean isAvailable() { 68 return mProviderInfo != null && mClockContentAvailable 69 && (mClocks == null || !mClocks.isEmpty()); 70 } 71 72 @Override fetch(OptionsFetchedListener<Clockface> callback, boolean reload)73 public void fetch(OptionsFetchedListener<Clockface> callback, boolean reload) { 74 if (!isAvailable()) { 75 if (callback != null) { 76 callback.onError(null); 77 } 78 return; 79 } 80 if (mClocks != null && !reload) { 81 if (callback != null) { 82 if (!mClocks.isEmpty()) { 83 callback.onOptionsLoaded(mClocks); 84 } else { 85 callback.onError(null); 86 } 87 } 88 return; 89 } 90 sExecutorService.submit(() -> { 91 Uri optionsUri = new Uri.Builder() 92 .scheme(ContentResolver.SCHEME_CONTENT) 93 .authority(mProviderInfo.authority) 94 .appendPath(LIST_OPTIONS) 95 .build(); 96 97 ContentResolver resolver = mContext.getContentResolver(); 98 99 List<Clockface> clockfaces = new ArrayList<>(); 100 try (Cursor c = resolver.query(optionsUri, null, null, null, null)) { 101 while (c != null && c.moveToNext()) { 102 String id = c.getString(c.getColumnIndex(COL_ID)); 103 String title = c.getString(c.getColumnIndex(COL_TITLE)); 104 String thumbnailUri = c.getString(c.getColumnIndex(COL_THUMBNAIL)); 105 String previewUri = c.getString(c.getColumnIndex(COL_PREVIEW)); 106 Uri thumbnail = Uri.parse(thumbnailUri); 107 Uri preview = Uri.parse(previewUri); 108 109 Clockface.Builder builder = new Builder(); 110 builder.setId(id).setTitle(title) 111 .setThumbnail(new ContentUriAsset(mContext, thumbnail, 112 RequestOptions.fitCenterTransform())) 113 .setPreview(new ContentUriAsset(mContext, preview, 114 RequestOptions.fitCenterTransform())); 115 clockfaces.add(builder.build()); 116 } 117 Glide.get(mContext).clearDiskCache(); 118 } catch (Exception e) { 119 clockfaces = null; 120 Log.e(TAG, "Failed to query clock face options.", e); 121 } 122 final List<Clockface> clockfaceList = clockfaces; 123 new Handler(Looper.getMainLooper()).post(() -> { 124 mClocks = clockfaceList; 125 if (callback != null) { 126 if (!mClocks.isEmpty()) { 127 callback.onOptionsLoaded(mClocks); 128 } else { 129 callback.onError(null); 130 } 131 } 132 }); 133 }); 134 } 135 } 136