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