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.service;
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.annotation.SuppressLint;
24 import android.annotation.TargetApi;
25 import android.app.Service;
26 import android.appwidget.AppWidgetHost;
27 import android.appwidget.AppWidgetHostView;
28 import android.appwidget.AppWidgetManager;
29 import android.appwidget.AppWidgetProviderInfo;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.SharedPreferences;
36 import android.content.pm.PackageManager;
37 import android.content.res.Configuration;
38 import android.graphics.Color;
39 import android.graphics.ColorMatrix;
40 import android.graphics.ColorMatrixColorFilter;
41 import android.graphics.PixelFormat;
42 import android.graphics.drawable.Drawable;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.IBinder;
47 import android.os.Process;
48 import android.support.v4.content.LocalBroadcastManager;
49 import android.support.v4.graphics.ColorUtils;
50 import android.util.SparseArray;
51 import android.view.MotionEvent;
52 import android.view.PointerIcon;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.view.WindowManager;
56 import android.widget.FrameLayout;
57 import android.widget.ImageView;
58 import android.widget.LinearLayout;
59 import android.widget.TextView;
60 import android.widget.Toast;
62 import com.farmerbb.taskbar.R;
63 import com.farmerbb.taskbar.activity.DashboardActivity;
64 import com.farmerbb.taskbar.activity.dark.DashboardActivityDark;
65 import com.farmerbb.taskbar.util.DashboardHelper;
66 import com.farmerbb.taskbar.widget.DashboardCell;
67 import com.farmerbb.taskbar.util.FreeformHackHelper;
68 import com.farmerbb.taskbar.util.LauncherHelper;
69 import com.farmerbb.taskbar.util.U;
71 import java.util.List;
73 public class DashboardService extends Service {
75 private AppWidgetManager mAppWidgetManager;
76 private AppWidgetHost mAppWidgetHost;
78 private WindowManager windowManager;
79 private LinearLayout layout;
81 private SparseArray<DashboardCell> cells = new SparseArray<>();
82 private SparseArray<AppWidgetHostView> widgets = new SparseArray<>();
84 private final int APPWIDGET_HOST_ID = 123;
89 private int previouslySelectedCell = -1;
91 private View.OnClickListener ocl = view -> toggleDashboard();
93 private View.OnClickListener cellOcl = view -> cellClick(view, true);
95 private View.OnHoverListener cellOhl = (v, event) -> {
98 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
99 v.setPointerIcon(PointerIcon.getSystemIcon(this, PointerIcon.TYPE_DEFAULT));
104 private View.OnLongClickListener olcl = v -> {
109 private View.OnGenericMotionListener ogml = (view, motionEvent) -> {
110 if(motionEvent.getAction() == MotionEvent.ACTION_BUTTON_PRESS
111 && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY)
117 private DashboardCell.OnInterceptedLongPressListener listener = this::cellLongClick;
119 private BroadcastReceiver toggleReceiver = new BroadcastReceiver() {
121 public void onReceive(Context context, Intent intent) {
126 private BroadcastReceiver addWidgetReceiver = new BroadcastReceiver() {
128 public void onReceive(Context context, Intent intent) {
131 if(intent.hasExtra("appWidgetId") && intent.hasExtra("cellId")) {
132 int appWidgetId = intent.getExtras().getInt("appWidgetId", -1);
133 int cellId = intent.getExtras().getInt("cellId", -1);
135 addWidget(appWidgetId, cellId, true);
140 private BroadcastReceiver removeWidgetReceiver = new BroadcastReceiver() {
142 public void onReceive(Context context, Intent intent) {
145 if(intent.hasExtra("cellId")) {
146 int cellId = intent.getExtras().getInt("cellId", -1);
148 removeWidget(cellId, false);
153 private BroadcastReceiver hideReceiver = new BroadcastReceiver() {
155 public void onReceive(Context context, Intent intent) {
161 public IBinder onBind(Intent intent) {
166 public int onStartCommand(Intent intent, int flags, int startId) {
170 @TargetApi(Build.VERSION_CODES.M)
172 public void onCreate() {
175 SharedPreferences pref = U.getSharedPreferences(this);
176 if(pref.getBoolean("dashboard", false)) {
177 if(pref.getBoolean("taskbar_active", false) || LauncherHelper.getInstance().isOnHomeScreen()) {
178 if(U.canDrawOverlays(this))
181 pref.edit().putBoolean("taskbar_active", false).apply();
189 @SuppressLint("RtlHardcoded")
190 private void drawDashboard() {
191 windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
193 final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
194 WindowManager.LayoutParams.MATCH_PARENT,
195 WindowManager.LayoutParams.MATCH_PARENT,
197 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
198 PixelFormat.TRANSLUCENT);
201 layout = new LinearLayout(this);
202 layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
203 layout.setVisibility(View.GONE);
206 SharedPreferences pref = U.getSharedPreferences(this);
207 int width = pref.getInt("dashboard_width", getApplicationContext().getResources().getInteger(R.integer.dashboard_width));
208 int height = pref.getInt("dashboard_height", getApplicationContext().getResources().getInteger(R.integer.dashboard_height));
210 boolean isPortrait = getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
211 boolean isLandscape = getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
223 maxSize = columns * rows;
225 int backgroundTint = U.getBackgroundTint(this);
226 int accentColor = U.getAccentColor(this);
227 int accentColorAlt = accentColor;
228 accentColorAlt = ColorUtils.setAlphaComponent(accentColorAlt, Color.alpha(accentColorAlt) / 2);
232 for(int i = 0; i < columns; i++) {
233 LinearLayout layout2 = new LinearLayout(this);
234 layout2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1));
235 layout2.setOrientation(LinearLayout.VERTICAL);
237 for(int j = 0; j < rows; j++) {
238 DashboardCell cellLayout = (DashboardCell) View.inflate(this, R.layout.dashboard, null);
239 cellLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1));
240 cellLayout.setBackgroundColor(backgroundTint);
241 cellLayout.setOnClickListener(cellOcl);
242 cellLayout.setOnHoverListener(cellOhl);
244 TextView empty = cellLayout.findViewById(R.id.empty);
245 empty.setBackgroundColor(accentColorAlt);
246 empty.setTextColor(accentColor);
248 Bundle bundle = new Bundle();
249 bundle.putInt("cellId", cellCount);
251 cellLayout.setTag(bundle);
252 cells.put(cellCount, cellLayout);
255 layout2.addView(cellLayout);
258 layout.addView(layout2);
261 mAppWidgetManager = AppWidgetManager.getInstance(this);
262 mAppWidgetHost = new AppWidgetHost(this, APPWIDGET_HOST_ID);
263 mAppWidgetHost.startListening();
265 for(int i = 0; i < maxSize; i++) {
266 int appWidgetId = pref.getInt("dashboard_widget_" + Integer.toString(i), -1);
267 if(appWidgetId != -1)
268 addWidget(appWidgetId, i, false);
269 else if(pref.getBoolean("dashboard_widget_" + Integer.toString(i) + "_placeholder", false))
273 mAppWidgetHost.stopListening();
275 LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
277 lbm.unregisterReceiver(toggleReceiver);
278 lbm.unregisterReceiver(addWidgetReceiver);
279 lbm.unregisterReceiver(removeWidgetReceiver);
280 lbm.unregisterReceiver(hideReceiver);
282 lbm.registerReceiver(toggleReceiver, new IntentFilter("com.farmerbb.taskbar.TOGGLE_DASHBOARD"));
283 lbm.registerReceiver(addWidgetReceiver, new IntentFilter("com.farmerbb.taskbar.ADD_WIDGET_COMPLETED"));
284 lbm.registerReceiver(removeWidgetReceiver, new IntentFilter("com.farmerbb.taskbar.REMOVE_WIDGET_COMPLETED"));
285 lbm.registerReceiver(hideReceiver, new IntentFilter("com.farmerbb.taskbar.HIDE_DASHBOARD"));
287 windowManager.addView(layout, params);
289 new Handler().postDelayed(() -> {
290 int paddingSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
292 switch(U.getTaskbarPosition(this)) {
293 case "top_vertical_left":
294 case "bottom_vertical_left":
295 layout.setPadding(paddingSize, 0, 0, 0);
299 layout.setPadding(0, paddingSize, 0, 0);
301 case "top_vertical_right":
302 case "bottom_vertical_right":
303 layout.setPadding(0, 0, paddingSize, 0);
307 layout.setPadding(0, 0, 0, paddingSize);
313 private void toggleDashboard() {
314 if(layout.getVisibility() == View.GONE)
320 @SuppressWarnings("deprecation")
321 @TargetApi(Build.VERSION_CODES.N)
322 private void showDashboard() {
323 if(layout.getVisibility() == View.GONE) {
324 layout.setOnClickListener(ocl);
327 LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.DASHBOARD_APPEARING"));
328 LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
330 boolean inFreeformMode = FreeformHackHelper.getInstance().isInFreeformWorkspace();
332 final SharedPreferences pref = U.getSharedPreferences(this);
333 Intent intent = null;
335 switch(pref.getString("theme", "light")) {
337 intent = new Intent(this, DashboardActivity.class);
340 intent = new Intent(this, DashboardActivityDark.class);
345 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
346 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
350 if(intent != null && U.hasBrokenSetLaunchBoundsApi())
351 intent.putExtra("context_menu_fix", true);
353 U.startActivityMaximized(this, intent);
355 startActivity(intent);
357 for(int i = 0; i < maxSize; i++) {
358 final DashboardCell cellLayout = cells.get(i);
359 final AppWidgetHostView hostView = widgets.get(i);
361 if(hostView != null) {
363 getPackageManager().getApplicationInfo(hostView.getAppWidgetInfo().provider.getPackageName(), 0);
364 hostView.post(() -> {
365 ViewGroup.LayoutParams params = hostView.getLayoutParams();
366 params.width = cellLayout.getWidth();
367 params.height = cellLayout.getHeight();
368 hostView.setLayoutParams(params);
369 hostView.updateAppWidgetSize(null, cellLayout.getWidth(), cellLayout.getHeight(), cellLayout.getWidth(), cellLayout.getHeight());
371 } catch (PackageManager.NameNotFoundException e) {
372 removeWidget(i, false);
373 } catch (NullPointerException e) {
374 removeWidget(i, true);
379 if(!pref.getBoolean("dashboard_tutorial_shown", false)) {
380 U.showToastLong(this, R.string.dashboard_tutorial);
381 pref.edit().putBoolean("dashboard_tutorial_shown", true).apply();
386 private void hideDashboard() {
387 if(layout.getVisibility() == View.VISIBLE) {
388 layout.setOnClickListener(null);
391 for(int i = 0; i < maxSize; i++) {
392 FrameLayout frameLayout = cells.get(i);
393 frameLayout.findViewById(R.id.empty).setVisibility(View.GONE);
396 previouslySelectedCell = -1;
400 private void fadeIn() {
401 mAppWidgetHost.startListening();
403 DashboardHelper.getInstance().setDashboardOpen(true);
405 layout.setVisibility(View.VISIBLE);
408 .setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime))
412 private void fadeOut(final boolean sendIntent) {
413 mAppWidgetHost.stopListening();
415 DashboardHelper.getInstance().setDashboardOpen(false);
419 .setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime))
420 .setListener(new AnimatorListenerAdapter() {
422 public void onAnimationEnd(Animator animation) {
423 layout.setVisibility(View.GONE);
424 if(sendIntent) LocalBroadcastManager.getInstance(DashboardService.this).sendBroadcast(new Intent("com.farmerbb.taskbar.DASHBOARD_DISAPPEARING"));
429 @TargetApi(Build.VERSION_CODES.M)
431 public void onConfigurationChanged(Configuration newConfig) {
432 super.onConfigurationChanged(newConfig);
436 windowManager.removeView(layout);
437 } catch (IllegalArgumentException e) { /* Gracefully fail */ }
439 SharedPreferences pref = U.getSharedPreferences(this);
440 if(U.canDrawOverlays(this))
443 pref.edit().putBoolean("taskbar_active", false).apply();
451 public void onDestroy() {
455 windowManager.removeView(layout);
456 } catch (IllegalArgumentException e) { /* Gracefully fail */ }
458 LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
460 lbm.unregisterReceiver(toggleReceiver);
461 lbm.unregisterReceiver(addWidgetReceiver);
462 lbm.unregisterReceiver(removeWidgetReceiver);
463 lbm.unregisterReceiver(hideReceiver);
465 lbm.sendBroadcast(new Intent("com.farmerbb.taskbar.DASHBOARD_DISAPPEARING"));
468 private void cellClick(View view, boolean isActualClick) {
469 Bundle bundle = (Bundle) view.getTag();
470 int cellId = bundle.getInt("cellId");
471 int appWidgetId = bundle.getInt("appWidgetId", -1);
473 int currentlySelectedCell = appWidgetId == -1 ? cellId : -1;
475 SharedPreferences pref = U.getSharedPreferences(this);
476 boolean shouldShowPlaceholder = pref.getBoolean("dashboard_widget_" + Integer.toString(cellId) + "_placeholder", false);
477 if(isActualClick && ((appWidgetId == -1 && currentlySelectedCell == previouslySelectedCell) || shouldShowPlaceholder)) {
480 FrameLayout frameLayout = cells.get(currentlySelectedCell);
481 frameLayout.findViewById(R.id.empty).setVisibility(View.GONE);
483 Intent intent = new Intent("com.farmerbb.taskbar.ADD_WIDGET_REQUESTED");
484 intent.putExtra("appWidgetId", APPWIDGET_HOST_ID);
485 intent.putExtra("cellId", cellId);
486 LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
488 if(shouldShowPlaceholder) {
489 String providerName = pref.getString("dashboard_widget_" + Integer.toString(cellId) + "_provider", "null");
490 if(!providerName.equals("null")) {
491 ComponentName componentName = ComponentName.unflattenFromString(providerName);
493 List<AppWidgetProviderInfo> providerInfoList = mAppWidgetManager.getInstalledProvidersForProfile(Process.myUserHandle());
494 for(AppWidgetProviderInfo info : providerInfoList) {
495 if(info.provider.equals(componentName)) {
496 U.showToast(this, getString(R.string.widget_restore_toast, info.loadLabel(getPackageManager())), Toast.LENGTH_SHORT);
503 previouslySelectedCell = -1;
505 for(int i = 0; i < maxSize; i++) {
506 FrameLayout frameLayout = cells.get(i);
507 frameLayout.findViewById(R.id.empty).setVisibility(i == currentlySelectedCell && !shouldShowPlaceholder ? View.VISIBLE : View.GONE);
510 previouslySelectedCell = currentlySelectedCell;
514 private void cellLongClick(View view) {
517 Bundle bundle = (Bundle) view.getTag();
518 int cellId = bundle.getInt("cellId");
520 Intent intent = new Intent("com.farmerbb.taskbar.REMOVE_WIDGET_REQUESTED");
521 intent.putExtra("cellId", cellId);
522 LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
525 private void addWidget(int appWidgetId, int cellId, boolean shouldSave) {
526 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
528 final DashboardCell cellLayout = cells.get(cellId);
529 final AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
530 hostView.setAppWidget(appWidgetId, appWidgetInfo);
532 Bundle bundle = new Bundle();
533 bundle.putInt("cellId", cellId);
534 hostView.setTag(bundle);
536 cellLayout.findViewById(R.id.empty).setVisibility(View.GONE);
537 cellLayout.findViewById(R.id.placeholder).setVisibility(View.GONE);
538 cellLayout.setOnLongClickListener(olcl);
539 cellLayout.setOnGenericMotionListener(ogml);
540 cellLayout.setOnInterceptedLongPressListener(listener);
542 LinearLayout linearLayout = cellLayout.findViewById(R.id.dashboard);
543 linearLayout.addView(hostView);
545 Bundle bundle2 = (Bundle) cellLayout.getTag();
546 bundle2.putInt("appWidgetId", appWidgetId);
547 cellLayout.setTag(bundle2);
549 widgets.put(cellId, hostView);
552 SharedPreferences pref = U.getSharedPreferences(this);
553 SharedPreferences.Editor editor = pref.edit();
554 editor.putInt("dashboard_widget_" + Integer.toString(cellId), appWidgetId);
555 editor.putString("dashboard_widget_" + Integer.toString(cellId) + "_provider", appWidgetInfo.provider.flattenToString());
556 editor.remove("dashboard_widget_" + Integer.toString(cellId) + "_placeholder");
560 new Handler().post(() -> {
561 ViewGroup.LayoutParams params = hostView.getLayoutParams();
562 params.width = cellLayout.getWidth();
563 params.height = cellLayout.getHeight();
564 hostView.setLayoutParams(params);
565 hostView.updateAppWidgetSize(null, cellLayout.getWidth(), cellLayout.getHeight(), cellLayout.getWidth(), cellLayout.getHeight());
569 private void removeWidget(int cellId, boolean tempRemove) {
570 widgets.remove(cellId);
572 DashboardCell cellLayout = cells.get(cellId);
573 Bundle bundle = (Bundle) cellLayout.getTag();
575 mAppWidgetHost.deleteAppWidgetId(bundle.getInt("appWidgetId"));
576 bundle.remove("appWidgetId");
578 LinearLayout linearLayout = cellLayout.findViewById(R.id.dashboard);
579 linearLayout.removeAllViews();
581 cellLayout.setTag(bundle);
582 cellLayout.setOnClickListener(cellOcl);
583 cellLayout.setOnHoverListener(cellOhl);
584 cellLayout.setOnLongClickListener(null);
585 cellLayout.setOnGenericMotionListener(null);
586 cellLayout.setOnInterceptedLongPressListener(null);
588 SharedPreferences pref = U.getSharedPreferences(this);
589 SharedPreferences.Editor editor = pref.edit();
590 editor.remove("dashboard_widget_" + Integer.toString(cellId));
593 editor.putBoolean("dashboard_widget_" + Integer.toString(cellId) + "_placeholder", true);
594 addPlaceholder(cellId);
596 editor.remove("dashboard_widget_" + Integer.toString(cellId) + "_provider");
601 private void addPlaceholder(int cellId) {
602 FrameLayout placeholder = cells.get(cellId).findViewById(R.id.placeholder);
603 SharedPreferences pref = U.getSharedPreferences(this);
604 String providerName = pref.getString("dashboard_widget_" + Integer.toString(cellId) + "_provider", "null");
606 if(!providerName.equals("null")) {
607 ImageView imageView = placeholder.findViewById(R.id.placeholder_image);
608 ComponentName componentName = ComponentName.unflattenFromString(providerName);
610 List<AppWidgetProviderInfo> providerInfoList = mAppWidgetManager.getInstalledProvidersForProfile(Process.myUserHandle());
611 for(AppWidgetProviderInfo info : providerInfoList) {
612 if(info.provider.equals(componentName)) {
613 Drawable drawable = info.loadPreviewImage(this, -1);
614 if(drawable == null) drawable = info.loadIcon(this, -1);
616 ColorMatrix matrix = new ColorMatrix();
617 matrix.setSaturation(0);
619 imageView.setImageDrawable(drawable);
620 imageView.setColorFilter(new ColorMatrixColorFilter(matrix));
626 placeholder.setVisibility(View.VISIBLE);