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.AsyncTask; 12 import android.text.TextUtils; 13 14 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 15 import com.android.customization.model.clock.Clockface.Builder; 16 import com.android.wallpaper.R; 17 import com.android.wallpaper.asset.ContentUriAsset; 18 19 import com.bumptech.glide.Glide; 20 import com.bumptech.glide.request.RequestOptions; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 public class ContentProviderClockProvider implements ClockProvider { 26 27 private final Context mContext; 28 private final ProviderInfo mProviderInfo; 29 private List<Clockface> mClocks; 30 private boolean mClockContentAvailable; 31 ContentProviderClockProvider(Context context)32 public ContentProviderClockProvider(Context context) { 33 mContext = context; 34 String providerAuthority = mContext.getString(R.string.clocks_provider_authority); 35 // TODO: check permissions if needed 36 mProviderInfo = TextUtils.isEmpty(providerAuthority) ? null 37 : mContext.getPackageManager().resolveContentProvider(providerAuthority, 38 PackageManager.MATCH_SYSTEM_ONLY); 39 40 if (TextUtils.isEmpty(mContext.getString(R.string.clocks_stub_package))) { 41 mClockContentAvailable = false; 42 } else { 43 try { 44 ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo( 45 mContext.getString(R.string.clocks_stub_package), 46 PackageManager.MATCH_SYSTEM_ONLY); 47 mClockContentAvailable = applicationInfo != null; 48 } catch (NameNotFoundException e) { 49 mClockContentAvailable = false; 50 } 51 } 52 } 53 54 @Override isAvailable()55 public boolean isAvailable() { 56 return mProviderInfo != null && mClockContentAvailable 57 && (mClocks == null || !mClocks.isEmpty()); 58 } 59 60 @Override fetch(OptionsFetchedListener<Clockface> callback, boolean reload)61 public void fetch(OptionsFetchedListener<Clockface> callback, boolean reload) { 62 if (!isAvailable()) { 63 if (callback != null) { 64 callback.onError(null); 65 } 66 return; 67 } 68 if (mClocks != null && !reload) { 69 if (callback != null) { 70 if (!mClocks.isEmpty()) { 71 callback.onOptionsLoaded(mClocks); 72 } else { 73 callback.onError(null); 74 } 75 } 76 return; 77 } 78 new ClocksFetchTask(mContext, mProviderInfo, options -> { 79 mClocks = options; 80 if (callback != null) { 81 if (!mClocks.isEmpty()) { 82 callback.onOptionsLoaded(mClocks); 83 } else { 84 callback.onError(null); 85 } 86 } 87 }).execute(); 88 } 89 90 private static class ClocksFetchTask extends AsyncTask<Void, Void, List<Clockface>> { 91 92 private static final String LIST_OPTIONS = "list_options"; 93 94 private static final String COL_NAME = "name"; 95 private static final String COL_TITLE = "title"; 96 private static final String COL_ID = "id"; 97 private static final String COL_THUMBNAIL = "thumbnail"; 98 private static final String COL_PREVIEW = "preview"; 99 100 private final OptionsFetchedListener<Clockface> mCallback; 101 private Context mContext; 102 private final ProviderInfo mProviderInfo; 103 ClocksFetchTask(Context context, ProviderInfo providerInfo, OptionsFetchedListener<Clockface> callback)104 public ClocksFetchTask(Context context, ProviderInfo providerInfo, 105 OptionsFetchedListener<Clockface> callback) { 106 super(); 107 mContext = context; 108 mProviderInfo = providerInfo; 109 mCallback = callback; 110 } 111 112 @Override doInBackground(Void... voids)113 protected List<Clockface> doInBackground(Void... voids) { 114 Uri optionsUri = new Uri.Builder() 115 .scheme(ContentResolver.SCHEME_CONTENT) 116 .authority(mProviderInfo.authority) 117 .appendPath(LIST_OPTIONS) 118 .build(); 119 120 ContentResolver resolver = mContext.getContentResolver(); 121 122 List<Clockface> clockfaces = new ArrayList<>(); 123 try (Cursor c = resolver.query(optionsUri, null, null, null, null)) { 124 while(c.moveToNext()) { 125 String id = c.getString(c.getColumnIndex(COL_ID)); 126 String title = c.getString(c.getColumnIndex(COL_TITLE)); 127 String thumbnailUri = c.getString(c.getColumnIndex(COL_THUMBNAIL)); 128 String previewUri = c.getString(c.getColumnIndex(COL_PREVIEW)); 129 Uri thumbnail = Uri.parse(thumbnailUri); 130 Uri preview = Uri.parse(previewUri); 131 132 Clockface.Builder builder = new Builder(); 133 builder.setId(id).setTitle(title) 134 .setThumbnail(new ContentUriAsset(mContext, thumbnail, 135 RequestOptions.fitCenterTransform())) 136 .setPreview(new ContentUriAsset(mContext, preview, 137 RequestOptions.fitCenterTransform())); 138 clockfaces.add(builder.build()); 139 } 140 Glide.get(mContext).clearDiskCache(); 141 } catch (Exception e) { 142 clockfaces = null; 143 } finally { 144 mContext = null; 145 } 146 return clockfaces; 147 } 148 149 @Override onPostExecute(List<Clockface> clockfaces)150 protected void onPostExecute(List<Clockface> clockfaces) { 151 super.onPostExecute(clockfaces); 152 mCallback.onOptionsLoaded(clockfaces); 153 } 154 } 155 } 156