1 /* Copyright 2016 Braden Farmer
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 package com.farmerbb.taskbar.adapter;
18 import android.content.ComponentName;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.content.pm.ActivityInfo;
23 import android.graphics.Color;
24 import android.graphics.Typeface;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.support.annotation.NonNull;
29 import android.support.v4.content.ContextCompat;
30 import android.support.v4.content.LocalBroadcastManager;
31 import android.support.v4.graphics.ColorUtils;
32 import android.util.SparseIntArray;
33 import android.view.LayoutInflater;
34 import android.view.MotionEvent;
35 import android.view.PointerIcon;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.ArrayAdapter;
39 import android.widget.ImageView;
40 import android.widget.LinearLayout;
41 import android.widget.SectionIndexer;
42 import android.widget.TextView;
44 import com.farmerbb.taskbar.R;
45 import com.farmerbb.taskbar.util.AppEntry;
46 import com.farmerbb.taskbar.util.FreeformHackHelper;
47 import com.farmerbb.taskbar.util.TopApps;
48 import com.farmerbb.taskbar.util.U;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashMap;
53 import java.util.List;
56 public class StartMenuAdapter extends ArrayAdapter<AppEntry> implements SectionIndexer {
58 private boolean isGrid = false;
60 private final List<Character> sections = new ArrayList<>();
61 private final SparseIntArray gpfsCache = new SparseIntArray();
62 private final SparseIntArray gsfpCache = new SparseIntArray();
63 private final Map<AppEntry, Boolean> topAppsCache = new HashMap<>();
65 private final List<Character> lowercase = Arrays.asList(
66 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
67 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
70 private final List<Character> uppercase = Arrays.asList(
71 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
72 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
75 public StartMenuAdapter(Context context, int layout, List<AppEntry> list) {
76 super(context, layout, list);
77 isGrid = layout == R.layout.row_alt;
79 updateList(list, true);
83 public @NonNull View getView(int position, View convertView, final @NonNull ViewGroup parent) {
84 // Check if an existing view is being reused, otherwise inflate the view
85 if(convertView == null)
86 convertView = LayoutInflater.from(getContext()).inflate(isGrid ? R.layout.row_alt : R.layout.row, parent, false);
88 final AppEntry entry = getItem(position);
91 final SharedPreferences pref = U.getSharedPreferences(getContext());
93 TextView textView = convertView.findViewById(R.id.name);
94 textView.setText(entry.getLabel());
95 textView.setTypeface(null, isTopApp(entry) ? Typeface.BOLD : Typeface.NORMAL);
97 switch(pref.getString("theme", "light")) {
99 textView.setTextColor(ContextCompat.getColor(getContext(), R.color.text_color));
102 textView.setTextColor(ContextCompat.getColor(getContext(), R.color.text_color_dark));
106 ImageView imageView = convertView.findViewById(R.id.icon);
107 imageView.setImageDrawable(entry.getIcon(getContext()));
109 LinearLayout layout = convertView.findViewById(R.id.entry);
110 layout.setOnClickListener(view -> {
111 LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
112 U.launchApp(getContext(), entry, null, false, false, view);
115 layout.setOnLongClickListener(view -> {
116 int[] location = new int[2];
117 view.getLocationOnScreen(location);
118 openContextMenu(entry, location);
122 layout.setOnGenericMotionListener((view, motionEvent) -> {
123 int action = motionEvent.getAction();
125 if(action == MotionEvent.ACTION_BUTTON_PRESS
126 && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
127 int[] location = new int[2];
128 view.getLocationOnScreen(location);
129 openContextMenu(entry, location);
132 if(action == MotionEvent.ACTION_SCROLL && U.visualFeedbackEnabled(getContext()))
133 view.setBackgroundColor(0);
138 if(U.visualFeedbackEnabled(getContext())) {
139 layout.setOnHoverListener((v, event) -> {
140 if(event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
141 int backgroundTint = pref.getBoolean("transparent_start_menu", false)
142 ? U.getAccentColor(getContext())
143 : U.getBackgroundTint(getContext());
145 //noinspection ResourceAsColor
146 backgroundTint = ColorUtils.setAlphaComponent(backgroundTint, Color.alpha(backgroundTint) / 2);
147 v.setBackgroundColor(backgroundTint);
150 if(event.getAction() == MotionEvent.ACTION_HOVER_EXIT)
151 v.setBackgroundColor(0);
153 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
154 v.setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
160 if(pref.getBoolean("visual_feedback", true)) {
161 layout.setOnTouchListener((v, event) -> {
162 v.setAlpha(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE ? 0.5f : 1);
170 private boolean isTopApp(AppEntry entry) {
171 if(topAppsCache.containsKey(entry))
172 return topAppsCache.get(entry);
174 Intent intent = new Intent();
175 intent.setComponent(ComponentName.unflattenFromString(entry.getComponentName()));
176 ActivityInfo activityInfo = intent.resolveActivityInfo(getContext().getPackageManager(), 0);
178 if(activityInfo != null) {
179 TopApps topApps = TopApps.getInstance(getContext());
180 boolean isTopApp = topApps.isTopApp(activityInfo.packageName + "/" + activityInfo.name + ":" + entry.getUserId(getContext()))
181 || topApps.isTopApp(activityInfo.packageName + "/" + activityInfo.name)
182 || topApps.isTopApp(activityInfo.name);
184 topAppsCache.put(entry, isTopApp);
188 topAppsCache.put(entry, false);
192 private void openContextMenu(final AppEntry entry, final int[] location) {
193 LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU_NO_RESET"));
195 Bundle args = new Bundle();
196 args.putSerializable("app_entry", entry);
197 args.putBoolean("launched_from_start_menu", true);
198 args.putInt("x", location[0]);
199 args.putInt("y", location[1]);
201 new Handler().postDelayed(() -> U.startContextMenuActivity(getContext(), args), shouldDelay() ? 100 : 0);
204 private boolean shouldDelay() {
205 SharedPreferences pref = U.getSharedPreferences(getContext());
206 return U.hasFreeformSupport(getContext())
207 && pref.getBoolean("freeform_hack", false)
208 && !FreeformHackHelper.getInstance().isFreeformHackActive();
211 public void updateList(List<AppEntry> list) {
212 updateList(list, false);
215 private void updateList(List<AppEntry> list, boolean firstUpdate) {
222 topAppsCache.clear();
227 SharedPreferences pref = U.getSharedPreferences(getContext());
228 if(pref.getBoolean("scrollbar", false)) {
229 for(AppEntry entry : list) {
230 char firstLetter = getSectionForAppEntry(entry);
231 if(!sections.contains(firstLetter))
232 sections.add(firstLetter);
237 private char getSectionForAppEntry(AppEntry entry) {
241 char origChar = entry.getLabel().charAt(0);
242 if(uppercase.contains(origChar))
245 if(lowercase.contains(origChar))
246 return uppercase.get(lowercase.indexOf(origChar));
252 public int getPositionForSection(int section) {
253 int cachedPos = gpfsCache.get(section, -1);
257 for(int i = 0; i < getCount(); i++) {
258 if(sections.get(section) == getSectionForAppEntry(getItem(i))) {
259 gpfsCache.put(section, i);
264 gpfsCache.put(section, 0);
269 public int getSectionForPosition(int position) {
270 int cachedSection = gsfpCache.get(position, -1);
271 if(cachedSection != -1)
272 return cachedSection;
274 for(int i = 0; i < sections.size(); i++) {
275 if(sections.get(i) == getSectionForAppEntry(getItem(position))) {
276 gsfpCache.put(position, i);
281 gsfpCache.put(position, 0);
286 public Object[] getSections() {
287 return sections.toArray();