OSDN Git Service

Various fixes for desktop icons
[android-x86/packages-apps-Taskbar.git] / app / src / main / java / com / farmerbb / taskbar / activity / HomeActivityDelegate.java
1 /* Copyright 2016 Braden Farmer
2  *
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
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  */
15
16 package com.farmerbb.taskbar.activity;
17
18 import android.annotation.SuppressLint;
19 import android.annotation.TargetApi;
20 import android.app.AlertDialog;
21 import android.app.WallpaperManager;
22 import android.content.ActivityNotFoundException;
23 import android.content.BroadcastReceiver;
24 import android.content.ClipData;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.SharedPreferences;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.support.design.widget.FloatingActionButton;
33 import android.support.v4.content.ContextCompat;
34 import android.support.v4.content.LocalBroadcastManager;
35 import android.support.v7.app.AppCompatActivity;
36 import android.util.SparseArray;
37 import android.view.DragEvent;
38 import android.view.Gravity;
39 import android.view.LayoutInflater;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.ViewTreeObserver;
44 import android.view.WindowManager;
45 import android.widget.FrameLayout;
46 import android.widget.GridLayout;
47 import android.widget.ImageView;
48 import android.widget.TextView;
49
50 import com.farmerbb.taskbar.R;
51 import com.farmerbb.taskbar.service.DashboardService;
52 import com.farmerbb.taskbar.service.NotificationService;
53 import com.farmerbb.taskbar.service.StartMenuService;
54 import com.farmerbb.taskbar.service.TaskbarService;
55 import com.farmerbb.taskbar.ui.DashboardController;
56 import com.farmerbb.taskbar.ui.UIHost;
57 import com.farmerbb.taskbar.ui.ViewParams;
58 import com.farmerbb.taskbar.ui.StartMenuController;
59 import com.farmerbb.taskbar.ui.TaskbarController;
60 import com.farmerbb.taskbar.util.AppEntry;
61 import com.farmerbb.taskbar.util.CompatUtils;
62 import com.farmerbb.taskbar.util.DesktopIconInfo;
63 import com.farmerbb.taskbar.util.DisplayInfo;
64 import com.farmerbb.taskbar.util.FeatureFlags;
65 import com.farmerbb.taskbar.util.FreeformHackHelper;
66 import com.farmerbb.taskbar.util.IconCache;
67 import com.farmerbb.taskbar.util.LauncherHelper;
68 import com.farmerbb.taskbar.util.MenuHelper;
69 import com.farmerbb.taskbar.util.U;
70
71 import org.json.JSONArray;
72 import org.json.JSONException;
73
74 import java.text.Collator;
75 import java.util.ArrayList;
76 import java.util.Collections;
77 import java.util.List;
78
79 public class HomeActivityDelegate extends AppCompatActivity implements UIHost {
80     private TaskbarController taskbarController;
81     private StartMenuController startMenuController;
82     private DashboardController dashboardController;
83
84     private FrameLayout layout;
85     private GridLayout desktopIcons;
86     private FloatingActionButton fab;
87
88     private boolean forceTaskbarStart = false;
89     private AlertDialog dialog;
90
91     private boolean shouldDelayFreeformHack;
92     private int hits;
93
94     private boolean iconArrangeMode = false;
95     private int startDragIndex;
96     private int endDragIndex;
97
98     private BroadcastReceiver killReceiver = new BroadcastReceiver() {
99         @Override
100         public void onReceive(Context context, Intent intent) {
101             killHomeActivity();
102         }
103     };
104
105     private BroadcastReceiver forceTaskbarStartReceiver = new BroadcastReceiver() {
106         @Override
107         public void onReceive(Context context, Intent intent) {
108             forceTaskbarStart = true;
109         }
110     };
111
112     private BroadcastReceiver restartReceiver = new BroadcastReceiver() {
113         @Override
114         public void onReceive(Context context, Intent intent) {
115             if(taskbarController != null) taskbarController.onRecreateHost(HomeActivityDelegate.this);
116             if(startMenuController != null) startMenuController.onRecreateHost(HomeActivityDelegate.this);
117             if(dashboardController != null) dashboardController.onRecreateHost(HomeActivityDelegate.this);
118         }
119     };
120
121     private BroadcastReceiver freeformToggleReceiver = new BroadcastReceiver() {
122         @Override
123         public void onReceive(Context context, Intent intent) {
124             updateWindowFlags();
125         }
126     };
127
128     private BroadcastReceiver refreshDesktopIconsReceiver = new BroadcastReceiver() {
129         @Override
130         public void onReceive(Context context, Intent intent) {
131             refreshDesktopIcons();
132         }
133     };
134
135     private BroadcastReceiver iconArrangeModeReceiver = new BroadcastReceiver() {
136         @Override
137         public void onReceive(Context context, Intent intent) {
138             enterIconArrangeMode();
139         }
140     };
141
142     private BroadcastReceiver sortDesktopIconsReceiver = new BroadcastReceiver() {
143         @Override
144         public void onReceive(Context context, Intent intent) {
145             sortDesktopIcons();
146         }
147     };
148
149     private BroadcastReceiver updateMarginsReceiver = new BroadcastReceiver() {
150         @Override
151         public void onReceive(Context context, Intent intent) {
152             updateMargins();
153         }
154     };
155
156     @SuppressLint("RestrictedApi")
157     @Override
158     protected void onCreate(Bundle savedInstanceState) {
159         super.onCreate(savedInstanceState);
160
161         shouldDelayFreeformHack = true;
162         hits = 0;
163
164         WindowManager.LayoutParams params = getWindow().getAttributes();
165         if(CompatUtils.applyDisplayCutoutModeTo(params))
166             getWindow().setAttributes(params);
167
168         SharedPreferences pref = U.getSharedPreferences(this);
169
170         layout = new FrameLayout(this) {
171             @Override
172             protected void onAttachedToWindow() {
173                 super.onAttachedToWindow();
174
175                 WallpaperManager wallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
176                 wallpaperManager.setWallpaperOffsets(getWindowToken(), 0.5f, 0.5f);
177
178                 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
179                     DisplayInfo display = U.getDisplayInfo(HomeActivityDelegate.this);
180                     wallpaperManager.suggestDesiredDimensions(display.width, display.height);
181                 }
182
183                 boolean shouldStartFreeformHack = shouldDelayFreeformHack && hits > 0;
184                 shouldDelayFreeformHack = false;
185
186                 if(shouldStartFreeformHack)
187                     startFreeformHack();
188             }
189         };
190
191         if(FeatureFlags.desktopIcons(HomeActivityDelegate.this)) {
192             layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
193                 @Override
194                 public void onGlobalLayout() {
195                     layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
196
197                     if(savedInstanceState != null)
198                         iconArrangeMode = savedInstanceState.getBoolean("icon_arrange_mode");
199
200                     initDesktopIcons();
201                 }
202             });
203         }
204
205         if(!FeatureFlags.desktopIcons(this)) {
206             layout.setOnClickListener(view1 -> LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU")));
207
208             layout.setOnLongClickListener(view2 -> {
209                 if(!pref.getBoolean("freeform_hack", false))
210                     setWallpaper();
211
212                 return false;
213             });
214
215             layout.setOnGenericMotionListener((view3, motionEvent) -> {
216                 if(motionEvent.getAction() == MotionEvent.ACTION_BUTTON_PRESS
217                         && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY
218                         && !pref.getBoolean("freeform_hack", false))
219                     setWallpaper();
220
221                 return false;
222             });
223         }
224
225         layout.setFitsSystemWindows(true);
226
227         if((this instanceof HomeActivity || U.isLauncherPermanentlyEnabled(this))
228                 && !U.isChromeOs(this)) {
229             setContentView(layout);
230             pref.edit().putBoolean("launcher", true).apply();
231         } else
232             killHomeActivity();
233
234         updateWindowFlags();
235
236         LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
237         lbm.registerReceiver(killReceiver, new IntentFilter("com.farmerbb.taskbar.KILL_HOME_ACTIVITY"));
238         lbm.registerReceiver(forceTaskbarStartReceiver, new IntentFilter("com.farmerbb.taskbar.FORCE_TASKBAR_RESTART"));
239
240         IntentFilter intentFilter = new IntentFilter();
241         intentFilter.addAction("com.farmerbb.taskbar.UPDATE_FREEFORM_CHECKBOX");
242         intentFilter.addAction("com.farmerbb.taskbar.TOUCH_ABSORBER_STATE_CHANGED");
243
244         lbm.registerReceiver(freeformToggleReceiver, intentFilter);
245
246         if(FeatureFlags.homeActivityUIHost())
247             lbm.registerReceiver(restartReceiver, new IntentFilter("com.farmerbb.taskbar.RESTART"));
248
249         if(FeatureFlags.desktopIcons(this)) {
250             lbm.registerReceiver(refreshDesktopIconsReceiver, new IntentFilter("com.farmerbb.taskbar.REFRESH_DESKTOP_ICONS"));
251             lbm.registerReceiver(iconArrangeModeReceiver, new IntentFilter("com.farmerbb.taskbar.ENTER_ICON_ARRANGE_MODE"));
252             lbm.registerReceiver(sortDesktopIconsReceiver, new IntentFilter("com.farmerbb.taskbar.SORT_DESKTOP_ICONS"));
253             lbm.registerReceiver(updateMarginsReceiver, new IntentFilter("com.farmerbb.taskbar.UPDATE_HOME_SCREEN_MARGINS"));
254         }
255
256         U.initPrefs(this);
257     }
258
259     private void setWallpaper() {
260         if(U.shouldCollapse(this, true))
261             LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.TEMP_HIDE_TASKBAR"));
262         else
263             LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
264
265         try {
266             startActivity(Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER), getString(R.string.set_wallpaper)));
267         } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
268     }
269
270     @TargetApi(Build.VERSION_CODES.N)
271     @Override
272     protected void onResume() {
273         super.onResume();
274
275         if(U.canBootToFreeform(this)) {
276             if(U.launcherIsDefault(this))
277                 startFreeformHack();
278             else {
279                 U.showToastLong(this, R.string.set_as_default_home);
280
281                 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
282                 homeIntent.addCategory(Intent.CATEGORY_HOME);
283                 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
284
285                 try {
286                     startActivity(homeIntent);
287                     finish();
288                 } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
289             }
290         } else {
291             LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.TEMP_SHOW_TASKBAR"));
292         }
293     }
294
295     @Override
296     protected void onStart() {
297         super.onStart();
298
299         LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
300
301         if(U.canDrawOverlays(this, true)) {
302             if(!U.canBootToFreeform(this)) {
303                 final LauncherHelper helper = LauncherHelper.getInstance();
304                 helper.setOnHomeScreen(true);
305
306                 if(forceTaskbarStart) {
307                     forceTaskbarStart = false;
308                     new Handler().postDelayed(() -> {
309                         helper.setOnHomeScreen(true);
310                         startTaskbar();
311                     }, 250);
312                 } else
313                     startTaskbar();
314             } else if(U.launcherIsDefault(this))
315                 startFreeformHack();
316         } else
317             dialog = U.showPermissionDialog(U.wrapContext(this),
318                     () -> dialog = U.showErrorDialog(U.wrapContext(this), "SYSTEM_ALERT_WINDOW"),
319                     null);
320     }
321
322     private void startTaskbar() {
323         SharedPreferences pref = U.getSharedPreferences(this);
324         if(pref.getBoolean("first_run", true)) {
325             SharedPreferences.Editor editor = pref.edit();
326             editor.putBoolean("first_run", false);
327             editor.putBoolean("collapsed", true);
328             editor.apply();
329
330             dialog = U.showRecentAppsDialog(U.wrapContext(this),
331                     () -> dialog = U.showErrorDialog(U.wrapContext(this), "GET_USAGE_STATS"),
332                     null);
333         }
334
335         if(FeatureFlags.homeActivityUIHost()) {
336             // Stop any currently running services and switch to using HomeActivityDelegate as UI host
337             stopService(new Intent(this, TaskbarService.class));
338             stopService(new Intent(this, StartMenuService.class));
339             stopService(new Intent(this, DashboardService.class));
340
341             taskbarController = new TaskbarController(this);
342             startMenuController = new StartMenuController(this);
343             dashboardController = new DashboardController(this);
344
345             taskbarController.onCreateHost(this);
346             startMenuController.onCreateHost(this);
347             dashboardController.onCreateHost(this);
348         } else {
349             // We always start the Taskbar and Start Menu services, even if the app isn't normally running
350             startService(new Intent(this, TaskbarService.class));
351             startService(new Intent(this, StartMenuService.class));
352             startService(new Intent(this, DashboardService.class));
353         }
354
355         if(pref.getBoolean("taskbar_active", false) && !U.isServiceRunning(this, NotificationService.class))
356             pref.edit().putBoolean("taskbar_active", false).apply();
357
358         // Show the Taskbar temporarily, as nothing else will be visible on screen
359         new Handler().postDelayed(() -> LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.TEMP_SHOW_TASKBAR")), 100);
360     }
361
362     private void startFreeformHack() {
363         if(shouldDelayFreeformHack)
364             hits++;
365         else
366             U.startFreeformHack(this);
367     }
368
369     @Override
370     protected void onStop() {
371         super.onStop();
372
373         SharedPreferences pref = U.getSharedPreferences(this);
374         if(!U.canBootToFreeform(this)) {
375             LauncherHelper.getInstance().setOnHomeScreen(false);
376
377             if(U.shouldCollapse(this, true))
378                 LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.TEMP_HIDE_TASKBAR"));
379             else
380                 LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
381
382             if(FeatureFlags.homeActivityUIHost()) {
383                 if(taskbarController != null) taskbarController.onDestroyHost(this);
384                 if(startMenuController != null) startMenuController.onDestroyHost(this);
385                 if(dashboardController != null) dashboardController.onDestroyHost(this);
386
387                 IconCache.getInstance(this).clearCache();
388
389                 // Stop using HomeActivityDelegate as UI host and restart services if needed
390                 if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
391                     startService(new Intent(this, TaskbarService.class));
392                     startService(new Intent(this, StartMenuService.class));
393                     startService(new Intent(this, DashboardService.class));
394                 }
395             } else {
396                 // Stop the Taskbar and Start Menu services if they should normally not be active
397                 if(!pref.getBoolean("taskbar_active", false) || pref.getBoolean("is_hidden", false)) {
398                     stopService(new Intent(this, TaskbarService.class));
399                     stopService(new Intent(this, StartMenuService.class));
400                     stopService(new Intent(this, DashboardService.class));
401
402                     IconCache.getInstance(this).clearCache();
403                 }
404             }
405         }
406
407         if(dialog != null) {
408             dialog.dismiss();
409             dialog = null;
410         }
411     }
412
413     @Override
414     protected void onDestroy() {
415         super.onDestroy();
416
417         LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
418         lbm.unregisterReceiver(killReceiver);
419         lbm.unregisterReceiver(forceTaskbarStartReceiver);
420         lbm.unregisterReceiver(freeformToggleReceiver);
421
422         if(FeatureFlags.homeActivityUIHost())
423             lbm.unregisterReceiver(restartReceiver);
424
425         if(FeatureFlags.desktopIcons(this)) {
426             lbm.unregisterReceiver(refreshDesktopIconsReceiver);
427             lbm.unregisterReceiver(iconArrangeModeReceiver);
428             lbm.unregisterReceiver(sortDesktopIconsReceiver);
429             lbm.unregisterReceiver(updateMarginsReceiver);
430         }
431     }
432
433     @Override
434     protected void onSaveInstanceState(Bundle outState) {
435         outState.putBoolean("icon_arrange_mode", iconArrangeMode);
436         super.onSaveInstanceState(outState);
437     }
438
439     @Override
440     public void onBackPressed() {
441         LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
442     }
443
444     private void killHomeActivity() {
445         LauncherHelper.getInstance().setOnHomeScreen(false);
446
447         if(FeatureFlags.homeActivityUIHost()) {
448             if(taskbarController != null) taskbarController.onDestroyHost(this);
449             if(startMenuController != null) startMenuController.onDestroyHost(this);
450             if(dashboardController != null) dashboardController.onDestroyHost(this);
451
452             IconCache.getInstance(this).clearCache();
453
454             U.stopFreeformHack(this);
455
456             // Stop using HomeActivityDelegate as UI host and restart services if needed
457             SharedPreferences pref = U.getSharedPreferences(this);
458             if(pref.getBoolean("taskbar_active", false) && !pref.getBoolean("is_hidden", false)) {
459                 startService(new Intent(this, TaskbarService.class));
460                 startService(new Intent(this, StartMenuService.class));
461                 startService(new Intent(this, DashboardService.class));
462             }
463         } else {
464             // Stop the Taskbar and Start Menu services if they should normally not be active
465             SharedPreferences pref = U.getSharedPreferences(this);
466             if(!pref.getBoolean("taskbar_active", false) || pref.getBoolean("is_hidden", false)) {
467                 stopService(new Intent(this, TaskbarService.class));
468                 stopService(new Intent(this, StartMenuService.class));
469                 stopService(new Intent(this, DashboardService.class));
470
471                 IconCache.getInstance(this).clearCache();
472
473                 U.stopFreeformHack(this);
474             }
475         }
476
477         finish();
478     }
479
480     private void updateWindowFlags() {
481         int flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
482         if(FreeformHackHelper.getInstance().isTouchAbsorberActive() && U.isOverridingFreeformHack(this))
483             getWindow().setFlags(flags, flags);
484         else
485             getWindow().clearFlags(flags);
486     }
487
488     @Override
489     public void addView(View view, ViewParams params) {
490         final FrameLayout.LayoutParams flParams = new FrameLayout.LayoutParams(
491                 params.width,
492                 params.height
493         );
494
495         if(params.gravity > -1)
496             flParams.gravity = params.gravity;
497
498         view.setLayoutParams(flParams);
499         layout.addView(view);
500     }
501
502     @Override
503     public void removeView(View view) {
504         layout.removeView(view);
505     }
506
507     @Override
508     public void terminate() {
509         // no-op
510     }
511
512     private void initDesktopIcons() {
513         desktopIcons = new GridLayout(this);
514         fab = new FloatingActionButton(this);
515
516         updateMargins();
517         refreshDesktopIcons();
518
519         fab.setImageResource(R.drawable.ic_done_black_24dp);
520         fab.setOnClickListener(v -> {
521             iconArrangeMode = false;
522             fab.hide();
523             refreshDesktopIcons();
524         });
525
526         if(!iconArrangeMode) fab.hide();
527
528         layout.addView(desktopIcons, 0);
529         layout.addView(fab, 1);
530     }
531
532     private void refreshDesktopIcons() {
533         boolean taskbarIsVertical = U.getTaskbarPosition(this).contains("vertical");
534         int iconSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
535         int desktopIconSize = getResources().getDimensionPixelSize(R.dimen.start_menu_grid_width);
536
537         int columns = (layout.getWidth() - (taskbarIsVertical ? iconSize : 0)) / desktopIconSize;
538         int rows = (layout.getHeight() - (!taskbarIsVertical ? iconSize : 0)) / desktopIconSize;
539
540         desktopIcons.removeAllViews();
541         desktopIcons.setOrientation(GridLayout.VERTICAL);
542         desktopIcons.setColumnCount(columns);
543         desktopIcons.setRowCount(rows);
544
545         SparseArray<DesktopIconInfo> icons = new SparseArray<>();
546
547         try {
548             SharedPreferences pref = U.getSharedPreferences(this);
549             JSONArray jsonIcons = new JSONArray(pref.getString("desktop_icons", "[]"));
550
551             for(int i = 0; i < jsonIcons.length(); i++) {
552                 DesktopIconInfo info = DesktopIconInfo.fromJson(jsonIcons.getJSONObject(i));
553                 if(info != null)
554                     icons.put(getIndex(info), info);
555             }
556         } catch (JSONException e) { /* Gracefully fail */ }
557
558         for(int i = 0; i < columns * rows; i++) {
559             FrameLayout iconContainer = new FrameLayout(this);
560             iconContainer.setLayoutParams(new GridLayout.LayoutParams(new ViewGroup.LayoutParams(desktopIconSize, desktopIconSize)));
561             iconContainer.setOnDragListener(new DesktopIconDragListener());
562
563             int index = i;
564
565             iconContainer.setOnClickListener(view -> {
566                 boolean isStartMenuOpen = MenuHelper.getInstance().isStartMenuOpen();
567                 LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.farmerbb.taskbar.HIDE_START_MENU"));
568
569                 DesktopIconInfo info = icons.get(index);
570                 if(!isStartMenuOpen && info != null && info.entry != null) {
571                     U.launchApp(
572                             this,
573                             info.entry.getPackageName(),
574                             info.entry.getComponentName(),
575                             info.entry.getUserId(this),
576                             null,
577                             false,
578                             false
579                     );
580                 }
581             });
582
583             iconContainer.setOnLongClickListener(view -> {
584                 int[] location = new int[2];
585                 view.getLocationOnScreen(location);
586
587                 DesktopIconInfo info = icons.get(index);
588                 if(info == null) info = getDesktopIconInfo(index);
589
590                 openContextMenu(info, location);
591                 return true;
592             });
593
594             iconContainer.setOnGenericMotionListener((view, motionEvent) -> {
595                 int action = motionEvent.getAction();
596
597                 if(action == MotionEvent.ACTION_BUTTON_PRESS
598                         && motionEvent.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
599                     int[] location = new int[2];
600                     view.getLocationOnScreen(location);
601
602                     DesktopIconInfo info = icons.get(index);
603                     if(info == null) info = getDesktopIconInfo(index);
604
605                     openContextMenu(info, location);
606                 }
607
608                 return false;
609             });
610
611             DesktopIconInfo info = icons.get(index);
612             if(info != null && info.entry != null)
613                 iconContainer.addView(inflateDesktopIcon(iconContainer, info.entry));
614
615             desktopIcons.addView(iconContainer);
616         }
617     }
618
619     private void sortDesktopIcons() {
620         try {
621             SharedPreferences pref = U.getSharedPreferences(this);
622             JSONArray jsonIcons = new JSONArray(pref.getString("desktop_icons", "[]"));
623
624             if(jsonIcons.length() == 0) {
625                 U.showToast(this, R.string.no_icons_to_sort);
626                 return;
627             }
628
629             List<DesktopIconInfo> icons = new ArrayList<>();
630
631             for(int i = 0; i < jsonIcons.length(); i++) {
632                 DesktopIconInfo info = DesktopIconInfo.fromJson(jsonIcons.getJSONObject(i));
633                 if(info != null)
634                     icons.add(info);
635             }
636
637             Collections.sort(icons, (o1, o2) -> Collator.getInstance().compare(o1.entry.getLabel(), o2.entry.getLabel()));
638
639             jsonIcons = new JSONArray();
640
641             for(int i = 0; i < icons.size(); i++) {
642                 DesktopIconInfo oldInfo = icons.get(i);
643                 DesktopIconInfo newInfo = getDesktopIconInfo(i);
644
645                 oldInfo.column = newInfo.column;
646                 oldInfo.row = newInfo.row;
647
648                 jsonIcons.put(oldInfo.toJson(this));
649             }
650
651             pref.edit().putString("desktop_icons", jsonIcons.toString()).apply();
652             refreshDesktopIcons();
653         } catch (JSONException e) { /* Gracefully fail */ }
654     }
655
656     private void reassignDroppedIcon() {
657         if(startDragIndex == endDragIndex) return;
658
659         try {
660             SharedPreferences pref = U.getSharedPreferences(this);
661             JSONArray jsonIcons = new JSONArray(pref.getString("desktop_icons", "[]"));
662             int iconToRemove = -1;
663
664             DesktopIconInfo oldInfo = getDesktopIconInfo(startDragIndex);
665             DesktopIconInfo newInfo = getDesktopIconInfo(endDragIndex);
666
667             for(int i = 0; i < jsonIcons.length(); i++) {
668                 DesktopIconInfo info = DesktopIconInfo.fromJson(jsonIcons.getJSONObject(i));
669                 if(info != null && info.column == oldInfo.column && info.row == oldInfo.row) {
670                     newInfo.entry = info.entry;
671                     iconToRemove = i;
672                     break;
673                 }
674             }
675
676             if(iconToRemove > -1) {
677                 jsonIcons.remove(iconToRemove);
678                 jsonIcons.put(newInfo.toJson(this));
679
680                 pref.edit().putString("desktop_icons", jsonIcons.toString()).apply();
681             }
682         } catch (JSONException e) { /* Gracefully fail */ }
683     }
684
685     private void enterIconArrangeMode() {
686         try {
687             SharedPreferences pref = U.getSharedPreferences(this);
688             JSONArray jsonIcons = new JSONArray(pref.getString("desktop_icons", "[]"));
689
690             if(jsonIcons.length() == 0) {
691                 U.showToast(this, R.string.no_icons_to_arrange);
692                 return;
693             }
694
695             iconArrangeMode = true;
696             fab.show();
697         } catch (JSONException e) { /* Gracefully fail */ }
698     }
699
700     private void updateMargins() {
701         if(desktopIcons == null || fab == null) return;
702
703         String position = U.getTaskbarPosition(this);
704         int iconSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
705
706         int left = 0;
707         int top = 0;
708         int right = 0;
709         int bottom = 0;
710
711         if(position.contains("vertical_left"))
712             left = iconSize;
713         else if(position.contains("vertical_right"))
714             right = iconSize;
715         else if(position.contains("bottom"))
716             bottom = iconSize;
717         else
718             top = iconSize;
719
720         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
721                 FrameLayout.LayoutParams.MATCH_PARENT,
722                 FrameLayout.LayoutParams.MATCH_PARENT
723         );
724
725         params.setMargins(left, top, right, bottom);
726         desktopIcons.setLayoutParams(params);
727
728         int fabMargin = getResources().getDimensionPixelSize(R.dimen.desktop_icon_fab_margin);
729         left += fabMargin;
730         top += fabMargin;
731         right += fabMargin;
732         bottom += fabMargin;
733
734         FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams(
735                 FrameLayout.LayoutParams.WRAP_CONTENT,
736                 FrameLayout.LayoutParams.WRAP_CONTENT
737         );
738
739         params2.gravity = Gravity.BOTTOM | Gravity.END;
740         params2.setMargins(left, top, right, bottom);
741         fab.setLayoutParams(params2);
742     }
743
744     private int getIndex(DesktopIconInfo info) {
745         return (info.column * desktopIcons.getRowCount()) + info.row;
746     }
747
748     private DesktopIconInfo getDesktopIconInfo(int index) {
749         int row = index % desktopIcons.getRowCount();
750
751         int pos = index;
752         int column = -1;
753
754         while(pos >= 0) {
755             pos -= desktopIcons.getRowCount();
756             column++;
757         }
758
759         return new DesktopIconInfo(column, row, null);
760     }
761
762     private View inflateDesktopIcon(ViewGroup parent, AppEntry entry) {
763         View icon = LayoutInflater.from(this).inflate(R.layout.row_alt, parent, false);
764
765         TextView textView = icon.findViewById(R.id.name);
766         textView.setText(entry.getLabel());
767         textView.setTextColor(ContextCompat.getColor(this, R.color.desktop_icon_text));
768         textView.setShadowLayer(50, 0, 0, R.color.desktop_icon_shadow);
769
770         ImageView imageView = icon.findViewById(R.id.icon);
771         imageView.setImageDrawable(entry.getIcon(this));
772
773         icon.setOnTouchListener(new DesktopIconTouchListener());
774         return icon;
775     }
776
777     private void openContextMenu(final DesktopIconInfo info, final int[] location) {
778         if(iconArrangeMode) return;
779
780         Bundle args = new Bundle();
781
782         if(info.entry != null) {
783             args.putString("package_name", info.entry.getPackageName());
784             args.putString("app_name", info.entry.getLabel());
785             args.putString("component_name", info.entry.getComponentName());
786             args.putLong("user_id", info.entry.getUserId(this));
787         }
788
789         args.putSerializable("desktop_icon", info);
790         args.putInt("x", location[0]);
791         args.putInt("y", location[1]);
792
793         U.startContextMenuActivity(this, args);
794     }
795
796     private final class DesktopIconTouchListener implements View.OnTouchListener {
797         @Override
798         public boolean onTouch(View view, MotionEvent motionEvent) {
799             if(iconArrangeMode && motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
800                 startDragIndex = desktopIcons.indexOfChild((ViewGroup) view.getParent());
801
802                 ClipData data = ClipData.newPlainText("", "");
803                 View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
804                 view.startDrag(data, shadowBuilder, view, 0);
805                 view.setVisibility(View.INVISIBLE);
806                 return true;
807             } else
808                 return false;
809         }
810     }
811
812     private final class DesktopIconDragListener implements View.OnDragListener {
813         @Override
814         public boolean onDrag(View v, DragEvent event) {
815             switch(event.getAction()) {
816                 case DragEvent.ACTION_DRAG_STARTED:
817                 default:
818                     // do nothing
819                     break;
820                 case DragEvent.ACTION_DRAG_ENTERED:
821                     FrameLayout container = (FrameLayout) v;
822                     if(container.getChildCount() == 0
823                             || startDragIndex == desktopIcons.indexOfChild(container)) {
824                         v.setBackgroundColor(U.getAccentColor(HomeActivityDelegate.this));
825                         v.setAlpha(0.5f);
826                     }
827                     break;
828                 case DragEvent.ACTION_DRAG_ENDED:
829                     View view = (View) event.getLocalState();
830                     view.setVisibility(View.VISIBLE);
831                     // fall through
832                 case DragEvent.ACTION_DRAG_EXITED:
833                     v.setBackground(null);
834                     v.setAlpha(1);
835                     break;
836                 case DragEvent.ACTION_DROP:
837                     FrameLayout container2 = (FrameLayout) v;
838                     if(container2.getChildCount() == 0) {
839                         // Dropped, reassign View to ViewGroup
840                         View view2 = (View) event.getLocalState();
841                         ViewGroup owner = (ViewGroup) view2.getParent();
842                         owner.removeView(view2);
843                         container2.addView(view2);
844
845                         endDragIndex = desktopIcons.indexOfChild(container2);
846                         reassignDroppedIcon();
847                     }
848                     break;
849             }
850             return true;
851         }
852     }
853 }