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