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