1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.customization.model.grid; 17 18 import android.content.ContentResolver; 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.database.ContentObserver; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.view.SurfaceView; 28 29 import androidx.annotation.Nullable; 30 import androidx.annotation.WorkerThread; 31 import androidx.lifecycle.LiveData; 32 import androidx.lifecycle.MutableLiveData; 33 34 import com.android.customization.model.ResourceConstants; 35 import com.android.wallpaper.R; 36 import com.android.wallpaper.config.BaseFlags; 37 import com.android.wallpaper.util.PreviewUtils; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Abstracts the logic to retrieve available grid options from the current Launcher. 44 */ 45 public class LauncherGridOptionsProvider { 46 47 private static final String LIST_OPTIONS = "list_options"; 48 private static final String PREVIEW = "preview"; 49 private static final String DEFAULT_GRID = "default_grid"; 50 51 private static final String COL_NAME = "name"; 52 private static final String COL_ROWS = "rows"; 53 private static final String COL_COLS = "cols"; 54 private static final String COL_PREVIEW_COUNT = "preview_count"; 55 private static final String COL_IS_DEFAULT = "is_default"; 56 57 private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version"; 58 59 private final Context mContext; 60 private final PreviewUtils mPreviewUtils; 61 private final boolean mIsGridApplyButtonEnabled; 62 private List<GridOption> mOptions; 63 private OptionChangeLiveData mLiveData; 64 LauncherGridOptionsProvider(Context context, String authorityMetadataKey)65 public LauncherGridOptionsProvider(Context context, String authorityMetadataKey) { 66 mPreviewUtils = new PreviewUtils(context, authorityMetadataKey); 67 mContext = context; 68 mIsGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(context); 69 } 70 areGridsAvailable()71 boolean areGridsAvailable() { 72 return mPreviewUtils.supportsPreview(); 73 } 74 75 /** 76 * Retrieve the available grids. 77 * @param reload whether to reload grid options if they're cached. 78 */ 79 @WorkerThread 80 @Nullable fetch(boolean reload)81 List<GridOption> fetch(boolean reload) { 82 if (!areGridsAvailable()) { 83 return null; 84 } 85 if (mOptions != null && !reload) { 86 return mOptions; 87 } 88 ContentResolver resolver = mContext.getContentResolver(); 89 String iconPath = mContext.getResources().getString(Resources.getSystem().getIdentifier( 90 ResourceConstants.CONFIG_ICON_MASK, "string", ResourceConstants.ANDROID_PACKAGE)); 91 try (Cursor c = resolver.query(mPreviewUtils.getUri(LIST_OPTIONS), null, null, null, 92 null)) { 93 mOptions = new ArrayList<>(); 94 while(c.moveToNext()) { 95 String name = c.getString(c.getColumnIndex(COL_NAME)); 96 int rows = c.getInt(c.getColumnIndex(COL_ROWS)); 97 int cols = c.getInt(c.getColumnIndex(COL_COLS)); 98 int previewCount = c.getInt(c.getColumnIndex(COL_PREVIEW_COUNT)); 99 boolean isSet = Boolean.parseBoolean(c.getString(c.getColumnIndex(COL_IS_DEFAULT))); 100 String title = mContext.getString(R.string.grid_title_pattern, cols, rows); 101 mOptions.add(new GridOption(title, name, isSet, rows, cols, 102 mPreviewUtils.getUri(PREVIEW), previewCount, iconPath)); 103 } 104 } catch (Exception e) { 105 mOptions = null; 106 } 107 return mOptions; 108 } 109 110 /** 111 * Request rendering of home screen preview via Launcher to Wallpaper using SurfaceView 112 * @param name the grid option name 113 * @param bundle surface view request bundle generated from 114 * {@link com.android.wallpaper.util.SurfaceViewUtils#createSurfaceViewRequest(SurfaceView)}. 115 * @param callback To receive the result (will be called on the main thread) 116 */ renderPreview(String name, Bundle bundle, PreviewUtils.WorkspacePreviewCallback callback)117 void renderPreview(String name, Bundle bundle, 118 PreviewUtils.WorkspacePreviewCallback callback) { 119 bundle.putString("name", name); 120 mPreviewUtils.renderPreview(bundle, callback); 121 } 122 updateView()123 void updateView() { 124 mLiveData.postValue(new Object()); 125 } 126 applyGrid(String name)127 int applyGrid(String name) { 128 ContentValues values = new ContentValues(); 129 values.put("name", name); 130 values.put("enable_apply_button", mIsGridApplyButtonEnabled); 131 return mContext.getContentResolver().update(mPreviewUtils.getUri(DEFAULT_GRID), values, 132 null, null); 133 } 134 135 /** 136 * Returns an observable that receives a new value each time that the grid options are changed. 137 * Do not call if {@link #areGridsAvailable()} returns false 138 */ getOptionChangeObservable( @ullable Handler handler)139 public LiveData<Object> getOptionChangeObservable( 140 @Nullable Handler handler) { 141 if (mLiveData == null) { 142 mLiveData = new OptionChangeLiveData( 143 mContext, mPreviewUtils.getUri(DEFAULT_GRID), handler); 144 } 145 146 return mLiveData; 147 } 148 149 private static class OptionChangeLiveData extends MutableLiveData<Object> { 150 151 private final ContentResolver mContentResolver; 152 private final Uri mUri; 153 private final ContentObserver mContentObserver; 154 OptionChangeLiveData( Context context, Uri uri, @Nullable Handler handler)155 OptionChangeLiveData( 156 Context context, 157 Uri uri, 158 @Nullable Handler handler) { 159 mContentResolver = context.getContentResolver(); 160 mUri = uri; 161 mContentObserver = new ContentObserver(handler) { 162 @Override 163 public void onChange(boolean selfChange) { 164 // If grid apply button is enabled, user has previewed the grid before applying 165 // the grid change. Thus there is no need to preview again (which will cause a 166 // blank preview as launcher's is loader thread is busy reloading workspace) 167 // after applying grid change. Thus we should ignore ContentObserver#onChange 168 // from launcher 169 if (BaseFlags.get().isGridApplyButtonEnabled(context.getApplicationContext())) { 170 return; 171 } 172 postValue(new Object()); 173 } 174 }; 175 } 176 177 @Override onActive()178 protected void onActive() { 179 mContentResolver.registerContentObserver( 180 mUri, 181 /* notifyForDescendants= */ true, 182 mContentObserver); 183 } 184 185 @Override onInactive()186 protected void onInactive() { 187 mContentResolver.unregisterContentObserver(mContentObserver); 188 } 189 } 190 } 191