1 /* Based on code by Leonardo Fischer
2 * See https://github.com/lgfischer/WidgetHostExample
4 * Copyright 2016 Braden Farmer
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package com.farmerbb.taskbar.ui;
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.annotation.SuppressLint;
24 import android.annotation.TargetApi;
25 import android.appwidget.AppWidgetHost;
26 import android.appwidget.AppWidgetHostView;
27 import android.appwidget.AppWidgetManager;
28 import android.appwidget.AppWidgetProviderInfo;
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.SharedPreferences;
34 import android.content.pm.PackageManager;
35 import android.content.res.Configuration;
36 import android.graphics.Color;
37 import android.graphics.ColorMatrix;
38 import android.graphics.ColorMatrixColorFilter;
39 import android.graphics.drawable.Drawable;
40 import android.os.Build;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Process;
45 import androidx.annotation.VisibleForTesting;
46 import androidx.core.graphics.ColorUtils;
47 import android.util.SparseArray;
48 import android.view.MotionEvent;
49 import android.view.PointerIcon;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.WindowManager;
53 import android.widget.FrameLayout;
54 import android.widget.ImageView;
55 import android.widget.LinearLayout;
56 import android.widget.TextView;
57 import android.widget.Toast;
59 import com.farmerbb.taskbar.R;
60 import com.farmerbb.taskbar.activity.DashboardActivity;
61 import com.farmerbb.taskbar.helper.LauncherHelper;
62 import com.farmerbb.taskbar.util.TaskbarPosition;
63 import com.farmerbb.taskbar.helper.DashboardHelper;
64 import com.farmerbb.taskbar.widget.DashboardCell;
65 import com.farmerbb.taskbar.helper.FreeformHackHelper;
66 import com.farmerbb.taskbar.util.U;
68 import java.util.List;
70 import static com.farmerbb.taskbar.util.Constants.*;
72 public class DashboardController extends UIController {
73 private AppWidgetManager appWidgetManager;
74 private AppWidgetHost mAppWidgetHost;
76 private LinearLayout layout;
78 private SparseArray<DashboardCell> cells = new SparseArray<>();
79 private SparseArray<AppWidgetHostView> widgets = new SparseArray<>();
81 private final int APPWIDGET_HOST_ID = 123;
86 private int previouslySelectedCell = -1;
88 private View.OnClickListener ocl = view -> toggleDashboard();
90 private View.OnClickListener cellOcl = view -> cellClick(view, true);
92 private View.OnHoverListener cellOhl = (v, event) -> {
95 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
96 v.setPointerIcon(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_DEFAULT));
101 private View.OnLongClickListener olcl = v -> {
106 private View.OnGenericMotionListener ogml = (view, motionEvent) -> {
107 if(motionEvent.getAction() == MotionEvent.ACTION_BUTTON_PRESS
108 && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY)
114 private DashboardCell.OnInterceptedLongPressListener listener = this::cellLongClick;
116 private BroadcastReceiver toggleReceiver = new BroadcastReceiver() {
118 public void onReceive(Context context, Intent intent) {
123 private BroadcastReceiver addWidgetReceiver = new BroadcastReceiver() {
125 public void onReceive(Context context, Intent intent) {
131 private BroadcastReceiver removeWidgetReceiver = new BroadcastReceiver() {
133 public void onReceive(Context context, Intent intent) {
136 if(intent.hasExtra(EXTRA_CELL_ID)) {
137 int cellId = intent.getExtras().getInt(EXTRA_CELL_ID, -1);
139 removeWidget(cellId, false);
144 private BroadcastReceiver hideReceiver = new BroadcastReceiver() {
146 public void onReceive(Context context, Intent intent) {
151 public DashboardController(Context context) {
155 @TargetApi(Build.VERSION_CODES.M)
157 public void onCreateHost(UIHost host) {
158 if(U.getBooleanPrefWithDefault(context, PREF_DASHBOARD))
159 init(context, host, () -> drawDashboard(host));
164 @SuppressLint("RtlHardcoded")
165 private void drawDashboard(UIHost host) {
166 final ViewParams params = new ViewParams(
167 WindowManager.LayoutParams.MATCH_PARENT,
168 WindowManager.LayoutParams.MATCH_PARENT,
170 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
171 getBottomMargin(context, host)
175 layout = new LinearLayout(context);
176 layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
177 layout.setVisibility(View.GONE);
180 SharedPreferences pref = U.getSharedPreferences(context);
181 int width = U.getIntPrefWithDefault(context, PREF_DASHBOARD_WIDTH);
182 int height = U.getIntPrefWithDefault(context, PREF_DASHBOARD_HEIGHT);
184 int orientation = U.getDisplayOrientation(context);
185 boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
186 boolean isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE;
198 maxSize = columns * rows;
200 int backgroundTint = U.getBackgroundTint(context);
201 int accentColor = U.getAccentColor(context);
202 int accentColorAlt = accentColor;
203 accentColorAlt = ColorUtils.setAlphaComponent(accentColorAlt, Color.alpha(accentColorAlt) / 3);
207 for(int i = 0; i < columns; i++) {
208 LinearLayout layout2 = new LinearLayout(context);
209 layout2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1));
210 layout2.setOrientation(LinearLayout.VERTICAL);
212 for(int j = 0; j < rows; j++) {
213 DashboardCell cellLayout = (DashboardCell) View.inflate(context, R.layout.tb_dashboard, null);
214 cellLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1));
215 cellLayout.setBackgroundColor(backgroundTint);
216 cellLayout.setOnClickListener(cellOcl);
217 cellLayout.setOnHoverListener(cellOhl);
218 cellLayout.setFocusable(false);
220 TextView empty = cellLayout.findViewById(R.id.empty);
221 empty.setBackgroundColor(accentColorAlt);
222 empty.setTextColor(accentColor);
224 Bundle bundle = new Bundle();
225 bundle.putInt(EXTRA_CELL_ID, cellCount);
227 cellLayout.setTag(bundle);
228 cells.put(cellCount, cellLayout);
231 layout2.addView(cellLayout);
234 layout.addView(layout2);
237 appWidgetManager = AppWidgetManager.getInstance(context);
238 mAppWidgetHost = new AppWidgetHost(context, APPWIDGET_HOST_ID);
239 mAppWidgetHost.startListening();
241 for(int i = 0; i < maxSize; i++) {
242 int appWidgetId = pref.getInt(PREF_DASHBOARD_WIDGET_PREFIX + i, -1);
243 if (appWidgetId != -1) {
244 addWidget(appWidgetId, i, false);
245 } else if (pref.getBoolean(generateProviderPlaceholderPrefKey(i), false)) {
250 mAppWidgetHost.stopListening();
252 U.registerReceiver(context, toggleReceiver, ACTION_TOGGLE_DASHBOARD);
253 U.registerReceiver(context, addWidgetReceiver, ACTION_ADD_WIDGET_COMPLETED);
254 U.registerReceiver(context, removeWidgetReceiver, ACTION_REMOVE_WIDGET_COMPLETED);
255 U.registerReceiver(context, hideReceiver, ACTION_HIDE_DASHBOARD);
257 host.addView(layout, params);
259 new Handler().postDelayed(() ->
263 TaskbarPosition.getTaskbarPosition(context)
270 void updatePaddingSize(Context context, LinearLayout layout, String position) {
271 int paddingSize = context.getResources().getDimensionPixelSize(R.dimen.tb_icon_size);
274 case POSITION_TOP_VERTICAL_LEFT:
275 case POSITION_BOTTOM_VERTICAL_LEFT:
276 layout.setPadding(paddingSize, 0, 0, 0);
278 case POSITION_TOP_LEFT:
279 case POSITION_TOP_RIGHT:
280 layout.setPadding(0, paddingSize, 0, 0);
282 case POSITION_TOP_VERTICAL_RIGHT:
283 case POSITION_BOTTOM_VERTICAL_RIGHT:
284 layout.setPadding(0, 0, paddingSize, 0);
286 case POSITION_BOTTOM_LEFT:
287 case POSITION_BOTTOM_RIGHT:
288 layout.setPadding(0, 0, 0, paddingSize);
293 private void toggleDashboard() {
294 if(layout.getVisibility() == View.GONE)
300 @TargetApi(Build.VERSION_CODES.N)
301 private void showDashboard() {
302 if(layout.getVisibility() == View.GONE) {
303 layout.setOnClickListener(ocl);
306 U.sendBroadcast(context, ACTION_DASHBOARD_APPEARING);
307 U.sendBroadcast(context, ACTION_HIDE_START_MENU);
309 boolean inFreeformMode = FreeformHackHelper.getInstance().isInFreeformWorkspace();
311 Intent intent = U.getThemedIntent(context, DashboardActivity.class);
312 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
313 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
315 if (inFreeformMode) {
316 if (U.hasBrokenSetLaunchBoundsApi()) {
317 intent.putExtra(EXTRA_CONTEXT_MENU_FIX, true);
320 U.startActivityMaximized(context, intent);
322 context.startActivity(intent);
325 for(int i = 0; i < maxSize; i++) {
326 final DashboardCell cellLayout = cells.get(i);
327 final AppWidgetHostView hostView = widgets.get(i);
329 if(hostView != null) {
331 context.getPackageManager().getApplicationInfo(hostView.getAppWidgetInfo().provider.getPackageName(), 0);
332 hostView.post(() -> {
333 ViewGroup.LayoutParams params = hostView.getLayoutParams();
334 params.width = cellLayout.getWidth();
335 params.height = cellLayout.getHeight();
336 hostView.setLayoutParams(params);
337 hostView.updateAppWidgetSize(null, cellLayout.getWidth(), cellLayout.getHeight(), cellLayout.getWidth(), cellLayout.getHeight());
339 } catch (PackageManager.NameNotFoundException e) {
340 removeWidget(i, false);
341 } catch (NullPointerException e) {
342 removeWidget(i, true);
347 showDashboardTutorialToast(context);
352 void showDashboardTutorialToast(Context context) {
353 final SharedPreferences pref = U.getSharedPreferences(context);
354 if(!pref.getBoolean(PREF_DASHBOARD_TUTORIAL_SHOWN, false)) {
355 U.showToastLong(context, R.string.tb_dashboard_tutorial);
356 pref.edit().putBoolean(PREF_DASHBOARD_TUTORIAL_SHOWN, true).apply();
360 private void hideDashboard() {
361 if(layout.getVisibility() == View.VISIBLE) {
362 layout.setOnClickListener(null);
365 for(int i = 0; i < maxSize; i++) {
366 FrameLayout frameLayout = cells.get(i);
367 frameLayout.findViewById(R.id.empty).setVisibility(View.GONE);
370 previouslySelectedCell = -1;
374 private void fadeIn() {
375 mAppWidgetHost.startListening();
377 DashboardHelper.getInstance().setDashboardOpen(true);
379 layout.setVisibility(View.VISIBLE);
382 .setDuration(context.getResources().getInteger(android.R.integer.config_shortAnimTime))
386 private void fadeOut(final boolean sendIntent) {
387 mAppWidgetHost.stopListening();
389 DashboardHelper.getInstance().setDashboardOpen(false);
393 .setDuration(context.getResources().getInteger(android.R.integer.config_shortAnimTime))
394 .setListener(new AnimatorListenerAdapter() {
396 public void onAnimationEnd(Animator animation) {
397 layout.setVisibility(View.GONE);
399 U.sendBroadcast(context, ACTION_DASHBOARD_DISAPPEARING);
405 @TargetApi(Build.VERSION_CODES.M)
407 public void onRecreateHost(UIHost host) {
410 host.removeView(layout);
411 } catch (IllegalArgumentException e) { /* Gracefully fail */ }
413 SharedPreferences pref = U.getSharedPreferences(context);
414 if(U.canDrawOverlays(context))
417 pref.edit().putBoolean(PREF_TASKBAR_ACTIVE, false).apply();
425 public void onDestroyHost(UIHost host) {
428 host.removeView(layout);
429 } catch (IllegalArgumentException e) { /* Gracefully fail */ }
431 U.unregisterReceiver(context, toggleReceiver);
432 U.unregisterReceiver(context, addWidgetReceiver);
433 U.unregisterReceiver(context, removeWidgetReceiver);
434 U.unregisterReceiver(context, hideReceiver);
436 SharedPreferences pref = U.getSharedPreferences(context);
437 if(!LauncherHelper.getInstance().isOnSecondaryHomeScreen(context)
438 || !pref.getBoolean(PREF_DONT_STOP_DASHBOARD, false))
439 U.sendBroadcast(context, ACTION_DASHBOARD_DISAPPEARING);
441 pref.edit().remove(PREF_DONT_STOP_DASHBOARD).apply();
444 private void cellClick(View view, boolean isActualClick) {
445 Bundle bundle = (Bundle) view.getTag();
446 int cellId = bundle.getInt(EXTRA_CELL_ID);
447 int appWidgetId = bundle.getInt(EXTRA_APPWIDGET_ID, -1);
449 int currentlySelectedCell = appWidgetId == -1 ? cellId : -1;
451 SharedPreferences pref = U.getSharedPreferences(context);
452 boolean shouldShowPlaceholder =
453 pref.getBoolean(generateProviderPlaceholderPrefKey(cellId), false);
454 if(isActualClick && ((appWidgetId == -1 && currentlySelectedCell == previouslySelectedCell) || shouldShowPlaceholder)) {
457 FrameLayout frameLayout = cells.get(currentlySelectedCell);
458 frameLayout.findViewById(R.id.empty).setVisibility(View.GONE);
460 Intent intent = new Intent(ACTION_ADD_WIDGET_REQUESTED);
461 intent.putExtra(EXTRA_APPWIDGET_ID, APPWIDGET_HOST_ID);
462 intent.putExtra(EXTRA_CELL_ID, cellId);
463 U.sendBroadcast(context, intent);
465 if(shouldShowPlaceholder) {
466 String providerName =
467 pref.getString(generateProviderPrefKey(cellId), PREF_DEFAULT_NULL);
468 if(!providerName.equals(PREF_DEFAULT_NULL)) {
469 ComponentName componentName = ComponentName.unflattenFromString(providerName);
471 List<AppWidgetProviderInfo> providerInfoList = appWidgetManager.getInstalledProvidersForProfile(Process.myUserHandle());
472 for(AppWidgetProviderInfo info : providerInfoList) {
473 if(info.provider.equals(componentName)) {
474 U.showToast(context, context.getString(R.string.tb_widget_restore_toast, info.loadLabel(context.getPackageManager())), Toast.LENGTH_SHORT);
481 previouslySelectedCell = -1;
483 for(int i = 0; i < maxSize; i++) {
484 FrameLayout frameLayout = cells.get(i);
485 frameLayout.findViewById(R.id.empty).setVisibility(i == currentlySelectedCell && !shouldShowPlaceholder ? View.VISIBLE : View.GONE);
488 previouslySelectedCell = currentlySelectedCell;
493 String generateProviderPrefKey(int cellId) {
494 return PREF_DASHBOARD_WIDGET_PREFIX + cellId + PREF_DASHBOARD_WIDGET_PROVIDER_SUFFIX;
498 String generateProviderPlaceholderPrefKey(int cellId) {
499 return PREF_DASHBOARD_WIDGET_PREFIX + cellId + PREF_DASHBOARD_WIDGET_PLACEHOLDER_SUFFIX;
502 private void cellLongClick(View view) {
505 Bundle bundle = (Bundle) view.getTag();
506 int cellId = bundle.getInt(EXTRA_CELL_ID);
508 Intent intent = new Intent(ACTION_REMOVE_WIDGET_REQUESTED);
509 intent.putExtra(EXTRA_CELL_ID, cellId);
510 U.sendBroadcast(context, intent);
514 void addWidget(Intent intent) {
515 if (intent.hasExtra(EXTRA_APPWIDGET_ID) && intent.hasExtra(EXTRA_CELL_ID)) {
516 int appWidgetId = intent.getExtras().getInt(EXTRA_APPWIDGET_ID, -1);
517 int cellId = intent.getExtras().getInt(EXTRA_CELL_ID, -1);
519 addWidget(appWidgetId, cellId, true);
523 private void addWidget(int appWidgetId, int cellId, boolean shouldSave) {
524 AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId);
526 final DashboardCell cellLayout = cells.get(cellId);
527 final AppWidgetHostView hostView = mAppWidgetHost.createView(context, appWidgetId, appWidgetInfo);
528 hostView.setAppWidget(appWidgetId, appWidgetInfo);
530 Bundle bundle = new Bundle();
531 bundle.putInt(EXTRA_CELL_ID, cellId);
532 hostView.setTag(bundle);
534 cellLayout.findViewById(R.id.empty).setVisibility(View.GONE);
535 cellLayout.findViewById(R.id.placeholder).setVisibility(View.GONE);
536 cellLayout.setOnLongClickListener(olcl);
537 cellLayout.setOnGenericMotionListener(ogml);
538 cellLayout.setOnInterceptedLongPressListener(listener);
540 LinearLayout linearLayout = cellLayout.findViewById(R.id.dashboard);
541 linearLayout.addView(hostView);
543 Bundle bundle2 = (Bundle) cellLayout.getTag();
544 bundle2.putInt(EXTRA_APPWIDGET_ID, appWidgetId);
545 cellLayout.setTag(bundle2);
547 widgets.put(cellId, hostView);
550 saveWidgetInfo(context, appWidgetInfo, cellId, appWidgetId);
553 new Handler().post(() -> {
554 ViewGroup.LayoutParams params = hostView.getLayoutParams();
555 params.width = cellLayout.getWidth();
556 params.height = cellLayout.getHeight();
557 hostView.setLayoutParams(params);
558 hostView.updateAppWidgetSize(null, cellLayout.getWidth(), cellLayout.getHeight(), cellLayout.getWidth(), cellLayout.getHeight());
563 void saveWidgetInfo(Context context,
564 AppWidgetProviderInfo appWidgetInfo,
567 SharedPreferences pref = U.getSharedPreferences(context);
568 SharedPreferences.Editor editor = pref.edit();
569 editor.putInt(PREF_DASHBOARD_WIDGET_PREFIX + cellId, appWidgetId);
571 generateProviderPrefKey(cellId),
572 appWidgetInfo.provider.flattenToString()
574 editor.remove(generateProviderPlaceholderPrefKey(cellId));
578 private void removeWidget(int cellId, boolean tempRemove) {
579 widgets.remove(cellId);
581 DashboardCell cellLayout = cells.get(cellId);
582 Bundle bundle = (Bundle) cellLayout.getTag();
584 mAppWidgetHost.deleteAppWidgetId(bundle.getInt(EXTRA_APPWIDGET_ID));
585 bundle.remove(EXTRA_APPWIDGET_ID);
587 LinearLayout linearLayout = cellLayout.findViewById(R.id.dashboard);
588 linearLayout.removeAllViews();
590 cellLayout.setTag(bundle);
591 cellLayout.setOnClickListener(cellOcl);
592 cellLayout.setOnHoverListener(cellOhl);
593 cellLayout.setOnLongClickListener(null);
594 cellLayout.setOnGenericMotionListener(null);
595 cellLayout.setOnInterceptedLongPressListener(null);
597 SharedPreferences pref = U.getSharedPreferences(context);
598 SharedPreferences.Editor editor = pref.edit();
599 editor.remove(PREF_DASHBOARD_WIDGET_PREFIX + cellId);
602 editor.putBoolean(generateProviderPlaceholderPrefKey(cellId), true);
603 addPlaceholder(cellId);
605 editor.remove(generateProviderPrefKey(cellId));
610 private void addPlaceholder(int cellId) {
611 FrameLayout placeholder = cells.get(cellId).findViewById(R.id.placeholder);
612 SharedPreferences pref = U.getSharedPreferences(context);
613 String providerName = pref.getString(generateProviderPrefKey(cellId), PREF_DEFAULT_NULL);
615 if(!providerName.equals(PREF_DEFAULT_NULL)) {
616 ImageView imageView = placeholder.findViewById(R.id.placeholder_image);
617 ComponentName componentName = ComponentName.unflattenFromString(providerName);
619 List<AppWidgetProviderInfo> providerInfoList = appWidgetManager.getInstalledProvidersForProfile(Process.myUserHandle());
620 for(AppWidgetProviderInfo info : providerInfoList) {
621 if(info.provider.equals(componentName)) {
622 Drawable drawable = info.loadPreviewImage(context, -1);
623 if(drawable == null) drawable = info.loadIcon(context, -1);
625 ColorMatrix matrix = new ColorMatrix();
626 matrix.setSaturation(0);
628 imageView.setImageDrawable(drawable);
629 imageView.setColorFilter(new ColorMatrixColorFilter(matrix));
635 placeholder.setVisibility(View.VISIBLE);