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