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.Handler; 26 27 import androidx.annotation.Nullable; 28 import androidx.annotation.WorkerThread; 29 import androidx.lifecycle.LiveData; 30 import androidx.lifecycle.MutableLiveData; 31 32 import com.android.customization.model.ResourceConstants; 33 import com.android.themepicker.R; 34 import com.android.wallpaper.config.BaseFlags; 35 import com.android.wallpaper.util.PreviewUtils; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * Abstracts the logic to retrieve available grid options from the current Launcher. 42 */ 43 public class LauncherGridOptionsProvider { 44 45 private static final String LIST_OPTIONS = "list_options"; 46 private static final String PREVIEW = "preview"; 47 private static final String DEFAULT_GRID = "default_grid"; 48 49 private static final String COL_NAME = "name"; 50 private static final String COL_ROWS = "rows"; 51 private static final String COL_COLS = "cols"; 52 private static final String COL_PREVIEW_COUNT = "preview_count"; 53 private static final String COL_IS_DEFAULT = "is_default"; 54 55 private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version"; 56 57 private final Context mContext; 58 private final PreviewUtils mPreviewUtils; 59 private final boolean mIsGridApplyButtonEnabled; 60 private List<GridOption> mOptions; 61 private OptionChangeLiveData mLiveData; 62 LauncherGridOptionsProvider(Context context, String authorityMetadataKey)63 public LauncherGridOptionsProvider(Context context, String authorityMetadataKey) { 64 mPreviewUtils = new PreviewUtils(context, authorityMetadataKey); 65 mContext = context; 66 mIsGridApplyButtonEnabled = BaseFlags.get().isGridApplyButtonEnabled(context); 67 } 68 areGridsAvailable()69 boolean areGridsAvailable() { 70 return mPreviewUtils.supportsPreview(); 71 } 72 73 /** 74 * Retrieve the available grids. 75 * @param reload whether to reload grid options if they're cached. 76 */ 77 @WorkerThread 78 @Nullable fetch(boolean reload)79 List<GridOption> fetch(boolean reload) { 80 if (!areGridsAvailable()) { 81 return null; 82 } 83 if (mOptions != null && !reload) { 84 return mOptions; 85 } 86 ContentResolver resolver = mContext.getContentResolver(); 87 String iconPath = mContext.getResources().getString(Resources.getSystem().getIdentifier( 88 ResourceConstants.CONFIG_ICON_MASK, "string", ResourceConstants.ANDROID_PACKAGE)); 89 try (Cursor c = resolver.query(mPreviewUtils.getUri(LIST_OPTIONS), null, null, null, 90 null)) { 91 mOptions = new ArrayList<>(); 92 while(c.moveToNext()) { 93 String name = c.getString(c.getColumnIndex(COL_NAME)); 94 int rows = c.getInt(c.getColumnIndex(COL_ROWS)); 95 int cols = c.getInt(c.getColumnIndex(COL_COLS)); 96 int previewCount = c.getInt(c.getColumnIndex(COL_PREVIEW_COUNT)); 97 boolean isSet = Boolean.parseBoolean(c.getString(c.getColumnIndex(COL_IS_DEFAULT))); 98 String title = mContext.getString(R.string.grid_title_pattern, cols, rows); 99 mOptions.add(new GridOption(title, name, isSet, rows, cols, 100 mPreviewUtils.getUri(PREVIEW), previewCount, iconPath)); 101 } 102 } catch (Exception e) { 103 mOptions = null; 104 } 105 return mOptions; 106 } 107 updateView()108 void updateView() { 109 mLiveData.postValue(new Object()); 110 } 111 applyGrid(String name)112 int applyGrid(String name) { 113 ContentValues values = new ContentValues(); 114 values.put("name", name); 115 values.put("enable_apply_button", mIsGridApplyButtonEnabled); 116 return mContext.getContentResolver().update(mPreviewUtils.getUri(DEFAULT_GRID), values, 117 null, null); 118 } 119 120 /** 121 * Returns an observable that receives a new value each time that the grid options are changed. 122 * Do not call if {@link #areGridsAvailable()} returns false 123 */ getOptionChangeObservable( @ullable Handler handler)124 public LiveData<Object> getOptionChangeObservable( 125 @Nullable Handler handler) { 126 if (mLiveData == null) { 127 mLiveData = new OptionChangeLiveData( 128 mContext, mPreviewUtils.getUri(DEFAULT_GRID), handler); 129 } 130 131 return mLiveData; 132 } 133 134 private static class OptionChangeLiveData extends MutableLiveData<Object> { 135 136 private final ContentResolver mContentResolver; 137 private final Uri mUri; 138 private final ContentObserver mContentObserver; 139 OptionChangeLiveData( Context context, Uri uri, @Nullable Handler handler)140 OptionChangeLiveData( 141 Context context, 142 Uri uri, 143 @Nullable Handler handler) { 144 mContentResolver = context.getContentResolver(); 145 mUri = uri; 146 mContentObserver = new ContentObserver(handler) { 147 @Override 148 public void onChange(boolean selfChange) { 149 // If grid apply button is enabled, user has previewed the grid before applying 150 // the grid change. Thus there is no need to preview again (which will cause a 151 // blank preview as launcher's is loader thread is busy reloading workspace) 152 // after applying grid change. Thus we should ignore ContentObserver#onChange 153 // from launcher 154 if (BaseFlags.get().isGridApplyButtonEnabled(context.getApplicationContext())) { 155 return; 156 } 157 postValue(new Object()); 158 } 159 }; 160 } 161 162 @Override onActive()163 protected void onActive() { 164 mContentResolver.registerContentObserver( 165 mUri, 166 /* notifyForDescendants= */ true, 167 mContentObserver); 168 } 169 170 @Override onInactive()171 protected void onInactive() { 172 mContentResolver.unregisterContentObserver(mContentObserver); 173 } 174 } 175 } 176