1 package com.android.launcher3.graphics;
2 
3 import static com.android.launcher3.Utilities.getPrefs;
4 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
5 import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
6 import static com.android.launcher3.util.Themes.isThemedIconEnabled;
7 
8 import android.annotation.TargetApi;
9 import android.content.ContentProvider;
10 import android.content.ContentValues;
11 import android.content.pm.PackageManager;
12 import android.database.Cursor;
13 import android.database.MatrixCursor;
14 import android.net.Uri;
15 import android.os.Binder;
16 import android.os.Build;
17 import android.os.Bundle;
18 import android.os.Handler;
19 import android.os.IBinder;
20 import android.os.IBinder.DeathRecipient;
21 import android.os.Message;
22 import android.os.Messenger;
23 import android.util.ArrayMap;
24 import android.util.Log;
25 
26 import com.android.launcher3.InvariantDeviceProfile;
27 import com.android.launcher3.InvariantDeviceProfile.GridOption;
28 import com.android.launcher3.Utilities;
29 import com.android.launcher3.config.FeatureFlags;
30 import com.android.launcher3.util.Executors;
31 
32 /**
33  * Exposes various launcher grid options and allows the caller to change them.
34  * APIs:
35  *      /list_options: List the various available grip options, has following columns
36  *          name: name of the grid
37  *          rows: number of rows in the grid
38  *          cols: number of columns in the grid
39  *          preview_count: number of previews available for this grid option. The preview uri
40  *                         looks like /preview/<grid-name>/<preview index starting with 0>
41  *          is_default: true if this grid is currently active
42  *
43  *     /preview: Opens a file stream for the grid preview
44  *
45  *     /default_grid: Call update to set the current grid, with values
46  *          name: name of the grid to apply
47  */
48 public class GridCustomizationsProvider extends ContentProvider {
49 
50     private static final String TAG = "GridCustomizationsProvider";
51 
52     private static final String KEY_NAME = "name";
53     private static final String KEY_ROWS = "rows";
54     private static final String KEY_COLS = "cols";
55     private static final String KEY_PREVIEW_COUNT = "preview_count";
56     private static final String KEY_IS_DEFAULT = "is_default";
57 
58     private static final String KEY_LIST_OPTIONS = "/list_options";
59     private static final String KEY_DEFAULT_GRID = "/default_grid";
60 
61     private static final String METHOD_GET_PREVIEW = "get_preview";
62 
63     private static final String GET_ICON_THEMED = "/get_icon_themed";
64     private static final String SET_ICON_THEMED = "/set_icon_themed";
65     private static final String ICON_THEMED = "/icon_themed";
66     private static final String BOOLEAN_VALUE = "boolean_value";
67 
68     private static final String KEY_SURFACE_PACKAGE = "surface_package";
69     private static final String KEY_CALLBACK = "callback";
70 
71     private final ArrayMap<IBinder, PreviewLifecycleObserver> mActivePreviews = new ArrayMap<>();
72 
73     @Override
onCreate()74     public boolean onCreate() {
75         return true;
76     }
77 
78     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)79     public Cursor query(Uri uri, String[] projection, String selection,
80             String[] selectionArgs, String sortOrder) {
81         switch (uri.getPath()) {
82             case KEY_LIST_OPTIONS: {
83                 MatrixCursor cursor = new MatrixCursor(new String[] {
84                         KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
85                 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
86                 for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
87                     cursor.newRow()
88                             .add(KEY_NAME, gridOption.name)
89                             .add(KEY_ROWS, gridOption.numRows)
90                             .add(KEY_COLS, gridOption.numColumns)
91                             .add(KEY_PREVIEW_COUNT, 1)
92                             .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
93                                     && idp.numRows == gridOption.numRows);
94                 }
95                 return cursor;
96             }
97             case GET_ICON_THEMED:
98             case ICON_THEMED: {
99                 MatrixCursor cursor = new MatrixCursor(new String[] {BOOLEAN_VALUE});
100                 cursor.newRow().add(BOOLEAN_VALUE, isThemedIconEnabled(getContext()) ? 1 : 0);
101                 return cursor;
102             }
103             default:
104                 return null;
105         }
106     }
107 
108     @Override
getType(Uri uri)109     public String getType(Uri uri) {
110         return "vnd.android.cursor.dir/launcher_grid";
111     }
112 
113     @Override
insert(Uri uri, ContentValues initialValues)114     public Uri insert(Uri uri, ContentValues initialValues) {
115         return null;
116     }
117 
118     @Override
delete(Uri uri, String selection, String[] selectionArgs)119     public int delete(Uri uri, String selection, String[] selectionArgs) {
120         return 0;
121     }
122 
123     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)124     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
125         switch (uri.getPath()) {
126             case KEY_DEFAULT_GRID: {
127                 String gridName = values.getAsString(KEY_NAME);
128                 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
129                 // Verify that this is a valid grid option
130                 GridOption match = null;
131                 for (GridOption option : idp.parseAllGridOptions(getContext())) {
132                     if (option.name.equals(gridName)) {
133                         match = option;
134                         break;
135                     }
136                 }
137                 if (match == null) {
138                     return 0;
139                 }
140 
141                 idp.setCurrentGrid(getContext(), gridName);
142                 return 1;
143             }
144             case ICON_THEMED:
145             case SET_ICON_THEMED: {
146                 if (FeatureFlags.ENABLE_THEMED_ICONS.get()) {
147                     getPrefs(getContext()).edit()
148                             .putBoolean(KEY_THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE))
149                             .apply();
150                 }
151                 return 1;
152             }
153             default:
154                 return 0;
155         }
156     }
157 
158     @Override
call(String method, String arg, Bundle extras)159     public Bundle call(String method, String arg, Bundle extras) {
160         if (getContext().checkPermission("android.permission.BIND_WALLPAPER",
161                 Binder.getCallingPid(), Binder.getCallingUid())
162                 != PackageManager.PERMISSION_GRANTED) {
163             return null;
164         }
165 
166         if (!Utilities.ATLEAST_R || !METHOD_GET_PREVIEW.equals(method)) {
167             return null;
168         }
169         return getPreview(extras);
170     }
171 
172     @TargetApi(Build.VERSION_CODES.R)
getPreview(Bundle request)173     private synchronized Bundle getPreview(Bundle request) {
174         PreviewLifecycleObserver observer = null;
175         try {
176             PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request);
177 
178             // Destroy previous
179             destroyObserver(mActivePreviews.get(renderer.getHostToken()));
180 
181             observer = new PreviewLifecycleObserver(renderer);
182             mActivePreviews.put(renderer.getHostToken(), observer);
183 
184             renderer.loadAsync();
185             renderer.getHostToken().linkToDeath(observer, 0);
186 
187             Bundle result = new Bundle();
188             result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
189 
190             Messenger messenger =
191                     new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
192             Message msg = Message.obtain();
193             msg.replyTo = messenger;
194             result.putParcelable(KEY_CALLBACK, msg);
195             return result;
196         } catch (Exception e) {
197             Log.e(TAG, "Unable to generate preview", e);
198             if (observer != null) {
199                 destroyObserver(observer);
200             }
201             return null;
202         }
203     }
204 
destroyObserver(PreviewLifecycleObserver observer)205     private synchronized void destroyObserver(PreviewLifecycleObserver observer) {
206         if (observer == null || observer.destroyed) {
207             return;
208         }
209         observer.destroyed = true;
210         observer.renderer.getHostToken().unlinkToDeath(observer, 0);
211         Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy);
212         PreviewLifecycleObserver cached = mActivePreviews.get(observer.renderer.getHostToken());
213         if (cached == observer) {
214             mActivePreviews.remove(observer.renderer.getHostToken());
215         }
216     }
217 
218     private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient {
219 
220         public final PreviewSurfaceRenderer renderer;
221         public boolean destroyed = false;
222 
PreviewLifecycleObserver(PreviewSurfaceRenderer renderer)223         PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) {
224             this.renderer = renderer;
225         }
226 
227         @Override
handleMessage(Message message)228         public boolean handleMessage(Message message) {
229             destroyObserver(this);
230             return true;
231         }
232 
233         @Override
binderDied()234         public void binderDied() {
235             destroyObserver(this);
236         }
237     }
238 }
239