1 /* 2 * Copyright (C) 2020 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.launcher3.secondarydisplay; 17 18 import static android.content.Context.MODE_PRIVATE; 19 20 import android.content.ComponentName; 21 import android.content.SharedPreferences; 22 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 23 import android.os.Process; 24 import android.os.UserHandle; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.View.OnClickListener; 28 import android.view.View.OnLongClickListener; 29 import android.view.ViewGroup; 30 import android.widget.BaseAdapter; 31 32 import com.android.launcher3.AbstractFloatingView; 33 import com.android.launcher3.BubbleTextView; 34 import com.android.launcher3.R; 35 import com.android.launcher3.allapps.AllAppsStore; 36 import com.android.launcher3.allapps.AppInfoComparator; 37 import com.android.launcher3.model.data.AppInfo; 38 import com.android.launcher3.model.data.ItemInfo; 39 import com.android.launcher3.pm.UserCache; 40 import com.android.launcher3.popup.SystemShortcut; 41 import com.android.launcher3.util.ComponentKey; 42 import com.android.launcher3.util.Executors; 43 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.HashSet; 47 import java.util.Objects; 48 import java.util.Set; 49 import java.util.function.Function; 50 import java.util.stream.Collectors; 51 52 /** 53 * Adapter to manage pinned apps and show then in a grid. 54 */ 55 public class PinnedAppsAdapter extends BaseAdapter implements OnSharedPreferenceChangeListener { 56 57 private static final String PINNED_APPS_KEY = "pinned_apps"; 58 59 private final SecondaryDisplayLauncher mLauncher; 60 private final OnClickListener mOnClickListener; 61 private final OnLongClickListener mOnLongClickListener; 62 private final SharedPreferences mPrefs; 63 private final AllAppsStore<SecondaryDisplayLauncher> mAllAppsList; 64 private final AppInfoComparator mAppNameComparator; 65 66 private final Set<ComponentKey> mPinnedApps = new HashSet<>(); 67 private final ArrayList<AppInfo> mItems = new ArrayList<>(); 68 PinnedAppsAdapter( SecondaryDisplayLauncher launcher, AllAppsStore<SecondaryDisplayLauncher> allAppsStore, OnLongClickListener onLongClickListener)69 public PinnedAppsAdapter( 70 SecondaryDisplayLauncher launcher, 71 AllAppsStore<SecondaryDisplayLauncher> allAppsStore, 72 OnLongClickListener onLongClickListener) { 73 mLauncher = launcher; 74 mOnClickListener = launcher.getItemOnClickListener(); 75 mOnLongClickListener = onLongClickListener; 76 mAllAppsList = allAppsStore; 77 mPrefs = launcher.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE); 78 mAppNameComparator = new AppInfoComparator(launcher); 79 80 mAllAppsList.addUpdateListener(this::createFilteredAppsList); 81 } 82 83 /** 84 * {@inheritDoc} 85 */ 86 @Override onSharedPreferenceChanged(SharedPreferences prefs, String key)87 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 88 if (PINNED_APPS_KEY.equals(key)) { 89 Executors.MODEL_EXECUTOR.submit(() -> { 90 Set<ComponentKey> apps = prefs.getStringSet(key, Collections.emptySet()) 91 .stream() 92 .map(this::parseComponentKey) 93 .filter(Objects::nonNull) 94 .collect(Collectors.toSet()); 95 Executors.MAIN_EXECUTOR.submit(() -> { 96 mPinnedApps.clear(); 97 mPinnedApps.addAll(apps); 98 createFilteredAppsList(); 99 }); 100 }); 101 } 102 } 103 104 /** 105 * {@inheritDoc} 106 */ 107 @Override getCount()108 public int getCount() { 109 return mItems.size(); 110 } 111 112 /** 113 * {@inheritDoc} 114 */ 115 @Override getItem(int position)116 public AppInfo getItem(int position) { 117 return mItems.get(position); 118 } 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override getItemId(int position)124 public long getItemId(int position) { 125 return position; 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 @Override getView(int position, View view, ViewGroup parent)132 public View getView(int position, View view, ViewGroup parent) { 133 BubbleTextView icon; 134 if (view instanceof BubbleTextView) { 135 icon = (BubbleTextView) view; 136 } else { 137 icon = (BubbleTextView) LayoutInflater.from(parent.getContext()) 138 .inflate(R.layout.app_icon, parent, false); 139 icon.setOnClickListener(mOnClickListener); 140 icon.setOnLongClickListener(mOnLongClickListener); 141 icon.setLongPressTimeoutFactor(1f); 142 int padding = mLauncher.getDeviceProfile().edgeMarginPx; 143 icon.setPadding(padding, padding, padding, padding); 144 } 145 146 icon.applyFromApplicationInfo(mItems.get(position)); 147 return icon; 148 } 149 createFilteredAppsList()150 private void createFilteredAppsList() { 151 mItems.clear(); 152 mPinnedApps.stream().map(mAllAppsList::getApp) 153 .filter(Objects::nonNull).forEach(mItems::add); 154 mItems.sort(mAppNameComparator); 155 notifyDataSetChanged(); 156 } 157 158 /** 159 * Initialized the pinned apps list and starts listening for changes 160 */ init()161 public void init() { 162 mPrefs.registerOnSharedPreferenceChangeListener(this); 163 onSharedPreferenceChanged(mPrefs, PINNED_APPS_KEY); 164 } 165 166 /** 167 * Stops listening for any pinned apps changes 168 */ destroy()169 public void destroy() { 170 mPrefs.unregisterOnSharedPreferenceChangeListener(this); 171 } 172 173 /** 174 * Pins or unpins apps from home screen 175 */ update(ItemInfo info, Function<ComponentKey, Boolean> op)176 public void update(ItemInfo info, Function<ComponentKey, Boolean> op) { 177 ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user); 178 if (op.apply(key)) { 179 createFilteredAppsList(); 180 Set<ComponentKey> copy = new HashSet<>(mPinnedApps); 181 Executors.MODEL_EXECUTOR.submit(() -> 182 mPrefs.edit().putStringSet(PINNED_APPS_KEY, 183 copy.stream().map(this::encode).collect(Collectors.toSet())) 184 .apply()); 185 } 186 } 187 parseComponentKey(String string)188 private ComponentKey parseComponentKey(String string) { 189 try { 190 String[] parts = string.split("#"); 191 UserHandle user; 192 if (parts.length > 2) { 193 user = UserCache.INSTANCE.get(mLauncher) 194 .getUserForSerialNumber(Long.parseLong(parts[2])); 195 } else { 196 user = Process.myUserHandle(); 197 } 198 ComponentName cn = ComponentName.unflattenFromString(parts[0]); 199 return new ComponentKey(cn, user); 200 } catch (Exception e) { 201 return null; 202 } 203 } 204 encode(ComponentKey key)205 private String encode(ComponentKey key) { 206 return key.componentName.flattenToShortString() + "#" 207 + UserCache.INSTANCE.get(mLauncher).getSerialNumberForUser(key.user); 208 } 209 210 /** 211 * Returns a system shortcut to pin/unpin a shortcut 212 */ getSystemShortcut(ItemInfo info, View originalView)213 public SystemShortcut getSystemShortcut(ItemInfo info, View originalView) { 214 return new PinUnPinShortcut(mLauncher, info, originalView, 215 mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user))); 216 } 217 218 /** 219 * Pins app to home screen 220 */ addPinnedApp(ItemInfo info)221 public void addPinnedApp(ItemInfo info) { 222 update(info, mPinnedApps::add); 223 } 224 225 private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> { 226 227 private final boolean mIsPinned; 228 PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, View originalView, boolean isPinned)229 PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, View originalView, 230 boolean isPinned) { 231 super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin, 232 isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace, 233 target, info, originalView); 234 mIsPinned = isPinned; 235 } 236 237 @Override onClick(View view)238 public void onClick(View view) { 239 if (mIsPinned) { 240 update(mItemInfo, mPinnedApps::remove); 241 } else { 242 update(mItemInfo, mPinnedApps::add); 243 } 244 AbstractFloatingView.closeAllOpenViews(mLauncher); 245 } 246 } 247 } 248